Flick
Guide Validation Services API Pro Examples GitHub
Docs / Features

Session Adapters

Flick abstracts session management behind a clean interface, enabling seamless framework integration while maintaining zero-config standalone usage. When you use Laravel or Symfony, their session managers can be wrapped and injected — but if you're using plain PHP, Flick "just works" with native sessions.

Quick Start

By default, Flick uses NativeSession which wraps PHP's native session functions. No configuration needed:

$form = new Flick\Flick('bootstrap');

// Flick automatically manages sessions for CSRF, multistep forms, etc.
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\Session\ArraySession;

$session = new ArraySession(['user_id' => 123]);

$form = new Flick\Flick([
    'session' => $session,
    'csrf' => false,
]);

Why Use Session Adapters?

Session adapters solve three common problems:

Framework Integration — Laravel, Symfony, and other frameworks manage their own sessions. Adapters let Flick work with these session managers instead of calling session_start() directly.

Testing — Unit tests shouldn't interact with PHP's native session system. With ArraySession, you can inject test data and verify session operations cleanly:

public function test_login_regenerates_session_id()
{
    $session = new ArraySession();

    $form = new Flick(['session' => $session, 'csrf' => false]);
    // ... perform login ...
    $form->session->regenerateId(true);

    expect($session->wasRegenerated())->toBeTrue();
}

Namespace Isolation — All Flick session data is stored under $_SESSION['flick'] to avoid conflicts with your application's session data.

Available Adapters

Adapter Purpose
NativeSession Default — wraps PHP native sessions with secure defaults
ArraySession Testing utility with in-memory storage and assertion helpers

Using NativeSession (Default)

NativeSession is used automatically when no adapter is specified. It provides:

  • Auto-start behavior — Sessions start automatically when needed
  • Secure cookie defaultshttponly, secure (on HTTPS), and samesite=Strict
  • Namespace isolation — All data stored under $_SESSION['flick']
use Flick\Session\NativeSession;

$session = new NativeSession();

// Store and retrieve values
$session->setValue('step', 1);
$step = $session->getValue('step'); // 1

// Check for values
if ($session->hasValue('step')) {
    $session->deleteValue('step');
}

You typically don't need to interact with NativeSession directly — Flick creates one internally.

Using ArraySession for Testing

ArraySession provides an in-memory session for testing, with helpers to verify session operations:

Constructor

use Flick\Session\ArraySession;

// Empty session
$session = new ArraySession();

// Pre-populated session
$session = new ArraySession(['user_id' => 123, 'role' => 'admin']);

// Inactive session (for testing edge cases)
$session = new ArraySession([], false);

Testing Helpers

ArraySession provides six testing helpers:

Method Returns Description
wasRegenerated() bool Check if regenerateId() was called
getRegenerateCount() int Count how many times regenerateId() was called
wasDestroyed() bool Check if destroy() was called
getAllValues() array Get all stored values for assertions
setActive(bool) static Simulate inactive session state
resetFlags() static Reset testing flags (regenerated, destroyed)

Complete Test Example

Here's a complete test showing a login flow with session regeneration:

use Flick\Flick;
use Flick\Http\ArrayRequest;
use Flick\Session\ArraySession;

test('login regenerates session ID to prevent fixation', function () {
    // Set up test fixtures
    $request = ArrayRequest::createPost([
        '_id' => 'login',
        'email' => 'user@example.com',
        'password' => 'secret123',
    ]);

    $session = new ArraySession();

    $form = new Flick([
        'id' => 'login',        // must match the posted _id
        'request' => $request,
        'session' => $session,
        'csrf' => false,
    ]);

    // Simulate login process
    $data = $form->request('Email[email, required], Password[required]');

    if ($form->ok()) {
        // Verify credentials (mocked)
        $authenticated = true;

        if ($authenticated) {
            // Security: regenerate session ID after login
            $form->session->regenerateId(true);
            $form->session->setValue('user_id', 42);
        }
    }

    // Assert session was properly secured
    expect($session->wasRegenerated())->toBeTrue();
    expect($session->getValue('user_id'))->toBe(42);
});

Framework Integration

Laravel

Don't write this one yourself — install the integration package:

composer require flickphp/laravel

It is auto-discovered, and ships a LaravelSession adapter already installed as the default, so multistep forms and CSRF tokens use Laravel's session with no setup:

// In any controller
$form = new Flick('tailwind');

// Uses Laravel's session, not $_SESSION
$form->session->setValue('form_step', 2);

It also wires up Laravel's request, 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();
        $session = $request->getSession();

        Flick::setDefaultRequest(new SymfonyRequest($request));
        Flick::setDefaultSession(new SymfonySession($session));
    }
}

