Dispatcher Filters

    There are several reasons to want a piece of code to be run before anycontroller code is executed or right before the response is sent to the client,such as response caching, header tuning, special authentication or just toprovide access to a mission-critical API response in lesser time than a completerequest dispatching cycle would take.

    CakePHP provides a clean interface for attaching filters to the dispatchcycle. It is similar to a middleware layer, but re-uses the existing eventsubsystem used in other parts of CakePHP. Since they do not work exactlylike traditional middleware, we refer to them as Dispatcher Filters.

    CakePHP comes with several dispatcher filters built-in. They handle commonfeatures that all applications are likely to need. The built-in filters are:

    • checks whether the request is referring to a themeor plugin asset file, such as a CSS, JavaScript or image file stored in either aplugin’s webroot folder or the corresponding one for a Theme. It will serve thefile accordingly if found, stopping the rest of the dispatching cycle:
    • RoutingFilter applies application routing rules to the request URL.Populates $request->getParam() with the results of routing.

    • ControllerFactory uses $request->getParam() to locate the controller thatwill handle the current request.

    • LocaleSelector enables automatic language switching from the Accept-Languageheader sent by the browser.

    1. DispatcherFactory::add('Routing');
    2. DispatcherFactory::add('ControllerFactory');
    3.  
    4. // Plugin syntax is also possible
    5.  
    6. // Use options to set priority
    7. DispatcherFactory::add('Asset', ['priority' => 1]);

    Dispatcher filters with higher priority (lower numbers) - will be executedfirst. Priority defaults to 10.

    While using the string name is convenient, you can also pass instances intoadd():

    When adding filters, you can control the order they are invoked in usingevent handler priorities. While filters can define a default priority using the$_priority property, you can set a specific priority when attaching thefilter:

    1. DispatcherFactory::add('Asset', ['priority' => 1]);
    2. DispatcherFactory::add(new AssetFilter(['priority' => 1]));

    The higher the priority the later this filter will be invoked.

    Conditionally Applying Filters

    If you don’t want to run a filter on every request, you can use conditions toonly apply it some of the time. You can apply conditions using the andwhen options. The for option lets you match on URL substrings, while thewhen option allows you to run a callable:

    The callable provided to when should return true when the filter should run.The callable can expect to get the current request and response as arguments.

    1. namespace App\Routing\Filter;
    2.  
    3. use Cake\Event\Event;
    4. use Cake\Routing\DispatcherFilter;
    5.  
    6. class TrackingCookieFilter extends DispatcherFilter
    7. {
    8.  
    9. {
    10. $request = $event->getData('request');
    11. $response = $event->getData('response');
    12. if (!$request->getCookie('landing_page')) {
    13. $response->cookie([
    14. 'name' => 'landing_page',
    15. 'value' => $request->here(),
    16. 'expire' => '+ 1 year',
    17. ]);
    18. }
    19. }
    20. }

    Save this file into src/Routing/Filter/TrackingCookieFilter.php. As you can see, like otherclasses in CakePHP, dispatcher filters have a few conventions:

    • Class names end in Filter.
    • Generally filters extend Cake\Routing\DispatcherFilter.
      DispatcherFilter exposes two methods that can be overridden in subclasses,they are beforeDispatch() and . These methods areexecuted before or after any controller is executed respectively. Both methodsreceive a Cake\Event\Event object containing the ServerRequest andResponse objects ( andCake\Http\Response instances) inside the $data property.

    While our filter was pretty simple, there are a few other interesting things wecan do in filter methods. By returning an Response object, you canshort-circuit the dispatch process and prevent the controller from being called.When returning a response, you should also remember to call$event->stopPropagation() so other filters are not called.

    Note

    When a beforeDispatch method returns a response, the controller, andafterDispatch event will not be invoked.

    Let’s now create another filter for altering response headers in any publicpage, in our case it would be anything served from the PagesController:

    This filter will send a expiration header to 1 day in the future forall responses produced by the pages controller. You could of course do the samein the controller, this is just an example of what could be done with filters.For instance, instead of altering the response, you could cache it using and serve the response from the beforeDispatch()callback.