Request Adapters
Flick abstracts HTTP request data behind a clean interface, enabling seamless framework integration while maintaining zero-config standalone usage. When you use Laravel or Symfony, their Request objects can be wrapped and injected — but if you're using plain PHP, Flick "just works" with native superglobals.
Quick Start
By default, Flick uses NativeRequest which wraps PHP's superglobals ($_POST, $_GET, etc.). No configuration needed:
$form = new Flick\Flick('bootstrap'); if ($form->submitted()) { $data = $form->request('Name, Email, Message'); }
To use a custom adapter (for framework integration or testing), pass it in the config:
use Flick\Http\ArrayRequest; $request = ArrayRequest::createPost([ '_id' => 'myForm', // matches the form's id, so submitted() is true 'email' => 'test@example.com', ]); $form = new Flick\Flick([ 'request' => $request, 'csrf' => false, ]);
Why Use Adapters?
Request adapters solve three common problems:
Framework Integration — Laravel, Symfony, and other frameworks use their own Request objects. Adapters let Flick read data from these objects instead of $_POST directly.
Testing — Unit tests shouldn't manipulate superglobals. With ArrayRequest, you can inject test data cleanly:
public function test_form_validates_email() { $request = ArrayRequest::createPost([ '_id' => 'contact', 'email' => 'invalid-email', ]); $form = new Flick([ 'id' => 'contact', // must match the posted _id 'request' => $request, 'csrf' => false, ]); $form->request('Email[email]'); expect($form->ok())->toBeFalse(); }
Warning
The _id has to match the form's id. Flick decides whether a form was submitted
by comparing the posted _id against the form's own id (default 'myForm'). If
they don't match, submitted() is false, request() returns null without
validating anything, and ok() stays true — so a test like this passes when it
should fail.
Consistency — Whether you're in a framework, CLI script, or test suite, the same Flick code works everywhere.
Available Adapters
| Adapter | Purpose |
|---|---|
NativeRequest |
Default — wraps $_POST, $_GET, $_SERVER, $_FILES, $_COOKIE, $_ENV |
ArrayRequest |
Testing utility with static factories and fluent API |
Using NativeRequest (Default)
NativeRequest is used automatically when no adapter is specified. It wraps PHP's superglobals to provide a consistent interface:
use Flick\Http\NativeRequest; $request = new NativeRequest(); // These all work as you'd expect $email = $request->post('email'); $page = $request->query('page', 1); $method = $request->method(); $isSecure = $request->isSecure(); $clientIp = $request->ip();
You typically don't need to interact with NativeRequest directly — Flick creates one internally.
Using ArrayRequest for Testing
ArrayRequest provides static factory methods for common test scenarios:
Static Factories
use Flick\Http\ArrayRequest; // Simple POST request $request = ArrayRequest::createPost([ 'name' => 'John', 'email' => 'john@example.com', ]); // GET request with query parameters $request = ArrayRequest::createGet([ 'page' => '2', 'sort' => 'name', ]); // AJAX/XHR request $request = ArrayRequest::createAjax([ 'email' => 'john@example.com', ]); // Multipart form with file upload $request = ArrayRequest::createMultipart( ['name' => 'John'], ['avatar' => ['name' => 'photo.jpg', 'tmp_name' => '/tmp/abc', 'size' => 1024, 'error' => 0]] );
Fluent Setters
For complex scenarios, chain fluent setters:
$request = ArrayRequest::createPost(['email' => 'test@example.com']) ->setFile('avatar', [ 'name' => 'photo.jpg', 'tmp_name' => '/tmp/abc123', 'size' => 1024, 'error' => UPLOAD_ERR_OK, ]) ->withCookie('session', 'abc123') ->setIp('192.168.1.100') ->asSecure();
| Method | Description |
|---|---|
setPost(array $data) |
Set all POST data |
addPost(string $key, mixed $value) |
Add a single POST value |
setQuery(array $data) |
Set all query/GET data |
addQuery(string $key, mixed $value) |
Add a single query value |
setServer(array $data) |
Merge server data |
setFile(string $key, array $file) |
Set a file upload |
setFiles(array $files) |
Set all files |
withCookie(string $key, mixed $value) |
Set a cookie |
setCookies(array $cookies) |
Set all cookies |
setEnv(string $key, mixed $value) |
Set an environment variable |
setMethod(string $method) |
Set HTTP method |
setUri(string $uri) |
Set request URI |
setIp(string $ip) |
Set client IP address |
asAjax() |
Mark as AJAX request |
asSecure() |
Mark as HTTPS request |
Warning
setCookie() is not in this list on purpose. It's the RequestInterface method and
returns void, so putting it in a chain breaks the chain. Use withCookie() to seed
a cookie on a test fixture — see withCookie vs setCookie.
Testing a Form Submission
Here's a complete test example:
use Flick\Flick; use Flick\Http\ArrayRequest; test('contact form validates and processes submission', function () { $request = ArrayRequest::createPost([ '_id' => 'contact', 'name' => 'Jane Doe', 'email' => 'jane@example.com', 'message' => 'Hello, this is a test message.', ]); $form = new Flick([ 'id' => 'contact', // must match the posted _id 'request' => $request, 'csrf' => false, ]); $data = $form->request('Name[required], Email[email, required], Message[required]'); expect($form->ok())->toBeTrue(); expect($data['name'])->toBe('Jane Doe'); expect($data['email'])->toBe('jane@example.com'); });
Framework Integration
Laravel
Don't write this one yourself — install the integration package:
composer require flickphp/laravel
It is auto-discovered, and ships a LaravelRequest adapter already installed as
the default, so every Flick instance reads Laravel's request with no setup:
// In any controller $form = new Flick('tailwind'); if ($form->submitted()) { // Reads from Laravel's request, not $_POST $data = $form->request('Name, Email'); }
It also wires up Laravel's session, CSRF token and validator. See Flick for Laravel.
The rest of this page is for frameworks Flick has no package for.
Symfony
// src/EventSubscriber/FlickSubscriber.php namespace App\EventSubscriber; use Flick\Flick; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; class FlickSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents(): array { return [KernelEvents::REQUEST => 'onRequest']; } public function onRequest(RequestEvent $event): void { $request = $event->getRequest(); Flick::setDefaultRequest(new SymfonyRequest($request)); } }
SymfonyRequest here is an adapter you write yourself — Flick doesn't ship one.
See Creating Custom Adapters below for the interface
to implement.
Creating Custom Adapters
To integrate with any framework, implement RequestInterface:
use Flick\Http\RequestInterface; use Illuminate\Http\Request; class LaravelRequest implements RequestInterface { public function __construct(private Request $request) {} public function post(string $key, mixed $default = null): mixed { return $this->request->post($key, $default); } public function postAll(): array { return $this->request->post(); } public function hasPost(string $key): bool { return $this->request->has($key) && $this->request->isMethod('POST'); } public function query(string $key, mixed $default = null): mixed { return $this->request->query($key, $default); } public function queryAll(): array { return $this->request->query(); } public function hasQuery(string $key): bool { return $this->request->query->has($key); } public function input(string $key, mixed $default = null): mixed { return $this->request->input($key, $default); } public function all(): array { return $this->request->all(); } public function has(string $key): bool { return $this->request->has($key); } public function file(string $key): ?array { $file = $this->request->file($key); if (!$file) return null; return [ 'name' => $file->getClientOriginalName(), 'type' => $file->getMimeType(), 'tmp_name' => $file->getPathname(), 'error' => $file->getError(), 'size' => $file->getSize(), ]; } public function files(): array { return array_map(fn($file) => $this->file($file), array_keys($this->request->allFiles())); } public function hasFile(string $key): bool { return $this->request->hasFile($key); } public function server(string $key, mixed $default = null): mixed { return $this->request->server($key, $default); } public function method(): string { return $this->request->method(); } public function isMethod(string $method): bool { return $this->request->isMethod($method); } public function isAjax(): bool { return $this->request->ajax(); } public function cookie(string $key, mixed $default = null): mixed { return $this->request->cookie($key, $default); } public function hasCookie(string $key): bool { return $this->request->cookies->has($key); } public function setCookie(string $name, string $value, array $options = []): void { // Laravel sets cookies on the response, e.g. Cookie::queue($name, $value) } public function deleteCookie(string $key): void { // Laravel handles cookie deletion via response } public function header(string $key, mixed $default = null): mixed { return $this->request->header($key, $default); } public function env(string $key, mixed $default = null): mixed { return env($key, $default); } public function ip(): string { return $this->request->ip(); } public function isSecure(): bool { return $this->request->secure(); } public function uri(): string { return $this->request->getRequestUri(); } public function clear(): void { // Not typically needed in Laravel } }
RequestInterface Reference
All adapters implement these methods:
| Method | Returns | Description |
|---|---|---|
post($key, $default) |
mixed |
Get a POST value |
postAll() |
array |
Get all POST data |
hasPost($key) |
bool |
Check if POST key exists |
query($key, $default) |
mixed |
Get a query string value |
queryAll() |
array |
Get all query string data |
hasQuery($key) |
bool |
Check if query key exists |
input($key, $default) |
mixed |
Get from POST or GET (POST priority) |
all() |
array |
Get all input data |
has($key) |
bool |
Check if key exists in POST or GET |
file($key) |
?array |
Get uploaded file data |
files() |
array |
Get all uploaded files |
hasFile($key) |
bool |
Check if file was uploaded |
server($key, $default) |
mixed |
Get server variable |
method() |
string |
Get HTTP method (GET, POST, etc.) |
isMethod($method) |
bool |
Check if method matches |
isAjax() |
bool |
Check if AJAX/XHR request |
cookie($key, $default) |
mixed |
Get cookie value |
hasCookie($key) |
bool |
Check if cookie exists |
setCookie($name, $value, $options) |
void |
Set a cookie with secure defaults |
deleteCookie($key) |
void |
Delete a cookie |
header($key, $default) |
mixed |
Get request header |
env($key, $default) |
mixed |
Get environment variable |
ip() |
string |
Get client IP address |
isSecure() |
bool |
Check if HTTPS |
uri() |
string |
Get request URI |
clear() |
void |
Clear POST and GET data |
Setting Cookies
The request interface includes methods for setting and deleting cookies with secure defaults.
setCookie
Set a cookie with secure defaults:
$form->support->request()->setCookie('remember', 'token123', [ 'expires' => time() + 86400, // 1 day 'path' => '/', 'secure' => true, 'httponly' => true, 'samesite' => 'Strict', ]);
| Option | Default | Description |
|---|---|---|
expires |
0 |
Unix timestamp (0 = session cookie) |
path |
/ |
Cookie path |
secure |
auto | HTTPS only (auto-detected from request) |
httponly |
true |
Prevent JavaScript access |
samesite |
Strict |
CSRF protection |
deleteCookie
Remove a cookie:
$form->support->request()->deleteCookie('remember');
withCookie vs setCookie
Note the distinction in ArrayRequest:
withCookie($key, $value)— Fluent setter for test fixtures (populates the cookies array)setCookie($name, $value, $options)— Interface method that records metadata for testing assertions
// Setting up test fixtures (withCookie) — chainable, returns the request $request = ArrayRequest::createPost(['email' => 'test@example.com']) ->withCookie('session', 'existing-session-id'); // Testing cookie operations (setCookie) — returns void, never chain it $form->support->request()->setCookie('remember', 'token123');
Testing Cookie Operations
ArrayRequest provides helpers for verifying cookie operations in tests:
Cookie Testing Methods
| Method | Description |
|---|---|
getSetCookies() |
Get all cookies that were set via setCookie() |
wasCookieSet($name) |
Check if a specific cookie was set |
getSetCookie($name) |
Get details of a set cookie (value, options) |
wasCookieDeleted($name) |
Check if a cookie was deleted via deleteCookie() |
Example
use Flick\Flick; use Flick\Http\ArrayRequest; test('remember me sets a secure cookie', function () { $request = ArrayRequest::createPost([ '_id' => 'login', 'email' => 'user@example.com', 'remember' => '1', ]); $form = new Flick([ 'id' => 'login', // must match the posted _id 'request' => $request, 'csrf' => false, ]); // Simulate login with "remember me" if ($form->submitted()) { $form->support->request()->setCookie('remember', 'token123', [ 'expires' => time() + 86400 * 30, 'httponly' => true, ]); } // Assert the cookie was set correctly expect($request->wasCookieSet('remember'))->toBeTrue(); $cookie = $request->getSetCookie('remember'); expect($cookie['value'])->toBe('token123'); expect($cookie['options']['httponly'])->toBeTrue(); });
Static Methods
Flick provides static methods for managing the default request adapter:
use Flick\Flick; use Flick\Http\ArrayRequest; // Set a default adapter for all Flick instances. ArrayRequest takes its data // keyed by source: 'post', 'query', 'server', 'files', 'cookies' — // a flat array here would silently produce an empty request. Flick::setDefaultRequest(new ArrayRequest(['post' => ['name' => 'Gern']])); // Get the current default (or null) $default = Flick::getDefaultRequest(); // Reset to use NativeRequest Flick::resetDefaultRequest();
Tip
setDefaultRequest() is ideal for service providers where you configure Flick once for your entire application.