Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Extracting asset dispatcher and cache dispatcher into separate classe…

…s to provide examples on how to use Dispatcher

Filters
  • Loading branch information...
commit 826699a67002e0ceb2c042c1dd21906fcb92055e 1 parent 70f3cc5
@lorenzo lorenzo authored
View
22 app/Config/bootstrap.php
@@ -122,3 +122,25 @@
* CakePlugin::load('DebugKit'); //Loads a single plugin named DebugKit
*
*/
+
+
+/**
+ * You can attach event listeners to the request lifecyle as Dispatcher Filter . By Default CakePHP bundles two filters:
+ *
+ * - AssetDispatcher filter will serve your asset files (css, images, js, etc) from your themes and plugins
+ * - CacheDispatcher filter will read the Cache.check configure variable and try to serve cached content generated from controllers
+ *
+ * Feel free to remove or add filters as you see fit for your application. A few examples:
+ *
+ * Configure::write('Dispatcher.filters', array(
+ * 'MyCacheFilter', // will use MyCacheFilter class from the Routing/Filter package in your app.
+ * 'MyPlugin.MyFilter', // will use MyFilter class from the Routing/Filter package in MyPlugin plugin.
+ * array('callbale' => $aFunction, 'on' => 'before', 'priority' => 9), // A valid PHP callback type to be called on beforeDispatch
+ * array('callbale' => $anotherMethod, 'on' => 'after'), // A valid PHP callback type to be called on afterDispatch
@jrbasso Collaborator
jrbasso added a note

Typo on callbale. Also, seems the spacing is different.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ *
+ * ));
+ */
+Configure::write('Dispatcher.filters', array(
+ 'AssetDispatcher',
+ 'CacheDispatcher'
+));
View
21 lib/Cake/Console/Templates/skel/Config/bootstrap.php
@@ -63,3 +63,24 @@
* CakePlugin::load('DebugKit'); //Loads a single plugin named DebugKit
*
*/
+
+/**
+ * You can attach event listeners to the request lifecyle as Dispatcher Filter . By Default CakePHP bundles two filters:
+ *
+ * - AssetDispatcher filter will serve your asset files (css, images, js, etc) from your themes and plugins
+ * - CacheDispatcher filter will read the Cache.check configure variable and try to serve cached content generated from controllers
+ *
+ * Feel free to remove or add filters as you see fit for your application. A few examples:
+ *
+ * Configure::write('Dispatcher.filters', array(
+ * 'MyCacheFilter', // will use MyCacheFilter class from the Routing/Filter package in your app.
+ * 'MyPlugin.MyFilter', // will use MyFilter class from the Routing/Filter package in MyPlugin plugin.
+ * array('callbale' => $aFunction, 'on' => 'before', 'priority' => 9), // A valid PHP callback type to be called on beforeDispatch
+ * array('callbale' => $anotherMethod, 'on' => 'after'), // A valid PHP callback type to be called on afterDispatch
+ *
+ * ));
+ */
+Configure::write('Dispatcher.filters', array(
+ 'AssetDispatcher',
+ 'CacheDispatcher'
+));
View
145 lib/Cake/Routing/Dispatcher.php
@@ -79,13 +79,7 @@ public function getEventManager() {
* @return array
**/
public function implementedEvents() {
- return array(
- 'Dispatcher.beforeDispatch' => array(
- array('callable' => array($this, 'asset')),
- array('callable' => array($this, 'cached')),
- array('callable' => array($this, 'parseParams')),
- )
- );
+ return array('Dispatcher.beforeDispatch' => 'parseParams');
}
/**
@@ -283,141 +277,4 @@ protected function _loadRoutes() {
include APP . 'Config' . DS . 'routes.php';
}
-/**
- * Checks whether the response was cached and set the body accordingly.
- *
- * @param CakeEvent $event containing the request and response object
- * @return CakeResponse with cached content if found, null otherwise
- */
- public function cached($event) {
- $path = $event->data['request']->here();
- if (Configure::read('Cache.check') === true) {
- if ($path == '/') {
- $path = 'home';
- }
- $path = strtolower(Inflector::slug($path));
-
- $filename = CACHE . 'views' . DS . $path . '.php';
-
- if (!file_exists($filename)) {
- $filename = CACHE . 'views' . DS . $path . '_index.php';
- }
- if (file_exists($filename)) {
- $controller = null;
- $view = new View($controller);
- $result = $view->renderCache($filename, microtime(true));
- if ($result !== false) {
- $event->data['response']->body($result);
- return $event->data['response'];
- }
- }
- }
- }
-
-/**
- * Checks if a requested asset exists and sends it to the browser
- *
- * @param CakeEvent $event containing the request and response object
- * @return CakeResponse if the client is requesting a recognized asset, null otherwise
- */
- public function asset($event) {
- $url = $event->data['request']->url;
- $response = $event->data['response'];
-
- if (strpos($url, '..') !== false || strpos($url, '.') === false) {
- return;
- }
-
- $filters = Configure::read('Asset.filter');
- $isCss = (
- strpos($url, 'ccss/') === 0 ||
- preg_match('#^(theme/([^/]+)/ccss/)|(([^/]+)(?<!css)/ccss)/#i', $url)
- );
- $isJs = (
- strpos($url, 'cjs/') === 0 ||
- preg_match('#^/((theme/[^/]+)/cjs/)|(([^/]+)(?<!js)/cjs)/#i', $url)
- );
-
- if (($isCss && empty($filters['css'])) || ($isJs && empty($filters['js']))) {
- $response->statusCode(404);
- $event->stopPropagation();
- return $response;
- } elseif ($isCss) {
- include WWW_ROOT . DS . $filters['css'];
- $event->stopPropagation();
- return $response;
- } elseif ($isJs) {
- include WWW_ROOT . DS . $filters['js'];
- $event->stopPropagation();
- return $response;
- }
-
- $pathSegments = explode('.', $url);
- $ext = array_pop($pathSegments);
- $parts = explode('/', $url);
- $assetFile = null;
-
- if ($parts[0] === 'theme') {
- $themeName = $parts[1];
- unset($parts[0], $parts[1]);
- $fileFragment = urldecode(implode(DS, $parts));
- $path = App::themePath($themeName) . 'webroot' . DS;
- if (file_exists($path . $fileFragment)) {
- $assetFile = $path . $fileFragment;
- }
- } else {
- $plugin = Inflector::camelize($parts[0]);
- if (CakePlugin::loaded($plugin)) {
- unset($parts[0]);
- $fileFragment = urldecode(implode(DS, $parts));
- $pluginWebroot = CakePlugin::path($plugin) . 'webroot' . DS;
- if (file_exists($pluginWebroot . $fileFragment)) {
- $assetFile = $pluginWebroot . $fileFragment;
- }
- }
- }
-
- if ($assetFile !== null) {
- $event->stopPropagation();
- $this->_deliverAsset($response, $assetFile, $ext);
- return $response;
- }
- }
-
-/**
- * Sends an asset file to the client
- *
- * @param CakeResponse $response The response object to use.
- * @param string $assetFile Path to the asset file in the file system
- * @param string $ext The extension of the file to determine its mime type
- * @return void
- */
- protected function _deliverAsset(CakeResponse $response, $assetFile, $ext) {
- ob_start();
- $compressionEnabled = Configure::read('Asset.compress') && $response->compress();
- if ($response->type($ext) == $ext) {
- $contentType = 'application/octet-stream';
- $agent = env('HTTP_USER_AGENT');
- if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent) || preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
- $contentType = 'application/octetstream';
- }
- $response->type($contentType);
- }
- if (!$compressionEnabled) {
- $response->header('Content-Length', filesize($assetFile));
- }
- $response->cache(filemtime($assetFile));
- $response->send();
- ob_clean();
- if ($ext === 'css' || $ext === 'js') {
- include $assetFile;
- } else {
- readfile($assetFile);
- }
-
- if ($compressionEnabled) {
- ob_end_flush();
- }
- }
-
}
View
2  lib/Cake/Routing/DispatcherFilter.php
@@ -23,7 +23,7 @@
* event listener with the ability to alter the request or response as needed before it is handled
* by a controller or after the response body has already been built.
*
- * @package Cake.Event
+ * @package Cake.Routing
*/
abstract class DispatcherFilter implements CakeEventListener {
View
156 lib/Cake/Routing/Filter/AssetDispatcher.php
@@ -0,0 +1,156 @@
+<?php
+/**
+ *
+ * PHP 5
+ *
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://cakephp.org CakePHP(tm) Project
+ * @package Cake.Routing
+ * @since CakePHP(tm) v 2.2
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+App::uses('DispatcherFilter', 'Routing');
+
+/**
+ * Filters a request and tests whether it is a file in the webroot folder or not and
+ * serves the file to the client if appropriate.
+ *
+ * @package Cake.Routing.Filter
+ */
+class AssetDispatcher extends DispatcherFilter {
+
+/**
+ * Default priority for all methods in this filter
+ * This filter should run before the request gets parsed by router
+ *
+ * @var int
+ **/
+ public $priority = 9;
+
+/**
+ * Checks if a requested asset exists and sends it to the browser
+ *
+ * @param CakeEvent $event containing the request and response object
+ * @return CakeResponse if the client is requesting a recognized asset, null otherwise
+ */
+ public function beforeDispatch($event) {
+ $url = $event->data['request']->url;
+ $response = $event->data['response'];
+
+ if (strpos($url, '..') !== false || strpos($url, '.') === false) {
+ return;
+ }
+
+ if ($result = $this->_filterAsset($event)) {
+ $event->stopPropagation();
+ return $result;
+ }
+
+ $pathSegments = explode('.', $url);
+ $ext = array_pop($pathSegments);
+ $parts = explode('/', $url);
+ $assetFile = null;
+
+ if ($parts[0] === 'theme') {
+ $themeName = $parts[1];
+ unset($parts[0], $parts[1]);
+ $fileFragment = urldecode(implode(DS, $parts));
+ $path = App::themePath($themeName) . 'webroot' . DS;
+ if (file_exists($path . $fileFragment)) {
+ $assetFile = $path . $fileFragment;
+ }
+ } else {
+ $plugin = Inflector::camelize($parts[0]);
+ if (CakePlugin::loaded($plugin)) {
+ unset($parts[0]);
+ $fileFragment = urldecode(implode(DS, $parts));
+ $pluginWebroot = CakePlugin::path($plugin) . 'webroot' . DS;
+ if (file_exists($pluginWebroot . $fileFragment)) {
+ $assetFile = $pluginWebroot . $fileFragment;
+ }
+ }
+ }
+
+ if ($assetFile !== null) {
+ $event->stopPropagation();
+ $this->_deliverAsset($response, $assetFile, $ext);
+ return $response;
+ }
+ }
+
+/**
+ * Checks if the client is requeting a filtered asset and runs the corresponding
+ * filter if any is configured
+ *
+ * @param CakeEvent $event containing the request and response object
+ * @return CakeResponse if the client is requesting a recognized asset, null otherwise
+ */
+ protected function _filterAsset($event) {
+ $url = $event->data['request']->url;
+ $response = $event->data['response'];
+ $filters = Configure::read('Asset.filter');
+ $isCss = (
+ strpos($url, 'ccss/') === 0 ||
+ preg_match('#^(theme/([^/]+)/ccss/)|(([^/]+)(?<!css)/ccss)/#i', $url)
+ );
+ $isJs = (
+ strpos($url, 'cjs/') === 0 ||
+ preg_match('#^/((theme/[^/]+)/cjs/)|(([^/]+)(?<!js)/cjs)/#i', $url)
+ );
+
+ if (($isCss && empty($filters['css'])) || ($isJs && empty($filters['js']))) {
+ $response->statusCode(404);
+ return $response;
+ } elseif ($isCss) {
+ include WWW_ROOT . DS . $filters['css'];
+ return $response;
+ } elseif ($isJs) {
+ include WWW_ROOT . DS . $filters['js'];
+ return $response;
+ }
+ }
+
+/**
+ * Sends an asset file to the client
+ *
+ * @param CakeResponse $response The response object to use.
+ * @param string $assetFile Path to the asset file in the file system
+ * @param string $ext The extension of the file to determine its mime type
+ * @return void
+ */
+ protected function _deliverAsset(CakeResponse $response, $assetFile, $ext) {
+ ob_start();
+ $compressionEnabled = Configure::read('Asset.compress') && $response->compress();
+ if ($response->type($ext) == $ext) {
+ $contentType = 'application/octet-stream';
+ $agent = env('HTTP_USER_AGENT');
+ if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent) || preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
+ $contentType = 'application/octetstream';
+ }
+ $response->type($contentType);
+ }
+ if (!$compressionEnabled) {
+ $response->header('Content-Length', filesize($assetFile));
+ }
+ $response->cache(filemtime($assetFile));
+ $response->send();
+ ob_clean();
+ if ($ext === 'css' || $ext === 'js') {
+ include $assetFile;
+ } else {
+ readfile($assetFile);
+ }
+
+ if ($compressionEnabled) {
+ ob_end_flush();
+ }
+ }
+
+}
View
70 lib/Cake/Routing/Filter/CacheDispatcher.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ *
+ * PHP 5
+ *
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://cakephp.org CakePHP(tm) Project
+ * @package Cake.Routing
+ * @since CakePHP(tm) v 2.2
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+App::uses('DispatcherFilter', 'Routing');
+
+/**
+ * This filter will check wheter the response was previously cached in the file system
+ * and served it back to the client if appropriate.
+ *
+ * @package Cake.Routing.Filter
+ */
+class CacheDispatcher extends DispatcherFilter {
+
+/**
+ * Default priority for all methods in this filter
+ * This filter should run before the request gets parsed by router
+ *
+ * @var int
+ **/
+ public $priority = 9;
+
+/**
+ * Checks whether the response was cached and set the body accordingly.
+ *
+ * @param CakeEvent $event containing the request and response object
+ * @return CakeResponse with cached content if found, null otherwise
+ */
+ public function beforeDispatch($event) {
+ if (Configure::read('Cache.check') !== true) {
+ return;
+ }
+
+ $path = $event->data['request']->here();
+ if ($path == '/') {
+ $path = 'home';
+ }
+ $path = strtolower(Inflector::slug($path));
+
+ $filename = CACHE . 'views' . DS . $path . '.php';
+
+ if (!file_exists($filename)) {
+ $filename = CACHE . 'views' . DS . $path . '_index.php';
+ }
+ if (file_exists($filename)) {
+ $controller = null;
+ $view = new View($controller);
+ $result = $view->renderCache($filename, microtime(true));
+ if ($result !== false) {
+ $event->data['response']->body($result);
+ return $event->data['response'];
+ }
+ }
+ }
+
+}
View
1  lib/Cake/Test/Case/AllRoutingTest.php
@@ -38,6 +38,7 @@ public static function suite() {
$suite->addTestDirectory($libs . 'Routing');
$suite->addTestDirectory($libs . 'Routing' . DS . 'Route');
+ $suite->addTestDirectory($libs . 'Routing' . DS . 'Filter');
return $suite;
}
}
View
59 lib/Cake/Test/Case/Routing/DispatcherTest.php
@@ -1357,6 +1357,7 @@ public function testAssets() {
'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS)
));
CakePlugin::load(array('TestPlugin', 'TestPluginTwo'));
+ Configure::write('Dispatcher.filters', array('AssetDispatcher'));
$Dispatcher = new TestDispatcher();
$response = $this->getMock('CakeResponse', array('_sendHeader'));
@@ -1377,7 +1378,7 @@ public function testAssets() {
}
/**
- * Data provider for asset()
+ * Data provider for asset filter
*
* - theme assets.
* - plugin assets.
@@ -1475,6 +1476,7 @@ public function testAsset($url, $file) {
'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS)
));
CakePlugin::load(array('TestPlugin', 'PluginJs'));
+ Configure::write('Dispatcher.filters', array('AssetDispatcher'));
$Dispatcher = new TestDispatcher();
$response = $this->getMock('CakeResponse', array('_sendHeader'));
@@ -1503,57 +1505,13 @@ public function testMissingAssetProcessor404() {
'js' => '',
'css' => null
));
+ Configure::write('Dispatcher.filters', array('AssetDispatcher'));
$request = new CakeRequest('ccss/cake.generic.css');
- $event = new CakeEvent('DispatcherTest', $Dispatcher, compact('request', 'response'));
- $this->assertSame($response, $Dispatcher->asset($event));
+ $Dispatcher->dispatch($request, $response);
$this->assertEquals('404', $response->statusCode());
- $this->assertTrue($event->isStopped());
}
-/**
- * test that asset filters work for theme and plugin assets
- *
- * @return void
- */
- public function testAssetFilterForThemeAndPlugins() {
- $Dispatcher = new TestDispatcher();
- $response = $this->getMock('CakeResponse', array('_sendHeader'));
- Configure::write('Asset.filter', array(
- 'js' => '',
- 'css' => ''
- ));
-
- $request = new CakeRequest('theme/test_theme/ccss/cake.generic.css');
- $event = new CakeEvent('DispatcherTest', $Dispatcher, compact('request', 'response'));
- $this->assertSame($response, $Dispatcher->asset($event));
- $this->assertTrue($event->isStopped());
-
- $request = new CakeRequest('theme/test_theme/cjs/debug_kit.js');
- $event = new CakeEvent('DispatcherTest', $Dispatcher, compact('request', 'response'));
- $this->assertSame($response, $Dispatcher->asset($event));
- $this->assertTrue($event->isStopped());
-
- $request = new CakeRequest('test_plugin/ccss/cake.generic.css');
- $event = new CakeEvent('DispatcherTest', $Dispatcher, compact('request', 'response'));
- $this->assertSame($response, $Dispatcher->asset($event));
- $this->assertTrue($event->isStopped());
-
- $request = new CakeRequest('test_plugin/cjs/debug_kit.js');
- $event = new CakeEvent('DispatcherTest', $Dispatcher, compact('request', 'response'));
- $this->assertSame($response, $Dispatcher->asset($event));
- $this->assertTrue($event->isStopped());
-
- $request = new CakeRequest('css/ccss/debug_kit.css');
- $event = new CakeEvent('DispatcherTest', $Dispatcher, compact('request', 'response'));
- $this->assertNull($Dispatcher->asset($event));
- $this->assertFalse($event->isStopped());
-
- $request = new CakeRequest('js/cjs/debug_kit.js');
- $event = new CakeEvent('DispatcherTest', $Dispatcher, compact('request', 'response'));
- $this->assertNull($Dispatcher->asset($event));
- $this->assertFalse($event->isStopped());
- }
/**
* Data provider for cached actions.
@@ -1606,8 +1564,11 @@ public function testFullPageCachingDispatch($url) {
$dispatcher->dispatch($request, $response);
$out = $response->body();
- $event = new CakeEvent('DispatcherTest', $dispatcher, array('request' => $request, 'response' => $response));
- $response = $dispatcher->cached($event);
+ Configure::write('Dispatcher.filters', array('CacheDispatcher'));
+ $request = new CakeRequest($url);
+ $response = $this->getMock('CakeResponse', array('send'));
+ $dispatcher = new TestDispatcher();
+ $dispatcher->dispatch($request, $response);
$cached = $response->body();
$cached = preg_replace('/<!--+[^<>]+-->/', '', $cached);
View
78 lib/Cake/Test/Case/Routing/Filter/AssetDispatcherTest.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * RouterTest file
+ *
+ * PHP 5
+ *
+ * CakePHP(tm) Tests <http://book.cakephp.org/view/1196/Testing>
+ * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests
+ * @package Cake.Test.Case.Routing.Filter
+ * @since CakePHP(tm) v 2.2
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+App::uses('AssetDispatcher', 'Routing/Filter');
+App::uses('CakeEvent', 'Event');
+App::uses('CakeResponse', 'Network');
+
+class AssetDispatcherTest extends CakeTestCase {
+
+/**
+ * tearDown method
+ *
+ * @return void
+ */
+ public function tearDown() {
+ Configure::write('Dispatcher.filters', array());
+ }
+
+/**
+ * test that asset filters work for theme and plugin assets
+ *
+ * @return void
+ */
+ public function testAssetFilterForThemeAndPlugins() {
+ $filter = new AssetDispatcher();
+ $response = $this->getMock('CakeResponse', array('_sendHeader'));
+ Configure::write('Asset.filter', array(
+ 'js' => '',
+ 'css' => ''
+ ));
+
+ $request = new CakeRequest('theme/test_theme/ccss/cake.generic.css');
+ $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response'));
+ $this->assertSame($response, $filter->beforeDispatch($event));
+ $this->assertTrue($event->isStopped());
+
+ $request = new CakeRequest('theme/test_theme/cjs/debug_kit.js');
+ $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response'));
+ $this->assertSame($response, $filter->beforeDispatch($event));
+ $this->assertTrue($event->isStopped());
+
+ $request = new CakeRequest('test_plugin/ccss/cake.generic.css');
+ $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response'));
+ $this->assertSame($response, $filter->beforeDispatch($event));
+ $this->assertTrue($event->isStopped());
+
+ $request = new CakeRequest('test_plugin/cjs/debug_kit.js');
+ $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response'));
+ $this->assertSame($response, $filter->beforeDispatch($event));
+ $this->assertTrue($event->isStopped());
+
+ $request = new CakeRequest('css/ccss/debug_kit.css');
+ $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response'));
+ $this->assertNull($filter->beforeDispatch($event));
+ $this->assertFalse($event->isStopped());
+
+ $request = new CakeRequest('js/cjs/debug_kit.js');
+ $event = new CakeEvent('DispatcherTest', $this, compact('request', 'response'));
+ $this->assertNull($filter->beforeDispatch($event));
+ $this->assertFalse($event->isStopped());
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.