CakePHP as a Microframework
One of the features that most frameworks toute is the ability to respond to a request from the route file immediately. For instance, here is how SlimPHP applications are structured (at least initially):
<?php
$app = new \Slim\Slim();
$app->get('/hello/:name', function ($name) {
echo "Hello, $name";
});
$app->run();
?>
People familiar with this type of application are likely familiar with the
Sinatra
microframework.
CakePHP has typically been in the opposite camp. Lots of classes to wire up to get a response on the page. Kind of lame, and then you have to integrate with CakePHP’s conventions, which can be frustrating if you simply want to use the framework as a library. It’s quite straightforward to turn CakePHP into a microframework using dispatch filters.
Lets define a simple api. We’ll want to be able to connect arbitrary routes to a callable
or a class that has a respond
method. This can look like the following:
<?php
class ResponseInterface {
public abstract function respond($request, $response);
}
class HelloWorld implements ResponseInterface {
public function respond($request, $response) {
$response->body('Hello World');
}
}
Router::connect('/hello/*', ['callable' => function($request, $response) {
$response->body('Hello World');
}]);
Router::connect('/world/*', ['callable' => 'HelloWorld']);
?>
Controller classes have plumbing to auto-generate responses based on just the CakeRequest
and CakeResponse
objects, hence why they are necessary. We also implement the ResponseInterface
class to make the PHPJava people happy :)
To route these properly, we’ll hook into CakePHP’s dispatch cycle using a custom dispatch filter as follows:
<?php
App::uses('DispatcherFilter', 'Routing');
class CallableFilter extends DispatcherFilter {
public function beforeDispatch(CakeEvent $event) {
$callable = null;
if (isset($event->data['request']->params['callable'])) {
$callable = $event->data['request']->params['callable'];
}
if (is_string($callable) && class_exists($callable)) {
$callable = new $callable;
$callable->respond($event->data['request'], $event->data['response']);
} elseif (is_callable($callable)) {
$callable($event->data['request'], $event->data['response']);
} else {
return null;
}
$event->stopPropagation();
return $event->data['response'];
}
}
?>
In our CallableFilter, we check for the existence of a callable
. For practicality, we’re a bit flexible in this definition and also allow class names to be “callables”. All callable
executions are given CakeRequest
and a CakeResponse
objects, and we automatically call $event->stopPropagation()
should the callable be invoked.
To configure our filter, simply attach it to your DispatcherFilter configuration in app/Config/bootstrap.php
like so:
<?php
Configure::write('Dispatcher.filters', [
'AssetDispatcher',
'CacheDispatcher',
'CallableFilter'
]);
?>
And voila! You have a CakePHP microframework.
Some things you can now do with this setup:
- Configure before and after request filters
- Setup a templating system (with helper loading)
- Automatically load model classes based on class name and configuration
- Figure out how to do reverse routing
- Reimplement all of the CakePHP dispatching because you refuse to use a full framework ;)
Microframeworks have their place, and while I don’t recommend you implement all of your CakePHP applications using the above setup, it can be a powerful tool in your arsenal.