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:

  1. Add the ApiDispatcher class name in the Dispatcher.filters configuration of your bootstrap.php file:
    Configure::write('Dispatcher.filters', array(
      'AssetDispatcher',
      'CacheDispatcher',
      'ApiDispatcher'
    ));
  2. Add the following $priority property to the ApiDispatcher class in app/Routing/Filter/:
    public $priority = 9;
  3. In the same ApiDispatcher class, add the following beforeDispatch() 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;
      }
    }
  4. Navigating to /api/libraries in your browser will return the libraries in a JSON formatted response, as shown in the following screenshot:
    How to do it...

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.