Execute middleware after route

Hi,

I try to create a simply flash middleware that unset $_SESSION['flash'] after my page render.

Inside my index.php I have:

$app = AppFactory::create();
$app->add(FlashMiddleware::class);
$app->addErrorMiddleware(true, true, true);
$app->get('login', App\Controllers\LoginController::class.':index')->setName('login');

Inside my FlashMiddlware I have:

class FlashMiddleware extends Middleware
{
    public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): Response
    {
        // Get response
        $response = $handler->handle($request);

        // Unset flash SESSION 
        if(isset($_SESSION['flash']))
        {
            unset($_SESSION['flash']);
        }
        
        // Return response
        return $response;
    }
}

And inside my LoginController I have:

public function index(ServerRequestInterface $request, ResponseInterface $response, array $args)
    {
        $args['flash'] = $_SESSION['flash'];
        return $this->render($response, 'login.php', $args);
    }

But when I set my flash session with some message, and I redirect to my /login page, I can’t get the value of my $_SESSION['flash'], it’s already unset by my FlashMiddleware, so how I can tell to FlashMiddleware to execute after I get this value on my LoginController ?

Thanks

Hi @webcimes Instead of using a middleware to unset the value, you may try to add a Flash message class that deletes the value after it has been fetched from the session.

You may also try to use the slim/message package instead.

Hi @odan, thanks for your answer, with the “slim/message” it’s working well for my controllers and views.
But I have another problem, in some routes I call a middleware for specific route, for auth the user like this:

$app->get('/admin', App\Controllers\AdminController::class.':index')->setName('admin')->add(new AuthMiddleware(['ADMIN']));

In the AuthMiddleware.php I check if the user has the right roles, and if the roles are wrong for the user, I redirect the user to login page with a flash message like this :

class AuthMiddleware extends Middleware
{
    private $tabLevels;

    public function __construct(array $tabLevels = [])
    {
        $this->tabLevels = $tabLevels;
    }

    public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): Response
    {

        $response = $handler->handle($request);

        // My code for check roles


        $this->container->get('flash')->addMessage('errorMessage', $errorMessage);;
        return $response->withHeader('Location', '/login')->withStatus(302);
    }
}

and in my Middleware.php class:

class Middleware
{
    public $container;

    public function __construct(Container $container)
    {
        $this->container = $container;
    }
}

But I get an error “Call to a member function get() on null” for the file AuthMiddleware.php, but In the my Controllers class I have the same type of code (and extend to Controller for get the container) and it’s working well, so I don’t understand why I get this error ?

Thanks for your help

Within the application, the DI container should not be used directly. Instead the DI container should inject the dependencies for you when you declare it within a class constructor.

To solve this issue, make sure that you set up the DI container correctly for autowiring.
Then declare the Message class instead of the DI container within the constructor of the middleware.

Thanks for your help, I don’t understand everything, I have declare my DI container like this (I think autowiring is active by default):

<?php

use DI\Container;
use Slim\Flash\Messages;
use Slim\Factory\AppFactory;

// Create Container
$container = new Container();
AppFactory::setContainer($container);

// Set flash in Container
$container->set('flash', function()
{
    $storage = [];
    return new Messages($storage);
});

Then in my FlashMiddleware I have try two ways but I don’t get it.

First try:

<?php

namespace App\Middlewares;

use DI\Container;
use Slim\Psr7\Response;
use App\Middlewares\Middleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

/**
 * Flash Middleware
 */
class FlashMiddleware extends Middleware
{
    public $container;

    public function __construct(Container $container)
    {
        $this->container = $container;
    }
    
    public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): Response
    {
        // Change flash message storage
        $this->container->get('flash')->__construct($_SESSION);

        // Return response
        return $handler->handle($request);
    }
}

Second try:

<?php

namespace App\Middlewares;

use Slim\Psr7\Response;
use Slim\Flash\Messages;
use App\Middlewares\Middleware;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

/**
 * Flash Middleware
 */
