Welcome to Abdul Malik Ikhsan's Blog

ZendExpressive: On Callable $next, and middleware_pipeline

Posted in Tutorial PHP, Zend Framework by samsonasik on September 20, 2015

In ZendExpressive, middleware_pipeline is a config that can be used to seed pre- and/or post-routing middleware. There are two keys inside it: pre_routing and post_routing.
Each of them has different purpose, pre_routing means the middleware registered under it will always be pipe()‘d before routing middleware, and for post_routing, they will be pipe()‘d afterwards.

If we create middlewares on them, we may need a call $next that on 3rd parameter in __invoke() method in its middlewares or middlewares that uses them as “next” middlewares. The code may be like this:

public function __invoke($request, $response, $next = null)
{
    // do something with request or response here
    return $next($request, $response);
}

$next will bring current $request and $response which may be modified before filled.

Ok, Let’s say we need an authorization middleware that pipe()‘d early, that means, we need a pre_routing:

class AuthorizationAction {

    public function __invoke($request, $response, $next = null)
    {
        // here, for example: check path and session exists
        // and handle with return response early or
        // continue to routing middleware
        return $next($request, $response);
    }

}

We can do something like this:

class AuthorizationAction {
  public function __construct($session) 
  { /** **/ }
    
  public function __invoke($request, $response, $next = null)
  {
    $path         = $request->getUri()->getPath();
    $sesionExists = $this->session->offsetExists('storage');

    // redirect to login page
    // when path = '/admin' and  
    // doesn't has session 
    if ($path === '/admin' && ! $sesionExists) {
        return new RedirectResponse('/auth');
    }

    if ($path === '/admin' && $sesionExists) {
        // check if session storage value !== 'admin'
        // then return early with status = 403
        $storageValue = $this->session->offsetGet('storage');
        if ($storageValue !== 'admin') {
            $response = $response->withStatus(403);
            $response->write('Forbidden');

            return $response;
        }
    }

    // if everything ok, continue
    return $next($request, $response);
  }
}

We then can register in our 'middleware_pipeline' config:

'middleware_pipeline' => [
    'pre_routing' => [
        [
            'middleware' => 'App\Action\AuthorizationAction',
        ],
    ],
    'post_routing' => [
        // ...
    ],
],

For example, we have routed middleware:

'routes' => [
    [
        'name' => 'funny',
        'path' => '/funny',
        'middleware' => App\Action\FunnyPageAction::class,
        'allowed_methods' => ['GET'],
    ],
    [
        'name' => 'admin',
        'path' => '/admin',
        'middleware' => App\Action\AdminPageAction::class,
        'allowed_methods' => ['GET'],
    ],
],

The AuthorizationAction middleware above will be pipe()‘d before middleware that registered in routes when specified middleware with its route match called.

So, how about post_routing ? We can call middleware registered in it, only if desired, that means, only if $next called in middleware that uses that. For example, we have a SugarAction middleware that change response header that will be placed in post_routing:

class SugarAction {

  public function __construct(RouterInterface $router)
  { $this->router = $router;}

  public function __invoke($request, $response, $next = null)
  {
     if (! $this->router->match($request)->isFailure()) {
         $response = $response->withHeader('X-Powered-By', 'JAVA');
     }
     return $next($request, $response);
  }
}

For note: As post_routing, because if the route is not match, the middleware above will be fall called, and withHeader() actually create new Response instance, we need to check whenever route match is not failure.

We then can register in our ‘middleware_pipeline’ config:

'middleware_pipeline' => [
    'pre_routing' => [
        [
            'middleware' => 'App\Action\AuthorizationAction',
        ],
    ],
    'post_routing' => [
        [
            'middleware' => 'App\Action\SugarAction',
        ],
    ],
],

and we have a /funny that uses that, so we can call $next:

class FunnyAction { 
  public function __invoke($request, $response, $next = null) {
    $response =  $response->write(
        $this->template->render('app::funny-page')
    );
    return $next($request, $response);
  }
}

If we check the header ( for example with Firebug or RestClient), and we can see the header ‘X-Powered-By’ changed. If we have a “not funny page”, and don’t want to modify header, then we can just not use $next.

6 Responses

Subscribe to comments with RSS.

  1. Rich said, on September 20, 2015 at 8:23 am

    How AuthorizationAction and SugarAction middleware get their dependency?

  2. Marcio Vinicius Silva said, on September 25, 2015 at 6:44 pm

    Hi, great tutorial!! But, how get 404 when using pipe ? By default… Thank you

  3. Daniel said, on July 6, 2016 at 12:38 am

    Hi, where do you create the session ?


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: