Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Session Laravel Package

odan/session

Odan/session is a session middleware for Slim and other PSR-15 applications. It provides a clean SessionInterface for reading and writing session data, flash messages, configurable session handlers, and straightforward PHP-DI integration for Slim 4.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and identify the three abstractions you will use most:

composer require odan/session
  • Odan\Session\SessionInterface — main session API: get(), set(), has(), remove(), clear(), regenerateId(), destroy(), save().
  • Odan\Session\FlashInterface — short-lived messages for the next request, available via $session->getFlash().
  • Odan\Session\SegmentInterface — namespaced session data, available via $session->getSegment('name').
  • Odan\Session\SessionMiddleware — optional PSR-15 middleware that starts the session before the handler and saves it afterward.

The smallest useful bootstrap looks like this:

use Odan\Session\Session;
use Odan\Session\SessionInterface;

$session = new Session([
    'name' => 'my_app_session',
    'cache_limiter' => '',
    'cookie_httponly' => true,
    'cookie_secure' => true,
    'cookie_samesite' => 'Lax',
    'use_strict_mode' => true,
]);

$session->start();

$session->set('user_id', 42);

var_dump($session->get('user_id')); // 42

Your first real use case should be the Post/Redirect/Get flash flow:

// POST /items
$session->getFlash()->add('success', 'Item created.');
header('Location: /items');
exit;
// GET /items on the next request
foreach ($session->getFlash()->get('success') as $message) {
    echo htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
}

Where to look first in the source/README:

  1. SessionInterface — confirms the exact method signatures for your installed version.
  2. FlashInterface — confirm how flash messages are read and cleared.
  3. SegmentInterface — confirm segment naming and clearing behavior.
  4. SessionMiddleware — see exactly where start() and save() happen.
  5. The constructor/options handling in Session — configuration keys vary by release, so verify against the installed version rather than copying old blog posts.

If you come from Laravel, the mental mapping is:

Laravel odan/session
session('key') $session->get('key')
session(['key' => $value]) $session->set('key', $value)
session()->flash('key', $value) $session->getFlash()->add('key', $value)
session()->forget('key') $session->remove('key')
session()->flush() $session->clear()
session()->regenerate() $session->regenerateId()

There is no equivalent of Laravel’s old() input flashing or validation error bag; you build those on top of FlashInterface.

Implementation Patterns

1. Container registration and PSR-15 middleware

Register one shared session instance in your container and let the middleware own the lifecycle:

use Odan\Session\Session;
use Odan\Session\SessionInterface;
use Odan\Session\SessionMiddleware;

$container->set(SessionInterface::class, function () {
    return new Session([
        'name' => 'my_app_session',
        'cache_limiter' => '',
        'cookie_httponly' => true,
        'cookie_secure' => true,
        'cookie_samesite' => 'Lax',
        'use_strict_mode' => true,
    ]);
});

$app->add(SessionMiddleware::class);

Then inject SessionInterface into controllers, actions, or services:

final class CreateItemAction
{
    public function __construct(private SessionInterface $session)
    {
    }

    public function __invoke($request, $response)
    {
        $this->session->getFlash()->add('success', 'Saved.');

        return $response->withHeader('Location', '/items')->withStatus(302);
    }
}

The important architectural rule is: let the middleware call start() and save(). Do not sprinkle session_start() and session_write_close() through application code unless you have a specific reason.

2. Flash messages as a request-boundary contract

Treat flash data as “write in this request, read in the next request.” A typical CRUD action:

$flash = $session->getFlash();

if ($error) {
    $flash->add('error', 'Could not save the item.');
} else {
    $flash->add('success', 'Item saved.');
}

return $response->withHeader('Location', '/items')->withStatus(302);

In the next request’s view layer:

$flash = $session->getFlash();

foreach (['success', 'error'] as $type) {
    foreach ($flash->get($type) as $message) {
        $messages[$type][] = $message;
    }
}

Keep flash values scalar or simple arrays. Avoid storing whole entities, especially if they contain closures or unserializable dependencies. If you need structured flash payloads, store plain arrays:

$flash->add('errors', [
    'email' => 'Email is already registered.',
]);

3. Segments for namespaced state

Use segments instead of inventing prefixed keys such as auth_user_id, cart_items, wizard_step. This keeps clear boundaries and makes bulk clearing safer.

$auth = $session->getSegment('auth');
$auth->set('user_id', $user->getId());
$auth->set('roles', $user->getRoles());

$cart = $session->getSegment('cart');
$cart->set('items', $items);

$wizard = $session->getSegment('checkout_wizard');
$wizard->set('step', 2);

Reading:

$userId = $session->getSegment('auth')->get('user_id');
$items = $session->getSegment('cart')->get('items', []);

Clearing one domain:

$session->getSegment('cart')->clear();

This is much safer than $session->clear(), which wipes the whole session including auth and flash data.

4. Authentication and session fixation

On successful login, migrate the session ID and then store identity data:

$session->regenerateId();
$session->getSegment('auth')->set('user_id', $user->getId());

On logout, clear the auth segment and destroy the session:

$session->getSegment('auth')->clear();
$session->destroy();

Also expire the session cookie if your application relies on the cookie being removed immediately:

