Flash in Twig in Slim 4

I didn’t get much luck reviving old threads on GitHub and SO, so I’ll try my luck here.

How can I access flash messages inside of Twig in Slim 4?

In Slim 3, I could do it like so:

$app->add(function ($request, $response, $next) {
    $this->view->offsetSet('flash', $this->flash);
    return $next($request, $response);
});

Then use say {{ flash.message('loginerror')[0] }} inside of a Twig template.

Trying to adapt it, I got this so far, but I’m clearly missing something:

$app->add(function ($request, $handler) {
    $response = $handler->handle($request);
    $this->get('view')->offsetSet('flash', $this->get('view'));
    return $response;
});

Any pointers?

The problem is that Slim\Flash\Messages relies on $_SESSION by default. But the session is not startet if you define the container definition. The session should be startet later in a middleware.

To solve this I define an array as default flash storage and change it after the session has been startet to $_SESSION as flash storage.

Example:

// config/container.php

use Slim\Views\Twig;
use Slim\Flash\Messages;

// ...

// flash
Messages::class => function () {
    // Array as default storage
    // Later the storage will be changed to $_SESSION
    $storage = [];

    return new Messages($storage);
},

Then register ‘flash’ as global twig variable:

Twig::class => function (ContainerInterface $container) {
    $config = $container->get('config')['twig'];

    $twig = Twig::create($config['path'], $config['options']);

    // Add extensions
    // ...

    // Add global variables
    $environment = $twig->getEnvironment();
    $environment->addGlobal('flash', $container->get(Messages::class));

    return $twig;
},

Then add this session middleware to start the session and change the flash storage:

<?php

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Flash\Messages;

class SessionStartMiddleware implements MiddlewareInterface
{
    /**
     * @var Messages
     */
    private $flash;

    public function __construct(Messages $flash)
    {
        $this->flash = $flash;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // Check that the session hasn't already been started
        if (session_status() !== PHP_SESSION_ACTIVE && !headers_sent()) {
            session_start();
        }

        // Change the storage
        $this->flash->__construct($_SESSION);

        return $handler->handle($request);
    }
}
use App\Middleware\SessionStartMiddleware;

//...
$app->add(SessionStartMiddleware::class);

Then add a message:

$this->flash->addMessage('success', 'You are now logged in.');

To show all flash messages in twig:

<div class="ui container flash">
    {% for name, messages in flash.getMessages() %}
        <div class="ui {{ name }} message">
            <div class="header">
                {% for message in messages %}
                    {{ message }}
                {% endfor %}
            </div>
        </div>
    {% endfor %}
</div>
1 Like

I don’t understand the first two code samples… where do you put those? What do they do? (I don’t have a config/container.php and I’m not familiar with the notation.)

The first two samples are the PHI-DI container definitions. Example

My app is much simpler than that.

I’m starting the session early with session_start();, and setting up Twig this way:

$container->set('view', function (\Psr\Container\ContainerInterface $container) {
    $view = new \Slim\Views\Twig(
        'app/templates',
        [
            'cache' => false,
            'debug' => true,
        ]
    );

    /**
     * TODO(): do i still need this in Slim 4??
     */
    // $router = $container->get('router');
    // $uri = \Slim\Http\Uri::createFromEnvironment(new \Slim\Http\Environment($_SERVER));
    // $view->addExtension(new \Slim\Views\TwigExtension($router, $uri));

    return $view;
});

Any advice?

It looks you are using the old version (2.x) of Slim-View. This will not work for Slim 4+.
You should upgrade to Slim-View v3.x.

You’re right, I’m using slim/twig-view 2.5.1, that’s because I’m still using Twig 1.x; slim/twig-view 3 works with Twig 3 (Planning to upgrade Twig itself, and a bunch of others things, once done upgrading Slim.)

However, I don’t believe that’s the source problem, seeing as all my views render fine and I’m able to access Flash variables through PHP, e.g.:

$app->get('/tests/flash/php/', function ($request, $response, $args) {
    $this->get('flash')->addMessage(
        'hello',
        "is it me you're looking for? (PHP)"
    );
    return $response->withStatus(302)->withHeader(
        'Location',
        '/tests/flash/php/get/'
    );
});


$app->get('/tests/flash/php/get/', function ($request, $response, $args) {
    $messages = $this->get('flash')->getMessages();
    print_r($messages);

    // Get the first message from a specific key
    $test = $this->get('flash')->getFirstMessage('hello');
    print_r($test);

    return $response;
});

…works fine, visiting the 1st route will redirect to the 2nd, where the Flash value will be shown as expected.

Currently, I have:

$app->add(function ($request, $handler) {
    $response = $handler->handle($request);
    $this->get('view')->offsetSet('flash', $this->get('view'));
    return $response;
});

offsetSet is the Pimple (Slim 3) of doing things. What’s the equivalent in Slim 4?

EDIT: and $_SESSION contains the expect flash messages… so the challenge really is in making {{ flash.getMessages }} / {{ flash.message('x') }} available to Twig.

I think you just solved an issue I was having with flash messages not showing occasionally.

Thanks

1 Like

offsetSet is the Pimple (Slim 3) of doing things. What’s the equivalent in Slim 4?

Pimple has been replaced by a PSR-11 container interface, e.g. PHP-DI. So the answer depends on the container implementation. Overwriting an container entry afterwards could cause some strange bugs, because other instances could still keep the old (flash) reference.

You could register a global Twig variable, e.g. flash.

Pseudo example, for you (not tested)

$flash = $this->get('flash');
$environment = $this->get('view')->getEnvironment();
$environment->addGlobal('flash', $flash);

In Twig

{{ flash.getMessage('error') }}

Thank you, Dan, it works.

1 Like