Skip to content

Commit

Permalink
enabling a callback to be used in addition to String::insert-style st…
Browse files Browse the repository at this point in the history
…rings for Dispatcher rules. a callback can be used to define the rules as well
  • Loading branch information
rmarscher committed Apr 26, 2012
1 parent 3e58f83 commit dc3e17d
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 7 deletions.
54 changes: 47 additions & 7 deletions action/Dispatcher.php
Expand Up @@ -50,15 +50,43 @@ class Dispatcher extends \lithium\core\StaticObject {
* Each key in the array represents a 'rule'; if a key that matches the rule is present (and * Each key in the array represents a 'rule'; if a key that matches the rule is present (and
* not empty) in a route, (i.e. the result of `lithium\net\http\Router::parse()`) then the * not empty) in a route, (i.e. the result of `lithium\net\http\Router::parse()`) then the
* rule's value will be applied to the route before it is dispatched. When applying a rule, any * rule's value will be applied to the route before it is dispatched. When applying a rule, any
* array elements array elements of the flag which are present in the route will be modified * array elements of the flag which are present in the route will be modified
* using a `lithium\util\String::insert()`-formatted string. * using a `lithium\util\String::insert()`-formatted string. Alternatively,
* a callback can be used to do custom transformations other than the
* default `lithium\util\String::insert()`.
* *
* For example, to implement action prefixes (i.e. `admin_index()`), set a rule named 'admin', * For example, to implement action prefixes (i.e. `admin_index()`), set a rule named 'admin',
* with a value array containing a modifier key for the `action` element of a route, i.e.: * with a value array containing a modifier key for the `action` element of a route, i.e.:
* `array('action' => 'admin_{:action}')`. Now, if the `'admin'` key is present and not empty * `array('action' => 'admin_{:action}')`. Now, if the `'admin'` key is present and not empty
* in the parameters returned from routing, the value of `'action'` will be rewritten per the * in the parameters returned from routing, the value of `'action'` will be rewritten per the
* settings in the rule. * settings in the rule.
* *
* Here's another example. To support normalizing actions,
* set a rule named 'action' with a value array containing a callback that uses
* `lithium\util\Inflector` to camelize the action:
*
* {{{
* use lithium\action\Dispatcher;
* use lithium\util\Inflector;
*
* Dispatcher::config(array('rules' => array(
* 'action' => array('action' => function($params) {
* return Inflector::camelize(strtolower($params['action']), false);
* })
* )));
* }}}
*
* The rules can be a callback as well:
*
* {{{
* Dispatcher::config(array('rules' => function($params) {
* if (isset($params['admin'])) {
* return array('special' => array('action' => 'special_{:action}'));
* }
* return array();
* }));
* }}}
*
* @see lithium\action\Dispatcher::config() * @see lithium\action\Dispatcher::config()
* @see lithium\util\String::insert() * @see lithium\util\String::insert()
*/ */
Expand All @@ -82,6 +110,10 @@ public static function config(array $config = array()) {


foreach ($config as $key => $val) { foreach ($config as $key => $val) {
$key = "_{$key}"; $key = "_{$key}";
if (!is_array($val)) {
static::${$key} = $val;
continue;
}
if (isset(static::${$key})) { if (isset(static::${$key})) {
static::${$key} = $val + static::${$key}; static::${$key} = $val + static::${$key};
} }
Expand Down Expand Up @@ -135,6 +167,7 @@ public static function run($request, array $options = array()) {
public static function applyRules(&$params) { public static function applyRules(&$params) {
$result = array(); $result = array();
$values = array(); $values = array();
$rules = static::$_rules;


if (!$params) { if (!$params) {
return false; return false;
Expand All @@ -158,17 +191,24 @@ public static function applyRules(&$params) {
} }
$values += $params; $values += $params;


foreach (static::$_rules as $rule => $value) { if (is_callable($rules)) {
$rules = $rules($params);
}
foreach ($rules as $rule => $value) {
if (!isset($values[$rule])) {
continue;
}
foreach ($value as $k => $v) { foreach ($value as $k => $v) {
if (isset($values[$rule])) { if (is_callable($v)) {
$result[$k] = String::insert($v, $values); $result[$k] = $v($values);
continue;
} }
$match = preg_replace('/\{:\w+\}/', '@', $v); $match = preg_replace('/\{:\w+\}/', '@', $v);
$match = preg_replace('/@/', '.+', preg_quote($match, '/')); $match = preg_replace('/@/', '.+', preg_quote($match, '/'));

if (preg_match('/' . $match . '/i', $values[$k])) { if (preg_match('/' . $match . '/i', $values[$k])) {
return false; continue;
} }
$result[$k] = String::insert($v, $values);
} }
} }
return $result + $values; return $result + $values;
Expand Down
45 changes: 45 additions & 0 deletions tests/cases/action/DispatcherTest.php
Expand Up @@ -13,6 +13,7 @@
use lithium\net\http\Router; use lithium\net\http\Router;
use lithium\action\Dispatcher; use lithium\action\Dispatcher;
use lithium\tests\mocks\action\MockDispatcher; use lithium\tests\mocks\action\MockDispatcher;
use lithium\util\Inflector;


class DispatcherTest extends \lithium\test\Unit { class DispatcherTest extends \lithium\test\Unit {


Expand Down Expand Up @@ -130,6 +131,50 @@ public function testConfigManipulation() {
$result = end(MockDispatcher::$dispatched); $result = end(MockDispatcher::$dispatched);
$expected = array('action' => 'admin_test', 'controller' => 'Test', 'admin' => true); $expected = array('action' => 'admin_test', 'controller' => 'Test', 'admin' => true);
$this->assertEqual($expected, $result->params); $this->assertEqual($expected, $result->params);

MockDispatcher::config(array('rules' => array(
'action' => array('action' => function($params) {
return Inflector::camelize(strtolower($params['action']), false);
})
)));

MockDispatcher::$dispatched = array();
Router::reset();
Router::connect('/', array('controller' => 'test', 'action' => 'TeST-camelize'));
MockDispatcher::run(new Request(array('url' => '/')));

$result = end(MockDispatcher::$dispatched);
$expected = array('action' => 'testCamelize', 'controller' => 'Test');
$this->assertEqual($expected, $result->params);

MockDispatcher::config(array('rules' => function($params) {
if (isset($params['admin'])) {
return array('special' => array('action' => 'special_{:action}'));
}
return array();
}));

MockDispatcher::$dispatched = array();
Router::reset();
Router::connect('/', array('controller' => 'test', 'action' => 'test', 'admin' => true));
Router::connect('/special', array(
'controller' => 'test', 'action' => 'test',
'admin' => true, 'special' => true
));
MockDispatcher::run(new Request(array('url' => '/')));

$result = end(MockDispatcher::$dispatched);
$expected = array('action' => 'test', 'controller' => 'Test', 'admin' => true);
$this->assertEqual($expected, $result->params);

MockDispatcher::run(new Request(array('url' => '/special')));

$result = end(MockDispatcher::$dispatched);
$expected = array(
'action' => 'special_test', 'controller' => 'Test',
'admin' => true, 'special' => true
);
$this->assertEqual($expected, $result->params);
} }


public function testControllerLookupFail() { public function testControllerLookupFail() {
Expand Down

0 comments on commit dc3e17d

Please sign in to comment.