class FlashMiddleware extends Middleware
{
    public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): Response
    {
        new Messages($_SESSION);

        // Return response
        return $handler->handle($request);
    }
}

I don’t know if it’s the good way to do this (first try or second try) ?

Thanks

I think neither the first nor the second is a good way to go because the DI container should not be injected. I would recommend declaring the Slim\Flash\Messages within the Middleware constructor instead.

Make sure the DI container can inject the Messages instance:

use Slim\Flash\Messages;
// ...

$container->set(Messages::class, function()
{
    $storage = [];
    return new Messages($storage);
});

The middleware constructor:

use Slim\Flash\Messages;
// ...

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

Thanks foŕ your help, I didn’t know that we could set the class directly with the container, it’s seems to work with controller but in my authMiddleware I get an error when I try to add a message

Just for be sure, the constructor is enough to call the flash class from the container ? I don’t need this ?

$this->flash = $container->get(Flash::class);

My custom Middlewares class:

<?php
namespace App\Middlewares;

use Slim\Flash\Messages;

class Middleware
{
    public $flash;
    public $test;

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

And in my authMiddleware class I get this error:

Call to a member function addMessage() on null

I have also try to put a simple variable “test” into the constructor and in my authMiddleware if I do an echo $this->test; I get nothing. So I think the extends not working / get constructor on middleware class ? Can you confirm ?

class AuthMiddleware extends Middleware
{
    public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): Response
    {
        $this->flash->addMessage('errorMessage', 'test');
        exit($this->test);
    }
}

Thanks

It seems like the $flash object is not being properly instantiated in your Middleware class. A typical middleware should also implement the Psr\Http\Server\MiddlewareInterface.

In order for Slim or the DI container to be able to create your Middleware objects correctly, you may use this syntax when you add a middleware:

use App\Middleware\MyMiddleware;

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

There are other things to consider when using this flash message package. You can find a complete article in my new Slim 4 eBook Vol. 2.

Ok thanks it’s working with normal class call, but I have just a last question, in my root I call my AuthMiddleware class for specific route, with specific parameters (array of level authorized).

So I can’t do something like this:

$app->get('/admin', App\Controllers\AdminController::class.':index')->setName('admin')->add(AuthMiddleware::class);

And I must do this for pass my parameters (array of level authorized) to my constructor:

$app->get('/admin', App\Controllers\AdminController::class.':index')->setName('admin')->add(new AuthMiddleware(['SUPERADMIN','ADMIN']));

But it create a problem to instancie Messages class with autowiring in authMiddleware, because I need to also pass the “Messages” parameter when I add the AuthMiddleware class to my route, because I use new AuthMiddleware(['SUPERADMIN','ADMIN']).

My AuthMiddleware constructor:

<?php

namespace App\Middlewares;

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

final class AuthMiddleware implements MiddlewareInterface
{
    private $tabLevels;
    private $flash;

// Problem here because I need to get my TabLevels, 
// so I also need to pass Messages parameters when I call this class in the route file.
    public function __construct(array $tabLevels = [], Messages $flash)
    {
        $this->tabLevels = $tabLevels;
        $this->flash = $flash;
    }


    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    // public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): Response
    {
.... my code

What is the best way to do it in this case?

Thanks for your time, I will look to your eBook

To solve this issue, you may consider the following approaches:

  1. Creating different AuthMiddleware classes: Instead of having a single AuthMiddleware class, you can create separate classes for different authorization levels.
  2. Using a Factory class: You can create a factory class that creates and returns an instance of the AuthMiddleware class with the required authorization level. This way, you need tho fetch the Container instance from the App instance within the routes, which is not a good practice.
  3. Checking the route within the AuthMiddleware: Within the AuthMiddleware class, you can access the current route from the $request object and determine the required authorization level based on the route name. This way, you don’t need to create different middleware classes or a factory class, and can determine the authorization level dynamically.

Ok it’s make sense, I will choose the option (3) I think it’s the better way to do this.

Thanks for your help.