PageRenderTime 40ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/en/development/dispatch-filters.rst

https://github.com/MontBlanc-Sucks/docs
ReStructuredText | 226 lines | 187 code | 39 blank | 0 comment | 0 complexity | e60b43ed1e217d9bccf784565c16ddab MD5 | raw file
  1. Dispatcher Filters
  2. ##################
  3. .. versionadded:: 2.2
  4. There are several reasons to want a piece of code to be run before any
  5. controller code is executed or right before the response is sent to the client,
  6. such as response caching, header tuning, special authentication or just to
  7. provide access to a mission-critical
  8. API response in lesser time than a complete
  9. request dispatching cycle would take.
  10. CakePHP provides for such cases a clean and extensible interface for attaching
  11. filters to this dispatching cycle, similar to a middleware layer thought to
  12. provide stackable services or routines for every request. We call them
  13. `Dispatcher Filters`
  14. Configuring Filters
  15. ===================
  16. Filters are usually configured in the ``bootstrap.php`` file, but you could easily
  17. load them from any other configuration file before the request is dispatched.
  18. Adding and removing filters is done through the `Configure` class, using the
  19. special key ``Dispatch.filters``. By default CakePHP comes with a couple filter
  20. classes already enabled for all requests, let's take a look at how they are
  21. added::
  22. <?php
  23. Configure::write('Dispatcher.filters', array(
  24. 'AssetDispatcher',
  25. 'CacheDispatcher'
  26. ));
  27. Each of those array values are class names that will be instantiated and added
  28. as listeners for the events generated at dispatcher level. The first one,
  29. ``AssetDispatcher`` is meant to check whether the request is referring to a theme
  30. or plugin asset file, such as a css, javascript or image stored on either a
  31. plugin's webroot folder or the corresponding one for a Theme. It will serve the
  32. file accordingly if found, stopping the rest of the dispatching cycle. The ``CacheDispatcher``
  33. filter, when ``Cache.check`` config variable is enabled, will check if the
  34. response was already cached in the file system for a similar request and serve
  35. the cached code immediately.
  36. As you can see, both provided filters have the responsibility of stopping any
  37. further code and send the response right away to the client. But filters are not
  38. limited to this role, as we will show shortly in this section.
  39. You can add your own class names to the list of filters, and they will get
  40. executed in the order they were defined. There is also an alternative way for
  41. attaching filters that do not involve the special ``DispatcherFilter`` classes::
  42. <?php
  43. Configure::write('Dispatcher.filters', array(
  44. 'my-filter' => array('callable' => array($classInstance, 'methodName'), 'on' => 'after')
  45. ));
  46. As shown above, you can pass any valid PHP `callback <http://php.net/callback>`_
  47. type, as you may remember, a `callback` is anything that PHP can execute with
  48. ``call_user_func``. We do make a little exception, if a string is provided it will
  49. be treated as a class name, not as a possible function name. This of course
  50. gives the ability to PHP 5.3 users to attach anonymous functions as filters::
  51. <?php
  52. Configure::write('Dispatcher.filters', array(
  53. 'my-filter' => array('callable' => function($event) {...}, 'on' => 'before'),
  54. //more filters here
  55. ));
  56. The ``on`` key only takes ``before`` and ``after`` as valid values, and evidently
  57. means whether the filter should run before or after any controller code is
  58. executed. Additionally to defining filters with the ``callable`` key, you also
  59. get the chance to define a priority for your filters, if none is specified then
  60. a default of ``10`` is selected for you::
  61. <?php
  62. Configure::write('Dispatcher.filters', array(
  63. 'my-filter' => array(
  64. 'callable' => function($event) {...},
  65. 'on' => 'before',
  66. 'priority' => 5
  67. ),
  68. 'other-filter' => array(
  69. 'callable' => array($class, 'method'),
  70. 'on' => 'after',
  71. 'priority' => 1
  72. ),
  73. //more filters here
  74. ));
  75. Obviously, when defining priorities the order in which filters are declared does
  76. not matter but for those having the same. When defining filters as class names
  77. there is no option to define priority in-line, we will get into that soon.
  78. Finally, CakePHP's plugin notation can be used to define filters located in
  79. plugins::
  80. <?php
  81. Configure::write('Dispatcher.filters', array(
  82. 'MyPlugin.MyFilter',
  83. ));
  84. Feel free to remove the default attached filters if you choose to use a more
  85. advanced/faster way of serving theme and plugin assets or if you do not wish to
  86. use built-in full page caching, or just implement your own.
  87. Filter Classes
  88. ==============
  89. Dispatcher filters, when defined as class names in configuration, should extend
  90. the class ``DispatcherFilter`` provided in the `Routing` CakePHP's directory.
  91. Let's create a simple filter to respond to a specific url with a 'Hello World'
  92. text::
  93. <?php
  94. App::uses('DispatcherFilter', 'Routing');
  95. class HelloWorldFilter extends DispatcherFilter {
  96. public $priority = 9;
  97. public function beforeDispatch($event) {
  98. $request = $event->data['request'];
  99. $response = $event->data['response'];
  100. if ($request->url === 'hello-world') {
  101. $response->body('Hello World');
  102. $event->stopPropagation();
  103. return $response;
  104. }
  105. }
  106. }
  107. This class should be saved in a file in ``app/Routing/Filter/HelloWorldFilter.php``
  108. and configured in the bootstrap file according to how it was explained in the
  109. previous section. There is plenty to explain here, let's begin with the
  110. ``$priority`` value.
  111. As mentioned before, when using filter classes you can only define the order in
  112. which they are run using the ``$priority`` property in the class, default value is
  113. 10 if the property is declared, this means that it will get executed _after_ the
  114. Router class has parsed the request. We do not want this to happen in our
  115. previous example, because most probably you do not have any controller set up
  116. for answering to that url, hence we chose 9 as our priority.
  117. ``DispatcherFilter`` exposes two methods that can be overridden in subclasses,
  118. they are ``beforeDispatch`` and ``afterDispatch``, and are executed before or after
  119. any controller is executed respectively. Both methods receive a :php:class:`CakeEvent`
  120. object containing the ``request`` and ``response`` objects
  121. (:php:class:`CakeRequest` and :php:class:`CakeResponse` instances) along with an
  122. ``additionalParams`` array inside the ``data`` property. The latter contains
  123. information used for internal dispatching when calling ``requestAction``.
  124. In our example we conditionally returned the ``$response`` object as a result,
  125. this will tell the Dispatcher to not instantiate any controller and return such
  126. object as response immediately to the client. We also added
  127. ``$event->stopPropagation()`` to prevent other filters from being executed after
  128. this one.
  129. Let's now create another filter for altering response headers in any public
  130. page, in our case it would be anything served from the ``PagesController``::
  131. <?php
  132. App::uses('DispatcherFilter', 'Routing');
  133. class HttpCacheFilter extends DispatcherFilter {
  134. public function afterDispatch($event) {
  135. $request = $event->data['request'];
  136. $response = $event->data['response'];
  137. if ($request->params['controller'] !== 'pages') {
  138. return;
  139. }
  140. if ($response->statusCode() === 200) {
  141. $response->sharable(true);
  142. $response->expires(strtotime('+1 day'));
  143. }
  144. }
  145. }
  146. This filter will send a expiration header to 1 day in the future for
  147. all responses produced by the pages controller. You could of course do the same
  148. in the controller, this is just an example of what could be done with filters.
  149. For instance, instead of altering the response you could cache it using the
  150. :php:class:`Cache` class and serve the response from the ``beforeDispatch``
  151. callback.
  152. Inline Filters
  153. ==============
  154. Our last example will use an anonymous function (only available on PHP 5.3+) to
  155. serve a list of posts in json format, we encourage you to do so using
  156. controllers and the :php:class:`JsonView` class, but let's imagine you need to save a
  157. few milliseconds for this mission-critical API endpoint::
  158. <?php
  159. $postsList = function($event) {
  160. if ($event->data['request']->url !== 'posts/recent.json') {
  161. return;
  162. }
  163. App::uses('ClassRegistry', Utility);
  164. $postModel = ClassRegistry::init('Post');
  165. $event->data['response']->body(json_encode($postModel->find('recent')));
  166. $event->stopPropagation();
  167. return $event->data['response'];
  168. };
  169. Configure::write('Dispatcher.filters', array(
  170. 'AssetDispatcher',
  171. 'CacheDispatcher',
  172. 'recent-posts' => array(
  173. 'callable' => $postsList,
  174. 'priority' => 9,
  175. 'on'=> 'before'
  176. )
  177. ));
  178. For obvious reasons this has the potential of making your app very difficult
  179. to maintain. Filters are a extremely powerful tool when used wisely, adding
  180. response handlers for each url in your app is not a good use for it. But if you
  181. got a valid reason to do so, then you have a clean solution at hand. Keep in
  182. mind that not everything need to be a filter, `Controllers` and `Components` are
  183. usually a more accurate choice for adding any request handling code to your app.
  184. .. meta::
  185. :title lang=en: Dispatcher Filters
  186. :description lang=en: Dispatcher filters are a middleware layer for CakePHP allowing to alter the request or response before it is sent
  187. :keywords lang=en: middleware, filters, dispatcher, request, response, rack, application stack, events, beforeDispatch, afterDispatch, router