- CakePHP 2 Application Cookbook
- James Watts Jorge González
- 816字
- 2025-03-31 03:57:19
Dispatch filter
The dispatch filters allow you to intercept the dispatch cycle, run code before or after the controller is built and dispatched, and even return early without completing the full cycle.
In this recipe, we'll create a dispatch filter that exposes an API without requiring the full dispatch cycle.
Getting ready
First, we'll create a file named ApiDispatcher.php
in app/Routing/Filter/
as shown in the following code:
<?php App::uses('DispatcherFilter', 'Routing'); App::uses('ClassRegistry', 'Utility'); class ApiDispatcher extends DispatcherFilter { }
We'll then need some data to serve from our API. For that, create a libraries
table with the following SQL statement:
CREATE TABLE libraries ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100), details TEXT, created DATETIME, modified DATETIME, PRIMARY KEY(id) );
Finally, populate the libraries
table with the following SQL statement:
INSERT INTO libraries (name, details, created, modified) VALUES ('Cake', 'Yummy and sweet', NOW(), NOW()), ('Cookie', 'Browsers love cookies', NOW(), NOW()), ('Helper', 'Helping you all the way', NOW(), NOW());
How to do it...
Perform the following steps:
- Add the
ApiDispatcher
class name in theDispatcher.filters
configuration of yourbootstrap.php file
:Configure::write('Dispatcher.filters', array( 'AssetDispatcher', 'CacheDispatcher', 'ApiDispatcher' ));
- Add the following
$priority
property to theApiDispatcher
class inapp/Routing/Filter/
:public $priority = 9;
- In the same
ApiDispatcher
class, add the followingbeforeDispatch()
method;public function beforeDispatch(CakeEvent $event) { $request = $event->data['request']; $response = $event->data['response']; $url = $request->url; if (substr($url, 0, 4) === 'api/') { try { switch (substr($url, 4)) { case 'libraries': $object = array( 'status' => 'success', 'data' => ClassRegistry::init('Library')->find('all') ); break; default: throw new Exception('Unknown end-point'); } } catch (Exception $e) { $response->statusCode(500); $object = array( 'status' => 'error', 'message' => $e->getMessage(), 'code' => $e->getCode() ); if (Configure::read('debug') > 0) { $object['data'] = array( 'file' => $e->getFile(), 'trace' => $e->getTrace() ); } } $response->type('json'); $response->body(json_encode($object)); $event->stopPropagation(); return $response; } }
- Navigating to
/api/libraries
in your browser will return the libraries in a JSON formatted response, as shown in the following screenshot:
How it works...
In the first step, we added the ApiDispatcher
class to the Dispatch.filter
configuration array. When adding a class name, by setting it as a key of the Dispatch.filters
array, you can pass an array of constructor $settings
as the value. The class name may also use dot notation to load dispatch filters from a plugin.
The order here is also important, as the filters are executed in order of definition. However, we also set the $priority
property on the class to a value of 9
. The default priority is 10
, with lower priorities executing first.
We then defined a beforeDispatch()
method. The DispatchFilter
class has two callback methods, beforeDisptach()
and afterDisptach()
, which executes before and after the dispatch cycle. These methods receive a single argument, which is the CakeEvent
object. This contains the request and response objects, and it can also contain an array that is passed when the dispatch is triggered by a call to requestAction()
.
In our callback method, we first extracted the request and response objects from $event['reqesut']
and $event['response']
respectively. We then read the URL of the request from CakeRequest::$url
, which gave us the end-point for our API. We checked that we're dealing with an API call by confirming that the URL starts with api/
. The remaining part of the URL string then represents the name of our endpoint. We then used a switch()
statement to allow future extensions to the API, where adding a new endpoint would be as simple as just adding another case to our switch()
.
In our case for libraries
, we defined an array in an $object
variable, which will later serve as our JSON object, following the Jsend spec. In this case, we define a status
key with the value of success
, and then use the ClassRegistry
utility class to load our Library
model on the fly, calling the find()
method to return all
records. These are stored in the data
key of our JSON object. You'll notice that we defined all of this inside a try/catch
statement. This is useful, as it allows us to capture and format any exceptions as a JSON formatted response. The default
case for our switch throws an Exception
, which advises that an end-point does not exist.
We finally prepare our response by calling the CakeResponse::type()
method to set the Content-Type
parameter of the HTTP response to application/json
. The CakeResponse
class has a significant list of predefined content types, which can be easily applied on demand. You can also define new content types if your application needs to support them. Following this, we use the json_encode()
function to format our $object
variable as a JSON object, and then apply that to the body of the HTTP response by using CakeResponse::body()
. We then call stopPropagation()
to stop any further dispatch filters from being executed and return the $response
object to be returned to the client.
There's more…
If you didn't want to create a class for your dispatch filter, you can also define it as a PHP callable, for example:
Configure::write('Dispatcher.filters', array( 'AssetDispatcher', 'CacheDispatcher', 'example' => array( 'callable' => function(CakeEvent $event) { // your code here }, 'on' => 'before', 'priority' => 9 ) ));
In the preceding code, the key is the name of your filter, while the array contains the configuration. callable
is the function that is executed. This could be an anonymous function or any valid callable type. The exception here is that a string is not interpreted as a function name, but instead it is interpreted as a class name. The on
setting defines the point at which the function is executed. This can either be before
or after
, to run before or after the dispatch cycle. Finally, the priority
setting is the same as the $priority
property we set in our class, and defines the execution order in relation to other dispatch filters.