Flick
Guide Validation Services API Pro Examples GitHub
Docs / Features

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.