Session and Flash messages
Table of contents
Loading...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 itselfSessionInterface
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);
},
// ...
];
Example settings:
File: config/defaults.php
$settings['session'] = [
'name' => 'session-name',
// 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 we added to the
container definitions earlier, 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">×</span>
</dialog>
<?php
}
} ?>
</aside>
The css, icons and the associated JavaScript code can be found in assets/general/page-component/flash-message
.