Search code examples
phpslim

How to Pass Parameters to Middleware in Slim 2?


I'm trying to figure out how to pass parameters to a middleware in slim 2. I have a specific use case where I need to customize the behavior of a middleware based on certain parameters.

Here's an example of the middleware code:

$app->post('/test',function () use ($app) { })->setMiddleware([new TestMiddleware(), 'call']);

I cannot add parameters in the object as no constructor can be used in the middleware. I tried to add the parameters in the request inside the router but the middleware is called first.


Solution

  • If you take a look at the documentation of slime v2 Middleware for routes

    You'll find some helpful examples, like this one:

    <?php
    $authenticateForRole = function ( $role = 'member' ) { -> defining an anonymous function
        return function () use ( $role ) { -> returns a callback
            ...code...
        };
    };
    
    
                                 | Uses here the custom middleware with the given param 
                                 v
    $app->get('/foo', $authenticateForRole('admin'), function () {
        //Display admin control panel
    });
    

    Now, for the piece of code you've provided, it makes use of the class TestMiddleware and the function call

    $app->post('/test',function () use ($app) { })->setMiddleware([new TestMiddleware(), 'call']);
    

    So if we take the example provided above as guidance your code should look like this:

    TestMiddleware.php

    <?php
    class TestMiddleware {
        public static function call($msg) {
            return function () use($msg) {
            ...code...
                echo $msg;
                echo "<br>";
            ...code...
            };
        }
    }
    

    Index.php

    $app->get('/test',TestMiddleware::call("Middleware Message"), function () {
        echo "Route Message";
    });
    

    This will be displayed in the browser

    Middlewared Message
    Route Message
    

    Last, if you want to use your call function like this:

    TestMiddleware::call("Middleware Message")
    

    you'll need to use the static keyword, otherwise you've to instantiate the class and pass it to the route:

    $middlware = new TestMiddleware();
    
    $app->get('/test',$middlware->call("Middleware Message"), function () {
        echo "Route Message";
    });
    

    Edit:

    Okay, I spent some time modifying the code to meet the requirements and I've found multiple solutions to do it, however the most optimal and best in my opinion is this one:

    // CustomMiddleware.php
    class CustomMiddleware extends \Slim\Middleware{
    
        private $state;
        private $msg;
    
        // Constructor function
        public function __construct($msg,$state) {
            $this->state = $state;
            $this->msg = $msg;
        }
    
        // Invoke function
        public function __invoke()
        {
            $this->app = \Slim\Slim::getInstance();
            $this->call();
        }
    
        // Call function
        public function call() {
            // if the variable state is false it returns an error 404 
            if(!$this->state) $this->app->halt(404,"Not Found");
    
            // else returns a message defined by the variable $this->msg
            else{
                $this->app->response()->write($this->msg);
                echo "<br>";
            }
    
            if ($this->next) $this->next->call();
        }
    }
    
    // Setting the parameters of the middleware
    $middCallback = function () {
        $msg = "Middleware Message";
        $state = true;
    
        $middleware = new CustomMiddleware($msg,$state);
        $middleware(); // calling the __invoke
    };
    
    // Defining the route and including the middleware + callback
    $app->get('/test', $middCallback , function () { 
        echo "Route Message";
    });
    

    I'll give you a brief explanation.

    Here, CustomMiddleware inherits properties and methods from the class Middleware

    class CustomMiddleware extends \Slim\Middleware{
    

    The constructor is used to retrieve parameters $state and $msg.

        private $state;
        private $msg;
    
        // Constructor function
        public function __construct($msg,$state) {
            $this->state = $state;
            $this->msg = $msg;
        }
    

    You may ask why we're not getting the parameters through the call() function? Well, If you take a look at the source code you will find out that call() is an abstract function that doesn't accept parameters because it belongs to the Middleware class, meaning CustomMiddleware is just implementing it

    I had trouble setting the middleware parameters, so I had to use an invoke function.

    This is how normaly you will set a middleware in Slim:

    1st option

      $app->add(middleware) // It works, however it is set globally (for all routes).
    

    2nd option

    $app->get('/test',function () { 
        echo "Route Message";
    })->add(middleware); // It doesn't work (I guess it's because of the v2 of Slim).
    

    3rd

    $app->get('/test',function () { 
        echo "Route Message";
    })->setMiddleware(middleware); // It works, however I can't set the parameters.
    
     
    

    Now, the way I did it is by invoking the instance of the object CustomMiddleware as a function using $middleware(), which activates the invoke method. Within the invoke method you can trigger the execution of another function, in this case the call function, avoiding like this calling it manually

    What I wanted to avoid:

    $middleware = new CustomMiddleware($msg,$state);
    $middleware->call();
    

    What I expected:

    $middleware = new CustomMiddleware($msg,$state);
    $middleware();
    

    The Invoke function:

    public function __invoke()
    {
        $this->app = \Slim\Slim::getInstance();
        $this->call();
    }
    

    The call function will be modified at your convenience, so there's not much to say.

    public function call() {
        // if the state is false $this->state it will return an error 404
        if(!$this->state) $this->app->halt(404,"Not Found");
    
        // else will return the message $this->msg
        else{
            $this->app->response()->write($this->msg);
            echo "<br>";
        }
        // Calls for the next Middleware or route if available
        if ($this->next) $this->next->call(); -> 
    }
    

    The parameters are set here, with $msg and $state being the two variables, we pass the parameters to the instance of the object CustomMiddleware and the __construct will take care of it, last we have $middleware(), that will trigger the __invoke and the call function.

    $middCallback = function () {
        $msg = "Middleware Message";
        $state = true;
    
        $middleware = new CustomMiddleware($msg,$state);
        $middleware();
    };
    

    Again, you may ask why I'm using an anonymous function.

    Well, let's say we set the route like this:

    $msg = "Middleware Message";
    $state = true;
    $middleware = new CustomMiddleware($msg,$state);
    
    $app->get('/test', $middCallback , function () { 
        echo "Route Message";
    });
    

    If we pass the instance of the object as a argument to the route you will get an error in the browser:

    Fatal error: Uncaught InvalidArgumentException: All Route middleware must be callable

    Meaning that the instance $middleware passed to the route shouldn't be an object but a function

    Last, we define the route.

    We specify as second argument, the middleware defined earlier

    // Defining the route and including the middleware + callback
    $app->get('/test', $middCallback , function () { 
        echo "Route Message";
    });
    

    Doing this, you should have a working Middleware

    Hope it helps

    Cheers