-
-
Notifications
You must be signed in to change notification settings - Fork 3
Session and Flash messages
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
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.
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();
// ...
}
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.
// Set single attribute
$this->session->set('key_name', 'value');
// Set multiple attributes
$this->session->setMultiple(['key_name' => 'value', 'key_name2' => 'value2']);
// Get stored value by key name
$keyData = $this->session->get('key_name');
// Get all attributes
$allKeys = $this->session->all();
$this->session->has('key_name');
// Delete single attribute
$this->session->delete('key_name');
// Remove all values
$this->session->clear();
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();
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 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();
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
.
Slim app basics
- Composer
- Web Server config and Bootstrapping
- Dependency Injection
- Configuration
- Routing
- Middleware
- Architecture
- Single Responsibility Principle
- Action
- Domain
- Repository and Query Builder
Features
- Logging
- Validation
- Session and Flash
- Authentication
- Authorization
- Translations
- Mailing
- Console commands
- Database migrations
- Error handling
- Security
- API endpoint
- GitHub Actions
- Scrutinizer
- Coding standards fixer
- PHPStan static code analysis
Testing
Frontend
Other