SymfonyRequest and SymfonySession are adapters you write yourself — Flick doesn't ship them. See Creating Custom Adapters below for the interface to implement.

Disabling Auto-Start

sessionAutoStart only applies when Flick falls back to its own NativeSession — when you provide a session adapter (via the session key or setDefaultSession()), that adapter controls the session lifecycle and the flag is ignored. Use it when you want native PHP sessions but your app calls session_start() itself:

$form = new Flick([
    'sessionAutoStart' => false, // The app already started the native session
]);

Creating Custom Adapters

To integrate with any framework, implement SessionInterface:

use Flick\Session\SessionInterface;
use Illuminate\Session\Store;

class LaravelSession implements SessionInterface
{
    public function __construct(private Store $session) {}

    public function isActive(): bool
    {
        return $this->session->isStarted();
    }

    public function start(): void
    {
        if (!$this->session->isStarted()) {
            $this->session->start();
        }
    }

    public function regenerateId(bool $deleteOldSession = false): void
    {
        $this->session->regenerate($deleteOldSession);
    }

    public function setValue(string $key, mixed $value): void
    {
        $this->session->put("flick.{$key}", $value);
    }

    public function getValue(string $key): mixed
    {
        return $this->session->get("flick.{$key}");
    }

    public function hasValue(string $key): bool
    {
        // exists(), not has(): Laravel's has() reports a stored null as absent,
        // and hasValue() is a presence check
        return $this->session->exists("flick.{$key}");
    }

    public function deleteValue(string $key): void
    {
        $this->session->forget("flick.{$key}");
    }

    public function destroy(): void
    {
        $this->session->forget('flick');
    }

    public function getAll(): array
    {
        return $this->session->get('flick', []);
    }
}

Warning

getAll() is easy to miss — it returns everything Flick has stored, and multistep forms rely on it. Leave it out and PHP fatals at class-definition time with "contains 1 abstract method and must be declared abstract". The full interface is isActive, start, regenerateId, setValue, getValue, hasValue, deleteValue, destroy, getAll.

Session Resolution Order

Flick resolves which session adapter to use in this order:

  1. Explicit session key in config — Highest priority
  2. Static default via Flick::setDefaultSession() — For service providers
  3. Falls back to NativeSession — With sessionAutoStart consideration
// 1. Explicit adapter in config (highest priority)
$form = new Flick(['session' => new ArraySession()]);

// 2. Static default (set once in service provider)
Flick::setDefaultSession(new LaravelSession(session()));
$form = new Flick('bootstrap'); // Uses Laravel session

// 3. Falls back to NativeSession
Flick::resetDefaultSession();
$form = new Flick('bootstrap'); // Uses NativeSession

Security: Session Regeneration

Security

Always call regenerateId(true) after authentication state changes (login/logout) to prevent session fixation attacks:

// After successful login
if ($authenticated) {
    $form->session->regenerateId(true);  // true = delete old session data
    $form->session->setValue('user_id', $user->id);
}

// After logout
$form->session->destroy();
$form->session->regenerateId(true);

Session fixation attacks occur when an attacker sets a victim's session ID before they authenticate. By regenerating the session ID after login, you ensure the attacker's known session ID becomes invalid.

SessionInterface Reference

All adapters implement these methods:

Method Returns Description
isActive() bool Check if session is active and ready
start() void Start the session if not already started
regenerateId(bool $delete) void Regenerate session ID (security)
setValue(string $key, mixed $value) void Store a value in the Flick namespace
getValue(string $key) mixed Retrieve a value (or null if not found)
hasValue(string $key) bool Check if a value exists (presence, not truthiness — see below)
deleteValue(string $key) void Remove a value
destroy() void Destroy all Flick session data
getAll() array Get every value Flick has stored (multistep forms rely on it)

`hasValue()` checks presence, not truthiness

A stored '0', 0, '', false or null is still a stored value, and your adapter must report it as present. Reach for a presence check (array_key_exists(), Laravel's exists()), never empty() or Laravel's has() — both of those call a stored value absent and break repopulating a field whose value is '0'.

Static Methods

Flick provides static methods for managing the default session adapter:

Flick::setDefaultSession

Sets the default session adapter for all Flick instances.

use Flick\Flick;

// In a service provider
Flick::setDefaultSession(new LaravelSession(session()));

// All subsequent Flick instances use this adapter
$form = new Flick('bootstrap');

Flick::getDefaultSession

Returns the currently configured default session adapter, or null if not set.

$session = Flick::getDefaultSession();

Flick::resetDefaultSession

Resets the default session adapter to null (useful in tests).

beforeEach(function () {
    Flick::resetDefaultSession();
});

Tip

setDefaultSession() is ideal for service providers where you configure Flick once for your entire application.