if (ini_get('session.use_cookies')) {
    $params = session_get_cookie_params();
    setcookie(
        session_name(),
        '',
        time() - 42000,
        $params['path'],
        $params['domain'],
        $params['secure'],
        $params['httponly']
    );
}

Regenerating on login is not optional; it is the main defense against session fixation.

5. CSRF protection built on the session

The package does not provide CSRF tokens itself, but the session is the correct place to store them:

$token = $session->get('csrf_token');

if ($token === null) {
    $token = bin2hex(random_bytes(32));
    $session->set('csrf_token', $token);
}

Render it into forms:

<input type="hidden" name="_token" value="<?= htmlspecialchars($token, ENT_QUOTES, 'UTF-8') ?>">

Verify with a timing-safe comparison:

$submitted = $_POST['_token'] ?? '';

if (!hash_equals((string) $session->get('csrf_token'), (string) $submitted)) {
    throw new RuntimeException('Invalid CSRF token');
}

For multiple tabs or long-lived forms, consider storing a small array of valid tokens, but do not disable the check for convenience.

6. Configurable handlers for production

For a single-server application, PHP’s default file handler is fine. For multi-server deployments, configure Redis or a database handler before the session starts:

ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://127.0.0.1:6379');

Or pass equivalent options if your installed version supports them through the Session constructor. The critical point is ordering: handler and cookie settings must be applied before session_start() / $session->start().

If you use Redis, keep session.gc_maxlifetime and the Redis TTL aligned. Otherwise sessions may expire in Redis before PHP believes they should, or vice versa.

7. Testing strategy

Do not unit-test through native PHP sessions unless you must. Mock the interfaces instead:

$session = $this->createMock(SessionInterface::class);
$session->method('get')->with('user_id')->willReturn(42);

$segment = $this->createMock(SegmentInterface::class);
$segment->method('get')->with('user_id')->willReturn(42);

$session->method('getSegment')->with('auth')->willReturn($segment);

For integration tests that must exercise native behavior, use an isolated process and set a deterministic session ID:

/**
 * @runInSeparateProcess
 */
public function testFlashMessageLifecycle(): void
{
    session_id('test-session-id');
    // start session, add flash, save, start again, assert message exists
}

A useful pattern is to create a simple in-memory SessionInterface test double for functional tests so your application code never depends directly on $_SESSION.

Gotchas and Tips

  • Headers must not be sent before the session starts. Native sessions need to send a cookie. The PSR-15 middleware avoids most problems, but manual start() calls in controllers are a common source of “headers already sent” warnings.

  • Do not start a session twice. If you write custom middleware or bootstrap code, guard with session_status() or rely on the package’s start() behavior. Calling session_start() when a session is already active produces notices and can reset expectations.

  • Flash data is for the next request, not the current one. If you add a flash message and immediately try to read it in the same request, you may see old data or nothing. Redirect after writing flash data, then read it on the next request.

  • Flash messages are not automatically HTML-escaped. The package stores whatever you give it. Escape at render time with htmlspecialchars() unless your template engine does it for you.

  • clear() is broader than you think. $session->clear() removes everything, including auth, CSRF, and flash data. Prefer segment-level clearing or remove() for targeted cleanup.

  • Segments reduce key collisions but do not eliminate them. Choose stable, application-specific segment names. Avoid segment names that another package or legacy code might use.

  • Session locking can serialize requests. The default file handler locks the session file for the duration of the request. Long-running requests can block other requests from the same user. If a request only reads session data, call $session->save() early to release the lock, but remember that further writes after save() may not persist unless the session is reopened.

  • Middleware save() may be skipped on exceptions. If the handler throws before the middleware reaches its save call, writes may not persist. If this matters, wrap the handler in try/finally in your own middleware or ensure the package middleware saves in a finally block.

  • destroy() does not always remove the browser cookie. Native session_destroy() deletes server-side data, but the cookie may remain. Expire the cookie explicitly on logout if immediate removal matters.

  • Cookie settings must be correct before startup. cookie_secure, cookie_httponly, cookie_samesite, cookie_path, and cookie_domain cannot be meaningfully changed after the session is active. Set them in bootstrap or constructor options.

  • use_strict_mode can surprise you. It rejects uninitialized session IDs. That is good for security, but it can break tests or custom flows that pass a session ID manually. Set it intentionally and verify your test setup.

  • Do not store resources, closures, or large object graphs. Session handlers serialize data. A PDO connection, stream, or closure will fail or produce broken sessions. Store identifiers and rehydrate from the database on the next request.

  • Session name collisions are real. If two applications share a cookie domain/path and use the same session name, they will overwrite each other. Use a unique name per application.

  • CLI is not a browser. Sessions depend on cookies and headers. Running session code from CLI will not persist an ID between commands unless you explicitly set session_id(). That is useful for tests but misleading for production debugging.

  • Check the installed version’s constructor and options. This package has evolved; older examples may use different class names or option arrays. Read the SessionInterface, FlashInterface, and SegmentInterface in vendor/odan/session/src for the exact API you have.

  • Extension point: your own middleware. If you need request attributes, tenant-aware session names, or custom save timing, wrap SessionMiddleware or write a small PSR-15 middleware that starts the session, attaches it to the request, and saves it in finally. Keep the package’s SessionInterface as the boundary so application code stays testable.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager