From 1a71c1093492ba1e81a99cb16906ad2dfd749daa Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 21 Mar 2014 22:01:20 -0400 Subject: [PATCH] Added `yii\web\PrefixUrlRule` --- framework/CHANGELOG.md | 3 +- framework/web/PrefixUrlRule.php | 138 +++++++++++ .../unit/framework/web/PrefixUrlRuleTest.php | 217 ++++++++++++++++++ 3 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 framework/web/PrefixUrlRule.php create mode 100644 tests/unit/framework/web/PrefixUrlRuleTest.php diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index fc08a1dbb3..f6fccfe4df 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -260,7 +260,8 @@ Yii Framework 2 Change Log - New #2149: Added `yii\base\DynamicModel` to support ad-hoc data validation (qiangxue) - New #2360: Added `AttributeBehavior` and `BlameableBehavior`, and renamed `AutoTimestamp` to `TimestampBehavior` (lucianobaraglia, qiangxue) - New: Yii framework now comes with core messages in multiple languages -- New: Added yii\codeception\DbTestCase (qiangxue) +- New: Added `yii\codeception\DbTestCase` (qiangxue) +- New: Added `yii\web\PrefixUrlRule` (qiangxue) 2.0.0 alpha, December 1, 2013 diff --git a/framework/web/PrefixUrlRule.php b/framework/web/PrefixUrlRule.php new file mode 100644 index 0000000000..789530a629 --- /dev/null +++ b/framework/web/PrefixUrlRule.php @@ -0,0 +1,138 @@ + 'admin', + * 'rules' => [ + * 'login' => 'user/login', + * 'logout' => 'user/logout', + * 'dashboard' => 'default/dashboard', + * ], + * ]); + * + * // the above rule is equivalent to the following three rules: + * + * [ + * 'admin/login' => 'admin/user/login', + * 'admin/logout' => 'admin/user/logout', + * 'admin/dashboard' => 'admin/default/dashboard', + * ] + * ``` + * + * The above example assumes the prefix for patterns and routes are the same. They can be made different + * by configuring [[prefix]] and [[routePrefix]] separately. + * + * Using a PrefixUrlRule is more efficient than directly declaring the individual rules its contains. + * This is because PrefixUrlRule can quickly determine if it should process a URL parsing or creation request + * by simply checking if the prefix matches. + * + * @author Qiang Xue + * @since 2.0 + */ +class PrefixUrlRule extends CompositeUrlRule +{ + /** + * @var array the rules contained within this composite rule. Please refer to [[UrlManager::rules]] + * for the format of this property. + * @see prefix + * @see routePrefix + */ + public $rules = []; + /** + * @var string the prefix for the pattern part of every rule declared in [[rules]]. + * The prefix and the pattern will be separated with a slash. + */ + public $prefix; + /** + * @var string the prefix for the route part of every rule declared in [[rules]]. + * The prefix and the route will be separated with a slash. + * If this property is not set, it will take the value of [[prefix]]. + */ + public $routePrefix; + /** + * @var array the default configuration of URL rules. Individual rule configurations + * specified via [[rules]] will take precedence when the same property of the rule is configured. + */ + public $ruleConfig = ['class' => 'yii\web\UrlRule']; + + + /** + * @inheritdoc + */ + public function init() + { + if ($this->routePrefix === null) { + $this->routePrefix = $this->prefix; + } + $this->prefix = trim($this->prefix, '/'); + $this->routePrefix = trim($this->routePrefix, '/'); + parent::init(); + } + + /** + * @inheritdoc + */ + protected function createRules() + { + $rules = []; + foreach ($this->rules as $key => $rule) { + if (!is_array($rule)) { + $rule = [ + 'pattern' => ltrim($this->prefix . '/' . $key, '/'), + 'route' => ltrim($this->routePrefix . '/' . $rule, '/'), + ]; + } elseif (isset($rule['pattern'], $rule['route'])) { + $rule['pattern'] = ltrim($this->prefix . '/' . $rule['pattern'], '/'); + $rule['route'] = ltrim($this->routePrefix . '/' . $rule['route'], '/'); + } + + $rule = Yii::createObject(array_merge($this->ruleConfig, $rule)); + if (!$rule instanceof UrlRuleInterface) { + throw new InvalidConfigException('URL rule class must implement UrlRuleInterface.'); + } + $rules[] = $rule; + } + return $rules; + } + + /** + * @inheritdoc + */ + public function parseRequest($manager, $request) + { + $pathInfo = $request->getPathInfo(); + if ($this->prefix === '' || strpos($pathInfo . '/', $this->prefix . '/') === 0) { + return parent::parseRequest($manager, $request); + } else { + return false; + } + } + + /** + * @inheritdoc + */ + public function createUrl($manager, $route, $params) + { + if ($this->routePrefix === '' || strpos($route, $this->routePrefix . '/') === 0) { + return parent::createUrl($manager, $route, $params); + } else { + return false; + } + } +} diff --git a/tests/unit/framework/web/PrefixUrlRuleTest.php b/tests/unit/framework/web/PrefixUrlRuleTest.php new file mode 100644 index 0000000000..e1def88529 --- /dev/null +++ b/tests/unit/framework/web/PrefixUrlRuleTest.php @@ -0,0 +1,217 @@ +mockApplication(); + } + + public function testCreateUrl() + { + $manager = new UrlManager(['cache' => null]); + $suites = $this->getTestsForCreateUrl(); + foreach ($suites as $i => $suite) { + list ($name, $config, $tests) = $suite; + $rule = new PrefixUrlRule($config); + foreach ($tests as $j => $test) { + list ($route, $params, $expected) = $test; + $url = $rule->createUrl($manager, $route, $params); + $this->assertEquals($expected, $url, "Test#$i-$j: $name"); + } + } + } + + public function testParseRequest() + { + $manager = new UrlManager(['cache' => null]); + $request = new Request(['hostInfo' => 'http://en.example.com']); + $suites = $this->getTestsForParseRequest(); + foreach ($suites as $i => $suite) { + list ($name, $config, $tests) = $suite; + $rule = new PrefixUrlRule($config); + foreach ($tests as $j => $test) { + $request->pathInfo = $test[0]; + $route = $test[1]; + $params = isset($test[2]) ? $test[2] : []; + $result = $rule->parseRequest($manager, $request); + if ($route === false) { + $this->assertFalse($result, "Test#$i-$j: $name"); + } else { + $this->assertEquals([$route, $params], $result, "Test#$i-$j: $name"); + } + } + } + } + + protected function getTestsForCreateUrl() + { + // structure of each test + // message for the test + // config for the URL rule + // list of inputs and outputs + // route + // params + // expected output + return [ + [ + 'no prefix', + [ + 'rules' => [ + 'login' => 'user/login', + 'logout' => 'user/logout', + ], + ], + [ + ['user/login', [], 'login'], + ['user/logout', [], 'logout'], + ['user/create', [], false], + ], + ], + [ + 'prefix only', + [ + 'prefix' => 'admin', + 'rules' => [ + 'login' => 'user/login', + 'logout' => 'user/logout', + ], + ], + [ + ['admin/user/login', [], 'admin/login'], + ['admin/user/logout', [], 'admin/logout'], + ['user/create', [], false], + ], + ], + [ + 'prefix and routePrefix different', + [ + 'prefix' => '_', + 'routePrefix' => 'admin', + 'rules' => [ + 'login' => 'user/login', + 'logout' => 'user/logout', + ], + ], + [ + ['admin/user/login', [], '_/login'], + ['admin/user/logout', [], '_/logout'], + ['user/create', [], false], + ], + ], + [ + 'ruleConfig with suffix', + [ + 'prefix' => '_', + 'routePrefix' => 'admin', + 'ruleConfig' => [ + 'suffix' => '.html', + 'class' => 'yii\\web\\UrlRule' + ], + 'rules' => [ + 'login' => 'user/login', + 'logout' => 'user/logout', + ], + ], + [ + ['admin/user/login', [], '_/login.html'], + ['admin/user/logout', [], '_/logout.html'], + ['user/create', [], false], + ], + ], + ]; + } + + protected function getTestsForParseRequest() + { + // structure of each test + // message for the test + // config for the URL rule + // list of inputs and outputs + // pathInfo + // expected route, or false if the rule doesn't apply + // expected params, or not set if empty + return [ + [ + 'no prefix', + [ + 'rules' => [ + 'login' => 'user/login', + 'logout' => 'user/logout', + ], + ], + [ + ['login', 'user/login'], + ['logout', 'user/logout'], + ['create', false], + ], + ], + [ + 'prefix only', + [ + 'prefix' => 'admin', + 'rules' => [ + 'login' => 'user/login', + 'logout' => 'user/logout', + ], + ], + [ + ['admin/login', 'admin/user/login'], + ['admin/logout', 'admin/user/logout'], + ['admin/create', false], + ['create', false], + ], + ], + [ + 'prefix and routePrefix different', + [ + 'prefix' => '_', + 'routePrefix' => 'admin', + 'rules' => [ + 'login' => 'user/login', + 'logout' => 'user/logout', + ], + ], + [ + ['_/login', 'admin/user/login'], + ['_/logout', 'admin/user/logout'], + ['_/create', false], + ['create', false], + ], + ], + [ + 'ruleConfig with suffix', + [ + 'prefix' => '_', + 'routePrefix' => 'admin', + 'ruleConfig' => [ + 'suffix' => '.html', + 'class' => 'yii\\web\\UrlRule' + ], + 'rules' => [ + 'login' => 'user/login', + 'logout' => 'user/logout', + ], + ], + [ + ['_/login.html', 'admin/user/login'], + ['_/logout.html', 'admin/user/logout'], + ['_/logout', false], + ['_/create.html', false], + ], + ], + ]; + } +}