PageRenderTime 56ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/en/development/dispatch-filters.rst

https://github.com/hiromi2424/docs
ReStructuredText | 245 lines | 199 code | 46 blank | 0 comment | 0 complexity | 22315c1adab1474bd8586bdf1a41d13c 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 ``Dispatcher.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. Configure::write('Dispatcher.filters', array(
  23. 'AssetDispatcher',
  24. 'CacheDispatcher'
  25. ));
  26. Each of those array values are class names that will be instantiated and added
  27. as listeners for the events generated at dispatcher level. The first one,
  28. ``AssetDispatcher`` is meant to check whether the request is referring to a theme
  29. or plugin asset file, such as a CSS, JavaScript or image stored on either a
  30. plugin's webroot folder or the corresponding one for a Theme. It will serve the
  31. file accordingly if found, stopping the rest of the dispatching cycle. The ``CacheDispatcher``
  32. filter, when ``Cache.check`` config variable is enabled, will check if the
  33. response was already cached in the file system for a similar request and serve
  34. the cached code immediately.
  35. As you can see, both provided filters have the responsibility of stopping any
  36. further code and send the response right away to the client. But filters are not
  37. limited to this role, as we will show shortly in this section.
  38. You can add your own class names to the list of filters, and they will get
  39. executed in the order they were defined. There is also an alternative way for
  40. attaching filters that do not involve the special ``DispatcherFilter`` classes::
  41. Configure::write('Dispatcher.filters', array(
  42. 'my-filter' => array(
  43. 'callable' => array($classInstance, 'methodName'),
  44. 'on' => 'after'
  45. )
  46. ));
  47. As shown above, you can pass any valid PHP `callback <http://php.net/callback>`_
  48. type, as you may remember, a `callback` is anything that PHP can execute with
  49. ``call_user_func``. We do make a little exception, if a string is provided it will
  50. be treated as a class name, not as a possible function name. This of course
  51. gives the ability to PHP 5.3 users to attach anonymous functions as filters::
  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. As all filters will have default priority ``10``, should you want to run a filter before
  62. any other in the list, select lower priority numbers as needed::
  63. Configure::write('Dispatcher.filters', array(
  64. 'my-filter' => array(
  65. 'callable' => function($event) {...},
  66. 'on' => 'before',
  67. 'priority' => 5
  68. ),
  69. 'other-filter' => array(
  70. 'callable' => array($class, 'method'),
  71. 'on' => 'after',
  72. 'priority' => 1
  73. ),
  74. //more filters here
  75. ));
  76. Obviously, when defining priorities the order in which filters are declared does
  77. not matter but for those having the same. When defining filters as class names
  78. there is no option to define priority in-line, we will get into that soon.
  79. Finally, CakePHP's plugin notation can be used to define filters located in
  80. plugins::
  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. If you need to pass constructor parameters or settings to you dispatch filter
  88. classes you can do that by providing an array of settings::
  89. Configure::write('Dispatcher.filters', array(
  90. 'MyAssetFilter' => array('service' => 'google.com')
  91. ));
  92. When the filter key is a valid classname, the value can be an array of
  93. parameters that are passed to the dispatch filter. By default the base class
  94. will assign these settings to the ``$settings`` property after merging them with
  95. the defaults in the class.
  96. .. versionchanged:: 2.5
  97. You can now provide constructor settings to dispatch filters in 2.5.
  98. Filter Classes
  99. ==============
  100. Dispatcher filters, when defined as class names in configuration, should extend
  101. the class ``DispatcherFilter`` provided in the `Routing` CakePHP's directory.
  102. Let's create a simple filter to respond to a specific URL with a 'Hello World'
  103. text::
  104. App::uses('DispatcherFilter', 'Routing');
  105. class HelloWorldFilter extends DispatcherFilter {
  106. public $priority = 9;
  107. public function beforeDispatch(CakeEvent $event) {
  108. $request = $event->data['request'];
  109. $response = $event->data['response'];
  110. if ($request->url === 'hello-world') {
  111. $response->body('Hello World');
  112. $event->stopPropagation();
  113. return $response;
  114. }
  115. }
  116. }
  117. This class should be saved in a file in ``app/Routing/Filter/HelloWorldFilter.php``
  118. and configured in the bootstrap file according to how it was explained in the
  119. previous section. There is plenty to explain here, let's begin with the
  120. ``$priority`` value.
  121. As mentioned before, when using filter classes you can only define the order in
  122. which they are run using the ``$priority`` property in the class, default value is
  123. 10 if the property is declared, this means that it will get executed _after_ the
  124. Router class has parsed the request. We do not want this to happen in our
  125. previous example, because most probably you do not have any controller set up
  126. for answering to that URL, hence we chose 9 as our priority.
  127. ``DispatcherFilter`` exposes two methods that can be overridden in subclasses,
  128. they are ``beforeDispatch`` and ``afterDispatch``, and are executed before or after
  129. any controller is executed respectively. Both methods receive a :php:class:`CakeEvent`
  130. object containing the ``request`` and ``response`` objects
  131. (:php:class:`CakeRequest` and :php:class:`CakeResponse` instances) along with an
  132. ``additionalParams`` array inside the ``data`` property. The latter contains
  133. information used for internal dispatching when calling ``requestAction``.
  134. In our example we conditionally returned the ``$response`` object as a result,
  135. this will tell the Dispatcher to not instantiate any controller and return such
  136. object as response immediately to the client. We also added
  137. ``$event->stopPropagation()`` to prevent other filters from being executed after
  138. this one.
  139. Let's now create another filter for altering response headers in any public
  140. page, in our case it would be anything served from the ``PagesController``::
  141. App::uses('DispatcherFilter', 'Routing');
  142. class HttpCacheFilter extends DispatcherFilter {
  143. public function afterDispatch(CakeEvent $event) {
  144. $request = $event->data['request'];
  145. $response = $event->data['response'];
  146. if ($request->params['controller'] !== 'pages') {
  147. return;
  148. }
  149. if ($response->statusCode() === 200) {
  150. $response->sharable(true);
  151. $response->expires(strtotime('+1 day'));
  152. }
  153. }
  154. }
  155. This filter will send a expiration header to 1 day in the future for
  156. all responses produced by the pages controller. You could of course do the same
  157. in the controller, this is just an example of what could be done with filters.
  158. For instance, instead of altering the response you could cache it using the
  159. :php:class:`Cache` class and serve the response from the ``beforeDispatch``
  160. callback.
  161. Inline Filters
  162. ==============
  163. Our last example will use an anonymous function (only available on PHP 5.3+) to
  164. serve a list of posts in JSON format, we encourage you to do so using
  165. controllers and the :php:class:`JsonView` class, but let's imagine you need to save a
  166. few milliseconds for this mission-critical API endpoint::
  167. $postsList = function($event) {
  168. if ($event->data['request']->url !== 'posts/recent.json') {
  169. return;
  170. }
  171. App::uses('ClassRegistry', 'Utility');
  172. $postModel = ClassRegistry::init('Post');
  173. $event->data['response']->body(json_encode($postModel->find('recent')));
  174. $event->stopPropagation();
  175. return $event->data['response'];
  176. };
  177. Configure::write('Dispatcher.filters', array(
  178. 'AssetDispatcher',
  179. 'CacheDispatcher',
  180. 'recent-posts' => array(
  181. 'callable' => $postsList,
  182. 'priority' => 9,
  183. 'on'=> 'before'
  184. )
  185. ));
  186. In previous example we have selected a priority of ``9`` for our filter, so to skip
  187. any other logic either placed in custom or core filters such as CakePHP internal
  188. routing system. Although it is not required, it shows how to make your important
  189. code run first in case you need to trim as much fat as possible from some requests.
  190. For obvious reasons this has the potential of making your app very difficult
  191. to maintain. Filters are an extremely powerful tool when used wisely, adding
  192. response handlers for each URL in your app is not a good use for it. But if you
  193. got a valid reason to do so, then you have a clean solution at hand. Keep in
  194. mind that not everything needs to be a filter, `Controllers` and `Components` are
  195. usually a more accurate choice for adding any request handling code to your app.
  196. .. meta::
  197. :title lang=en: Dispatcher Filters
  198. :description lang=en: Dispatcher filters are a middleware layer for CakePHP allowing to alter the request or response before it is sent
  199. :keywords lang=en: middleware, filters, dispatcher, request, response, rack, application stack, events, beforeDispatch, afterDispatch, router