From 09e4229e16cfa174ee83c9217d1d2dbdd19e9b93 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 30 May 2013 13:33:38 +0200 Subject: [PATCH 1/4] [WIP] RESTful routing syntax for UrlManager issue #303 Here is the proposal for RESTful routing syntax: https://gist.github.com/cebe/5674918 --- framework/yii/web/UrlManager.php | 32 ++++++++++++++++++++++++++++- tests/unit/framework/web/UrlManagerTest.php | 2 ++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/framework/yii/web/UrlManager.php b/framework/yii/web/UrlManager.php index 47f5c5d..a6ef0cf 100644 --- a/framework/yii/web/UrlManager.php +++ b/framework/yii/web/UrlManager.php @@ -43,6 +43,28 @@ class UrlManager extends Component * array, one can use the key to represent the pattern and the value the corresponding route. * For example, `'post/' => 'post/view'`. * + * For RESTful routing this shortcut also allows you to specify the [[UrlRule::verb|HTTP verb]] + * the rule should apply for by prepending it to the pattern, separated by a blank. + * For example, `'PUT post/' => 'post/update'`. + * You may specify multiple verbs by separating them with comma + * like this: `'POST,PUT post/index' => 'post/create'`. + * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way + * so you normally would not specify a verb for normal GET request. + * + * Here is an example configuration for RESTful CRUD controller: + * ~~~php + * array( + * 'dashboard' => 'site/index', + * + * 'POST s' => '/create', + * 's' => '/index', + * + * 'PUT /' => '/update', + * 'DELETE /' => '/delete', + * '/' => '/view', + * ); + * ~~~ + * * Note that if you modify this property after the UrlManager object is created, make sure * you populate the array with rule objects instead of rule configurations. */ @@ -115,9 +137,17 @@ class UrlManager extends Component foreach ($this->rules as $key => $rule) { if (!is_array($rule)) { $rule = array( - 'pattern' => $key, 'route' => $rule, ); + if (($pos = strpos($key, ' ')) !== false) { + $verbs = substr($key, 0, $pos); + if (preg_match('/^((GET|POST|PUT|DELETE),?)*$/', $verbs, $matches)) { + $rule['verb'] = explode(',', $verbs); + $rule['mode'] = UrlRule::PARSING_ONLY; + $key = substr($key, $pos + 1); + } + } + $rule['pattern'] = $key; } $rules[] = Yii::createObject(array_merge($this->ruleConfig, $rule)); } diff --git a/tests/unit/framework/web/UrlManagerTest.php b/tests/unit/framework/web/UrlManagerTest.php index 0f08790..d0e8775 100644 --- a/tests/unit/framework/web/UrlManagerTest.php +++ b/tests/unit/framework/web/UrlManagerTest.php @@ -248,4 +248,6 @@ class UrlManagerTest extends TestCase $result = $manager->parseRequest($request); $this->assertFalse($result); } + + // TODO test RESTful pattern syntax e.g. 'GET index' => 'site/index' } From fbb93d0b63f8fa91ee9068dc563f27ddae4a530c Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 3 Jun 2013 08:42:50 +0200 Subject: [PATCH 2/4] doc enhancements for #461 --- framework/yii/web/UrlManager.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/framework/yii/web/UrlManager.php b/framework/yii/web/UrlManager.php index a6ef0cf..d7135c5 100644 --- a/framework/yii/web/UrlManager.php +++ b/framework/yii/web/UrlManager.php @@ -43,15 +43,18 @@ class UrlManager extends Component * array, one can use the key to represent the pattern and the value the corresponding route. * For example, `'post/' => 'post/view'`. * - * For RESTful routing this shortcut also allows you to specify the [[UrlRule::verb|HTTP verb]] - * the rule should apply for by prepending it to the pattern, separated by a blank. + * For RESTful routing the mentioned shortcut format also allows you to specify the + * [[UrlRule::verb|HTTP verb]] that the rule should apply for. + * You can do that by prepending it to the pattern, separated by a space. * For example, `'PUT post/' => 'post/update'`. * You may specify multiple verbs by separating them with comma * like this: `'POST,PUT post/index' => 'post/create'`. + * The supported verbs in the shortcut format are: GET, POST, PUT and DELETE. * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way * so you normally would not specify a verb for normal GET request. * * Here is an example configuration for RESTful CRUD controller: + * * ~~~php * array( * 'dashboard' => 'site/index', From 11923064810be47ed24888efcd53a36674e83fd2 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 3 Jun 2013 08:59:41 +0200 Subject: [PATCH 3/4] improved UrlManager RESTful syntax regex --- framework/yii/web/UrlManager.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/framework/yii/web/UrlManager.php b/framework/yii/web/UrlManager.php index d7135c5..44c63c5 100644 --- a/framework/yii/web/UrlManager.php +++ b/framework/yii/web/UrlManager.php @@ -45,11 +45,11 @@ class UrlManager extends Component * * For RESTful routing the mentioned shortcut format also allows you to specify the * [[UrlRule::verb|HTTP verb]] that the rule should apply for. - * You can do that by prepending it to the pattern, separated by a space. + * You can do that by prepending it to the pattern, separated by space. * For example, `'PUT post/' => 'post/update'`. * You may specify multiple verbs by separating them with comma * like this: `'POST,PUT post/index' => 'post/create'`. - * The supported verbs in the shortcut format are: GET, POST, PUT and DELETE. + * The supported verbs in the shortcut format are: GET, HEAD, POST, PUT and DELETE. * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way * so you normally would not specify a verb for normal GET request. * @@ -142,13 +142,10 @@ class UrlManager extends Component $rule = array( 'route' => $rule, ); - if (($pos = strpos($key, ' ')) !== false) { - $verbs = substr($key, 0, $pos); - if (preg_match('/^((GET|POST|PUT|DELETE),?)*$/', $verbs, $matches)) { - $rule['verb'] = explode(',', $verbs); - $rule['mode'] = UrlRule::PARSING_ONLY; - $key = substr($key, $pos + 1); - } + if (preg_match('/^((?:(GET|HEAD|POST|PUT|DELETE),)*(GET|HEAD|POST|PUT|DELETE))\s+(.*)$/', $key, $matches)) { + $rule['verb'] = explode(',', $matches[1]); + $rule['mode'] = UrlRule::PARSING_ONLY; + $key = $matches[4]; } $rule['pattern'] = $key; } From e94cc6bb9ea56886975827ee41087569438d2bb9 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 3 Jun 2013 09:32:14 +0200 Subject: [PATCH 4/4] unit test for UrlManager RESTful routing syntax removed test param 'appClass' as it does not make sense to configure application class for all tests, some explicitly need webapp where some might explicitly need console app. --- tests/unit/TestCase.php | 3 +- tests/unit/data/config.php | 2 -- tests/unit/framework/web/UrlManagerTest.php | 54 ++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/tests/unit/TestCase.php b/tests/unit/TestCase.php index 479f85d..efcedf0 100644 --- a/tests/unit/TestCase.php +++ b/tests/unit/TestCase.php @@ -38,14 +38,13 @@ abstract class TestCase extends \yii\test\TestCase * The application will be destroyed on tearDown() automatically. * @param array $config The application configuration, if needed */ - protected function mockApplication($config=array()) + protected function mockApplication($config = array(), $appClass = '\yii\console\Application') { static $defaultConfig = array( 'id' => 'testapp', 'basePath' => __DIR__, ); - $appClass = $this->getParam( 'appClass', '\yii\web\Application' ); new $appClass(array_merge($defaultConfig,$config)); } diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php index 88c8127..a2cc445 100644 --- a/tests/unit/data/config.php +++ b/tests/unit/data/config.php @@ -1,8 +1,6 @@ '\yii\web\Application', - 'appClass' => '\yii\console\Application', 'databases' => array( 'mysql' => array( 'dsn' => 'mysql:host=127.0.0.1;dbname=yiitest', diff --git a/tests/unit/framework/web/UrlManagerTest.php b/tests/unit/framework/web/UrlManagerTest.php index d0e8775..7da8f34 100644 --- a/tests/unit/framework/web/UrlManagerTest.php +++ b/tests/unit/framework/web/UrlManagerTest.php @@ -249,5 +249,57 @@ class UrlManagerTest extends TestCase $this->assertFalse($result); } - // TODO test RESTful pattern syntax e.g. 'GET index' => 'site/index' + public function testParseRESTRequest() + { + $manager = new UrlManager(array( + 'cache' => null, + )); + $request = new Request; + + // pretty URL rules + $manager = new UrlManager(array( + 'enablePrettyUrl' => true, + 'cache' => null, + 'rules' => array( + 'PUT,POST post//' => 'post/create', + 'DELETE post/<id>' => 'post/delete', + 'post/<id>/<title>' => 'post/view', + 'POST/GET' => 'post/get', + ), + )); + // matching pathinfo GET request + $_SERVER['REQUEST_METHOD'] = 'GET'; + $request->pathInfo = 'post/123/this+is+sample'; + $result = $manager->parseRequest($request); + $this->assertEquals(array('post/view', array('id' => '123', 'title' => 'this+is+sample')), $result); + // matching pathinfo PUT/POST request + $_SERVER['REQUEST_METHOD'] = 'PUT'; + $request->pathInfo = 'post/123/this+is+sample'; + $result = $manager->parseRequest($request); + $this->assertEquals(array('post/create', array('id' => '123', 'title' => 'this+is+sample')), $result); + $_SERVER['REQUEST_METHOD'] = 'POST'; + $request->pathInfo = 'post/123/this+is+sample'; + $result = $manager->parseRequest($request); + $this->assertEquals(array('post/create', array('id' => '123', 'title' => 'this+is+sample')), $result); + + // no wrong matching + $_SERVER['REQUEST_METHOD'] = 'POST'; + $request->pathInfo = 'POST/GET'; + $result = $manager->parseRequest($request); + $this->assertEquals(array('post/get', array()), $result); + + // createUrl should ignore REST rules + $this->mockApplication(array( + 'components' => array( + 'request' => array( + 'hostInfo' => 'http://localhost/', + 'baseUrl' => '/app' + ) + ) + ), \yii\web\Application::className()); + $this->assertEquals('/app/post/delete?id=123', $manager->createUrl('post/delete', array('id' => 123))); + $this->destroyApplication(); + + unset($_SERVER['REQUEST_METHOD']); + } }