Skip to content

Session and Flash messages

samuelgfeller edited this page Apr 2, 2024 · 10 revisions

Introduction

Sessions provide a way to retain user information throughout a user's interaction with a website.

Unlike stateless protocols such as HTTP, which doesn't inherently retain information between requests, sessions offer a way to associate data with a specific user across multiple page views.

For the slim-example-project, I've chosen the lightweight session handler odan/session which works well with PSR-7 HTTP messages and PSR-15 middlewares.

composer require odan/session

Setup

The library provides two interfaces to define session handling:

  • SessionManagerInterface contains the methods to handle the session itself
  • SessionInterface the methods to handle the session data

Both are added to the DI container in the config/container.php file to resolve to the PhpSession class.

use Odan\Session\SessionInterface;
use Odan\Session\SessionManagerInterface;
use Odan\Session\PhpSession;

return [
    // ...
    SessionManagerInterface::class => function (ContainerInterface $container) {
        return $container->get(SessionInterface::class);
    },
    SessionInterface::class => function (ContainerInterface $container) {
        $options = $container->get('settings')['session'];
        return new PhpSession($options);
    },
    // ...
];

These are the settings used in the slim-example-project:

File: config/defaults.php

$settings['session'] = [
    'name' => 'slim-example-project',
    // 5h session lifetime
    'lifetime' => 18000, // Time in seconds
];

For a full list of options see the documentation.

Session start middleware

The library provides the SessionsStartMiddleware which should be added to the middleware stack right before the routing middleware after every middleware that might depend on session.
With the reversed stack order (LIFO) this ensures that the session is started before middlewares that need it are invoked.

File: config/middleware.php

return function (App $app) {

    // ... session available upwards
    $app->add(Odan\Session\Middleware\SessionStartMiddleware::class);
    $app->addRoutingMiddleware();
    // ...
}

Usage

To access the session in an Action, the SessionInterface that was added to the container before can be injected in the constructor.

namespace App\Application\Action\Example;

use Odan\Session\SessionInterface;

final readonly class ExampleAction
{
    public function __construct(
        private SessionInterface $session,
    ) {
    }
}

The SessionInterface defines methods to store and retrieve the session data.

Store attribute

// Set single attribute
$this->session->set('key_name', 'value');
// Set multiple attributes
$this->session->setMultiple(['key_name' => 'value', 'key_name2' => 'value2']);

Get stored attribute

// Get stored value by key name
$keyData = $this->session->get('key_name');
// Get all attributes
$allKeys = $this->session->all();

Check if attribute exists

$this->session->has('key_name');

Delete attribute

// Delete single attribute
$this->session->delete('key_name');
// Remove all values
$this->session->clear();

Modify session itself

To modify the session itself, the SessionManagerInterface has to be used.

namespace App\Application\Action\Example;

use Odan\Session\SessionManagerInterface;

final readonly class ExampleAction
{
    public function __construct(
        private SessionManagerInterface $sessionManager,
    ) {
    }
}

This interface contains the following methods among others:

// Start session
$this->sessionManager->start();
// Regenerate session id
$this->sessionManager->regenerateId();
// Destroy session
$this->sessionManager->destroy();

Testing

For testing, instead of using the PhpSession class, the container key SessionInterface can be set to return a MemorySession instance.

File: tests/Traits/AppTestTrait.php

protected function setUp() {
    // Start slim app
    $this->app = require __DIR__ . '/../../config/bootstrap.php';
    $container = $this->app->getContainer();
    // Set session to MemorySession
    $container->set(SessionInterface::class, new MemorySession());
    // ...
}

Now the session data can be accessed and handled in the test functions.

public function testFunction(): void
{
    // Set user id
    $this->container->get(SessionInterface::class)->set('user_id', 1));
    // Get user id
    $this->container->get(SessionInterface::class)->get('user_id'));
    // ...
}

Flash messages

Flash messages are typically used to display temporary messages to users after a specific action has been performed.
Flash messages created by the server are displayed on the next page the user visits.

The SessionInterface provides a getFlash() method that returns the flash messages handler.

$flash = $this->session->getFlash();

// Add flash message
$flash->add('success', 'Your message here');
// Get flash message
$flash->get('error');
// Get all flash messages
$flash->all();

Display flash messages

On each page load, the template renderer loads the templates/layout.html.php file which fetches the flash message template that displays the flash messages on the page.

File: templates/layout/flash-messages.html.php

<?php
/** @var \Odan\Session\FlashInterface $flash */
?>

<aside id="flash-container">
    <?php
    // Display flash messages if there are any
    foreach ($flash?->all() ?? [] as $flashCategory => $flashMessages) {
        foreach ($flashMessages as $msg) { ?>
            <dialog class="flash <?= $flashCategory /* success, error, info, warning */ ?>">
                <figure class="flash-fig" draggable="false">
                    <?php
                    // If it was possible to set the base path for css, the `content:` tag could be used?>
                    <img class="<?= $flashCategory === "success" ? "open" : '' ?>" draggable="false"
                         src="assets/general/page-component/flash-message/img/flash-checkmark.svg" alt="success">
                    <img class="<?= $flashCategory === "error" ? "open" : '' ?>" draggable="false"
                         src="assets/general/page-component/flash-message/img/flash-error.svg" alt="error">
                    <img class="<?= $flashCategory === "info" ? "open" : '' ?>" draggable="false"
                         src="assets/general/page-component/flash-message/img/flash-info.svg" alt="info">
                    <img class="<?= $flashCategory === "warning" ? "open" : '' ?>" draggable="false"
                         src="assets/general/page-component/flash-message/img/flash-warning.svg" alt="warning">
                </figure>
                <div class="flash-message"><h3><?= html(ucfirst($flashCategory)) /* Serves as default, is overwritten in  */
            ?> message</h3><p><?= // No line break between h3 and p
            /* Flash messages are hardcoded strings on the server, and html is used to format them,
                   so it should be interpreted. This is the only exception where html() for escaping is not used*/
            $msg ?></p></div>
                <span class="flash-close-btn">&times;</span>
            </dialog>
            <?php
        }
    } ?>
</aside>

The css, icons and the associated JavaScript code can be found in assets/general/page-component/flash-message.

Clone this wiki locally