Flick
Guide Validation Services API Pro Examples GitHub
Docs / Features

Framework Integration

Flick works great as a standalone library, and it also integrates seamlessly with PHP frameworks like Laravel, Symfony, and others. Response handlers let you control how Flick handles redirects, security events, and AJAX responses — returning framework-native responses instead of Flick's defaults.

Tip

This page covers response handling (outputs). For reading request data from framework Request objects, see Request Adapters. For session management, see Session Adapters.

Quick Start

Here's how to integrate Flick with Laravel in under a minute:

use Flick\Flick;

$form = new Flick([
    'views' => 'tailwind',
    'onRedirect' => fn($response) => redirect($response->getUrl()),
    'onHoneypot' => fn() => abort(403),
]);

if ($form->submitted() && $form->ok()) {
    // Returns a Laravel redirect instead of calling exit()
    return $form->redirect('/thank-you');
}

Available Handlers

Flick provides five configurable response handlers:

Handler When It's Called Default Behavior
onRedirect When redirect() is called after successful submission Sends redirect header
onJson When an AJAX form submission is processed Outputs JSON response
onException When an uncaught exception is displayed Shows HTML error page
onHoneypot When a honeypot field is filled (bot detected) Silently rejects request
onCsrfExpired When the CSRF token has expired Shows error message

Handler Signatures

Each handler receives specific data relevant to its event:

onRedirect

'onRedirect' => function (\Flick\Http\RedirectResponse $response) {
    // $response->getUrl()        - The redirect URL
    // $response->getStatusCode() - HTTP status (default: 302)

    return redirect($response->getUrl());
}

onJson

'onJson' => function (\Flick\Http\JsonResponse $response) {
    // $response->getData()       - The response data array
    // $response->getStatusCode() - HTTP status (default: 200)

    return response()->json($response->getData(), $response->getStatusCode());
}

onException

'onException' => function (\Flick\Http\HtmlResponse $response) {
    // $response->getContent()    - The HTML content
    // $response->getStatusCode() - HTTP status (default: 500)

    // Option 1: Use framework's error handling
    throw new \Exception('Form error occurred');

    // Option 2: Return the HTML response
    return response($response->getContent(), $response->getStatusCode());
}

onHoneypot

'onHoneypot' => function () {
    // No parameters - honeypot was triggered, bot detected

    // Option 1: Abort with 403 Forbidden
    abort(403);

    // Option 2: Silently redirect somewhere
    return redirect('/');

    // Option 3: Log and continue (return null to continue execution)
    Log::warning('Bot detected');
    return null;
}

onCsrfExpired

'onCsrfExpired' => function (string $message) {
    // $message - The expiration message (e.g., "Session has expired")

    // Option 1: Throw Laravel's token mismatch exception
    throw new \Illuminate\Session\TokenMismatchException($message);

    // Option 2: Redirect back with error
    return back()->withErrors(['csrf' => $message]);
}

Framework Examples

Laravel

Create a service provider that configures Flick's adapters and a factory for response handlers:

// app/Providers/FlickServiceProvider.php
namespace App\Providers;

use Flick\Flick;
use Illuminate\Support\ServiceProvider;

class FlickServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Set up request and session adapters
        Flick::setDefaultRequest(new LaravelRequest(request()));
        Flick::setDefaultSession(new LaravelSession(session()));
    }
}

Session Integration

For frameworks that manage their own sessions, you can configure a session adapter. See Session Adapters for details on creating adapters and the full SessionInterface.

Create a factory for response handlers:

// app/Services/FlickFactory.php
namespace App\Services;

use Flick\Flick;
use Flick\Http\ResponseHandlers;
use Illuminate\Session\TokenMismatchException;

class FlickFactory
{
    public static function create(array $config = []): Flick
    {
        $defaults = [
            'views' => 'tailwind',
            'onRedirect' => fn($r) => redirect($r->getUrl()),
            'onJson' => fn($r) => response()->json($r->getData(), $r->getStatusCode()),
            'onHoneypot' => fn() => abort(403),
            'onCsrfExpired' => fn($m) => throw new TokenMismatchException($m),
        ];

        return new Flick(array_merge($defaults, $config));
    }
}

Use it in your controllers:

use App\Services\FlickFactory;

class ContactController extends Controller
{
    public function show()
    {
        $form = FlickFactory::create();

        if ($form->submitted() && $form->ok()) {
            $data = $form->request('Name[required], Email[email, required], Message');

            // Process the form...

            return $form->redirect('/thank-you');
        }

        return view('contact', ['form' => $form]);
    }
}

Symfony

use Flick\Flick;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

$form = new Flick([
    'views' => 'bootstrap',
    'onRedirect' => fn($r) => new RedirectResponse($r->getUrl(), $r->getStatusCode()),
    'onJson' => fn($r) => new JsonResponse($r->getData(), $r->getStatusCode()),
    'onHoneypot' => fn() => throw new AccessDeniedHttpException(),
    'onCsrfExpired' => fn($m) => throw new AccessDeniedHttpException($m),
]);

Slim Framework

use Flick\Flick;
use Slim\Psr7\Response;

$form = new Flick([
    'onRedirect' => function($r) use ($response) {
        return $response
            ->withHeader('Location', $r->getUrl())
            ->withStatus($r->getStatusCode());
    },
    'onJson' => function($r) use ($response) {
        $response->getBody()->write(json_encode($r->getData()));
        return $response
            ->withHeader('Content-Type', 'application/json')
            ->withStatus($r->getStatusCode());
    },
]);

Using the ResponseHandlers Class

For more control, you can create a ResponseHandlers instance and pass it directly:

use Flick\Flick;
use Flick\Http\ResponseHandlers;

$handlers = (new ResponseHandlers())
    ->onRedirect(fn($r) => redirect($r->getUrl()))
    ->onJson(fn($r) => response()->json($r->getData()))
    ->onException(fn($r) => response($r->getContent(), 500))
    ->onHoneypot(fn() => abort(403))
    ->onCsrfExpired(fn($m) => throw new TokenMismatchException($m));

$form = new Flick([
    'handlers' => $handlers,
    'views' => 'tailwind',
]);

This approach is useful when you want to:

  • Share handlers across multiple forms
  • Configure handlers in a service provider
  • Test handlers in isolation

Response Objects

Flick's response objects provide a clean interface for accessing response data:

RedirectResponse

$response->getUrl();        // string - The redirect URL
$response->getStatusCode(); // int - HTTP status (default: 302)
$response->toArray();       // array - ['type' => 'redirect', 'url' => '...', 'statusCode' => 302]

JsonResponse

$response->getData();       // array - The response data
$response->getStatusCode(); // int - HTTP status (default: 200)
$response->toArray();       // array - ['type' => 'json', 'data' => [...], 'statusCode' => 200]

HtmlResponse

$response->getContent();    // string - The HTML content
$response->getStatusCode(); // int - HTTP status (default: 200 or 500)
$response->toArray();       // array - ['type' => 'html', 'content' => '...', 'statusCode' => 500]

Standalone Behavior (Default)

If you don't configure any handlers, Flick works exactly as you'd expect in standalone PHP — redirects happen immediately, security checks are handled automatically. No configuration needed:

$form = new Flick('bootstrap');

if ($form->submitted() && $form->ok()) {
    $form->redirect('/thank-you'); // Redirects immediately
}

Testing with Response Handlers

Response handlers make testing easy by letting you intercept responses:

public function test_form_redirects_after_submission()
{
    $capturedResponse = null;

    $form = new Flick([
        'onRedirect' => function($response) use (&$capturedResponse) {
            $capturedResponse = $response;
            return null; // Don't actually redirect
        },
    ]);

    // Simulate form submission...
    $_POST = ['name' => 'John', 'email' => 'john@example.com'];
    $_SERVER['REQUEST_METHOD'] = 'POST';

    $form->redirect('/thank-you');

    $this->assertEquals('/thank-you', $capturedResponse->getUrl());
    $this->assertEquals(302, $capturedResponse->getStatusCode());
}

Best Practices

  1. Configure handlers once — Set up handlers in a factory or service provider, not in every controller.

  2. Return framework responses — Always return the framework's response type from your handlers.

  3. Handle honeypots gracefully — Consider logging honeypot triggers for security monitoring.

  4. Don't suppress CSRF errors — Always throw an exception or show an error for CSRF failures.

  5. Test your handlers — Response handlers make Flick forms fully testable.