diff --git a/docs/guide/runtime-routing.md b/docs/guide/runtime-routing.md index 2647d3a938..5883e6ed85 100644 --- a/docs/guide/runtime-routing.md +++ b/docs/guide/runtime-routing.md @@ -43,7 +43,7 @@ The [[yii\web\UrlManager|URL manager]] supports two URL formats: - the default URL format; - the pretty URL format. -The default URL format uses a query parameter named `r` to represent the route and normal query parameters +The default URL format uses a [[yii\web\UrlManager::$routeParam|query parameter]] named `r` to represent the route and normal query parameters to represent the query parameters associated with the route. For example, the URL `/index.php?r=post/view&id=100` represents the route `post/view` and the `id` query parameter `100`. The default URL format does not require any configuration of the [[yii\web\UrlManager|URL manager]] and works in any Web server setup. diff --git a/framework/web/UrlManager.php b/framework/web/UrlManager.php index 81faf689f5..694cbcaaf2 100644 --- a/framework/web/UrlManager.php +++ b/framework/web/UrlManager.php @@ -32,6 +32,9 @@ use yii\helpers\Url; * ] * ``` * + * Rules are classes implementing the [[UrlRuleInterface]], by default that is [[UrlRule]]. + * For nesting rules, there is also a [[GroupUrlRule]] class. + * * For more details and usage information on UrlManager, see the [guide article on routing](guide:runtime-routing). * * @property string $baseUrl The base URL that is used by [[createUrl()]] to prepend to created URLs. @@ -542,7 +545,7 @@ class UrlManager extends Component /** * Returns the host info that is used by [[createAbsoluteUrl()]] to prepend to created URLs. - * @return string the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend to created URLs. + * @return string the host info (e.g. `http://www.example.com`) that is used by [[createAbsoluteUrl()]] to prepend to created URLs. * @throws InvalidConfigException if running in console application and [[hostInfo]] is not configured. */ public function getHostInfo() diff --git a/framework/web/UrlRule.php b/framework/web/UrlRule.php index a36e42ae1e..b4417f3f70 100644 --- a/framework/web/UrlRule.php +++ b/framework/web/UrlRule.php @@ -67,7 +67,7 @@ class UrlRule extends Object implements UrlRuleInterface /** * @var string the URL suffix used for this rule. * For example, ".html" can be used so that the URL looks like pointing to a static HTML page. - * If not, the value of [[UrlManager::suffix]] will be used. + * If not set, the value of [[UrlManager::suffix]] will be used. */ public $suffix; /** diff --git a/tests/framework/web/UrlManagerCreateUrlTest.php b/tests/framework/web/UrlManagerCreateUrlTest.php new file mode 100644 index 0000000000..528aab483d --- /dev/null +++ b/tests/framework/web/UrlManagerCreateUrlTest.php @@ -0,0 +1,566 @@ + value + * - array config + * + * The following features are tested: + * - route only Url::to(['post/index']); + * - with params Url::to(['post/view', 'id' => 100]); + * - with anchor Url::to(['post/view', 'id' => 100, '#' => 'content']); + * - named parameters + * - as query params + * - as controller/actions '//' => '/', + * - Rules with Server Names + * - with protocol (TODO) + * - without protocol i.e protocol relative, see https://github.com/yiisoft/yii2/pull/12697 (TODO) + * - with parameters + * - with suffix + * - with default values + * - with HTTP methods (TODO) + * - absolute/relative + * + * - Adding rules dynamically (TODO) + * - Test custom rules that only implement the interface (TODO) + * + * NOTE: if a test is added here, you probably also need to add one in UrlManagerParseUrlTest. + * + * @group web + */ +class UrlManagerCreateUrlTest extends TestCase +{ + protected function getUrlManager($config = [], $showScriptName = true) + { + // in this test class, all tests have enablePrettyUrl enabled. + $config['enablePrettyUrl'] = true; + $config['cache'] = null; + + // set default values if they are not set + $config = array_merge([ + 'baseUrl' => '', + 'scriptUrl' => '/index.php', + 'hostInfo' => 'http://www.example.com', + 'showScriptName' => $showScriptName, + ], $config); + + return new UrlManager($config); + } + + + public function variationsProvider() + { + $baseUrlConfig = [ + 'baseUrl' => '/test', + 'scriptUrl' => '/test/index.php', + ]; + + return [ + // method name, $showScriptName, expected URL prefix + ['createUrl', true, '/index.php', []], + ['createUrl', false, '', []], + ['createAbsoluteUrl', true, 'http://www.example.com/index.php', []], + ['createAbsoluteUrl', false, 'http://www.example.com', []], + + // with different baseUrl + ['createUrl', true, '/test/index.php', $baseUrlConfig], + ['createUrl', false, '/test', $baseUrlConfig], + ['createAbsoluteUrl', true, 'http://www.example.com/test/index.php', $baseUrlConfig], + ['createAbsoluteUrl', false, 'http://www.example.com/test', $baseUrlConfig], + ]; + } + + /** + * Test createUrl() and createAbsoluteUrl() + * with varying $showScriptName + * without rules. + * + * @dataProvider variationsProvider + */ + public function testWithoutRules($method, $showScriptName, $prefix, $config) + { + $manager = $this->getUrlManager($config, $showScriptName); + + $url = $manager->$method('post/view'); + $this->assertEquals("$prefix/post/view", $url); + $url = $manager->$method(['post/view']); + $this->assertEquals("$prefix/post/view", $url); + + $url = $manager->$method(['post/view', 'id' => 1, 'title' => 'sample post']); + $this->assertEquals("$prefix/post/view?id=1&title=sample+post", $url); + + $url = $manager->$method(['post/view', '#' => 'testhash']); + $this->assertEquals("$prefix/post/view#testhash", $url); + + $url = $manager->$method(['post/view', 'id' => 1, 'title' => 'sample post', '#' => 'testhash']); + $this->assertEquals("$prefix/post/view?id=1&title=sample+post#testhash", $url); + + // with defaultAction + $url = $manager->$method(['/post', 'page' => 1]); + $this->assertEquals("$prefix/post?page=1", $url); + } + + /** + * Test createUrl() and createAbsoluteUrl() + * with varying $showScriptName + * without rules. + * With UrlManager::$suffix + * + * @dataProvider variationsProvider + */ + public function testWithoutRulesWithSuffix($method, $showScriptName, $prefix, $config) + { + $config['suffix'] = '.html'; + $manager = $this->getUrlManager($config, $showScriptName); + + $url = $manager->$method('post/view'); + $this->assertEquals("$prefix/post/view.html", $url); + $url = $manager->$method(['post/view']); + $this->assertEquals("$prefix/post/view.html", $url); + + $url = $manager->$method(['post/view', 'id' => 1, 'title' => 'sample post']); + $this->assertEquals("$prefix/post/view.html?id=1&title=sample+post", $url); + + $url = $manager->$method(['post/view', '#' => 'testhash']); + $this->assertEquals("$prefix/post/view.html#testhash", $url); + + $url = $manager->$method(['post/view', 'id' => 1, 'title' => 'sample post', '#' => 'testhash']); + $this->assertEquals("$prefix/post/view.html?id=1&title=sample+post#testhash", $url); + + // with defaultAction + $url = $manager->$method(['/post', 'page' => 1]); + $this->assertEquals("$prefix/post.html?page=1", $url); + + + // test suffix '/' as it may be trimmed + $config['suffix'] = '/'; + $manager = $this->getUrlManager($config, $showScriptName); + + $url = $manager->$method('post/view'); + $this->assertEquals("$prefix/post/view/", $url); + $url = $manager->$method(['post/view']); + $this->assertEquals("$prefix/post/view/", $url); + + $url = $manager->$method(['post/view', 'id' => 1, 'title' => 'sample post']); + $this->assertEquals("$prefix/post/view/?id=1&title=sample+post", $url); + + $url = $manager->$method(['post/view', '#' => 'testhash']); + $this->assertEquals("$prefix/post/view/#testhash", $url); + + $url = $manager->$method(['post/view', 'id' => 1, 'title' => 'sample post', '#' => 'testhash']); + $this->assertEquals("$prefix/post/view/?id=1&title=sample+post#testhash", $url); + + // with defaultAction + $url = $manager->$method(['/post', 'page' => 1]); + $this->assertEquals("$prefix/post/?page=1", $url); + } + + /** + * Test createUrl() and createAbsoluteUrl() + * with varying $showScriptName + * with simple rules. + * + * @dataProvider variationsProvider + */ + public function testSimpleRules($method, $showScriptName, $prefix, $config) + { + $config['rules'] = [ + 'post/' => 'post/view', + 'posts' => 'post/index', + 'book//' => 'book/view', + ]; + $manager = $this->getUrlManager($config, $showScriptName); + + // does not match any rule + $url = $manager->$method(['post/view']); + $this->assertEquals("$prefix/post/view", $url); + + // with defaultAction also does not match any rule + $url = $manager->$method(['/post', 'page' => 1]); + $this->assertEquals("$prefix/post?page=1", $url); + + // match first rule + $url = $manager->$method(['post/view', 'id' => 1]); + $this->assertEquals("$prefix/post/1", $url); + + // match first rule with additional param + $url = $manager->$method(['post/view', 'id' => 1, 'title' => 'sample post']); + $this->assertEquals("$prefix/post/1?title=sample+post", $url); + // match first rule with hash + $url = $manager->$method(['post/view', 'id' => 1, '#' => 'testhash']); + $this->assertEquals("$prefix/post/1#testhash", $url); + + // match second rule + $url = $manager->$method(['post/index']); + $this->assertEquals("$prefix/posts", $url); + + // match second rule with additional param + $url = $manager->$method(['post/index', 'category' => 'test']); + $this->assertEquals("$prefix/posts?category=test", $url); + + // match third rule, ensure encoding of params + $url = $manager->$method(['book/view', 'id' => 1, 'title' => 'sample post']); + $this->assertEquals("$prefix/book/1/sample+post", $url); + } + + /** + * Test createUrl() and createAbsoluteUrl() + * with varying $showScriptName + * with simple rules. + * With UrlManager::$suffix + * + * @dataProvider variationsProvider + */ + public function testSimpleRulesWithSuffix($method, $showScriptName, $prefix, $config) + { + $config['rules'] = [ + 'post/<id:\d+>' => 'post/view', + 'posts' => 'post/index', + 'book/<id:\d+>/<title>' => 'book/view', + ]; + $config['suffix'] = '/'; + $manager = $this->getUrlManager($config, $showScriptName); + + // does not match any rule + $url = $manager->$method(['post/view']); + $this->assertEquals("$prefix/post/view/", $url); + + // with defaultAction also does not match any rule + $url = $manager->$method(['/post', 'page' => 1]); + $this->assertEquals("$prefix/post/?page=1", $url); + + // match first rule + $url = $manager->$method(['post/view', 'id' => 1]); + $this->assertEquals("$prefix/post/1/", $url); + + // match first rule with additional param + $url = $manager->$method(['post/view', 'id' => 1, 'title' => 'sample post']); + $this->assertEquals("$prefix/post/1/?title=sample+post", $url); + // match first rule with hash + $url = $manager->$method(['post/view', 'id' => 1, '#' => 'testhash']); + $this->assertEquals("$prefix/post/1/#testhash", $url); + + // match second rule + $url = $manager->$method(['post/index']); + $this->assertEquals("$prefix/posts/", $url); + + // match second rule with additional param + $url = $manager->$method(['post/index', 'category' => 'test']); + $this->assertEquals("$prefix/posts/?category=test", $url); + + // match third rule, ensure encoding of params + $url = $manager->$method(['book/view', 'id' => 1, 'title' => 'sample post']); + $this->assertEquals("$prefix/book/1/sample+post/", $url); + } + + /** + * Test createUrl() and createAbsoluteUrl() + * with varying $showScriptName + * with rules that have varadic controller/actions. + * + * @dataProvider variationsProvider + */ + public function testControllerActionParams($method, $showScriptName, $prefix, $config) + { + $config['rules'] = [ + '<controller>/<id:\d+>' => '<controller>/view', + '<controller>s' => '<controller>/index', + '<controller>/default' => '<controller>', // rule to match default action + '<controller>/test/<action:\w+>' => '<controller>/<action>', + ]; + $manager = $this->getUrlManager($config, $showScriptName); + + // match last rule + $url = $manager->$method(['post/view']); + $this->assertEquals("$prefix/post/test/view", $url); + + // defaultAction should match third rule + $url = $manager->$method(['/post/']); + $this->assertEquals("$prefix/post/default", $url); + $url = $manager->$method(['/post']); + $this->assertEquals("$prefix/post/default", $url); + + // match first rule + $url = $manager->$method(['post/view', 'id' => 1]); + $this->assertEquals("$prefix/post/1", $url); + + // match first rule with additional param + $url = $manager->$method(['post/view', 'id' => 1, 'title' => 'sample post']); + $this->assertEquals("$prefix/post/1?title=sample+post", $url); + // match first rule with hash + $url = $manager->$method(['post/view', 'id' => 1, '#' => 'testhash']); + $this->assertEquals("$prefix/post/1#testhash", $url); + + // match second rule + $url = $manager->$method(['post/index']); + $this->assertEquals("$prefix/posts", $url); + + // match second rule with additional param + $url = $manager->$method(['post/index', 'category' => 'test']); + $this->assertEquals("$prefix/posts?category=test", $url); + } + + /** + * Test createUrl() and createAbsoluteUrl() + * with varying $showScriptName + * with rules that have default values for parameters. + * + * @dataProvider variationsProvider + */ + public function testRulesWithDefaultParams($method, $showScriptName, $prefix, $config) + { + $config['rules'] = [ + [ + 'pattern' => '', + 'route' => 'frontend/page/view', + 'defaults' => ['slug' => 'index'], + ], + 'page/<slug>' => 'frontend/page/view', + ]; + $manager = $this->getUrlManager($config, $showScriptName); + + // match first rule + $url = $manager->$method(['frontend/page/view', 'slug' => 'index']); + $this->assertEquals("$prefix/", $url); + + // match first rule with additional param + $url = $manager->$method(['frontend/page/view', 'slug' => 'index', 'sort' => 'name']); + $this->assertEquals("$prefix/?sort=name", $url); + + // match first rule with hash + $url = $manager->$method(['frontend/page/view', 'slug' => 'index', '#' => 'testhash']); + $this->assertEquals("$prefix/#testhash", $url); + + // match second rule + $url = $manager->$method(['frontend/page/view', 'slug' => 'services']); + $this->assertEquals("$prefix/page/services", $url); + + // match second rule with additional param + $url = $manager->$method(['frontend/page/view', 'slug' => 'services', 'sort' => 'name']); + $this->assertEquals("$prefix/page/services?sort=name", $url); + + // match second rule with hash + $url = $manager->$method(['frontend/page/view', 'slug' => 'services', '#' => 'testhash']); + $this->assertEquals("$prefix/page/services#testhash", $url); + + // matches none of the rules + $url = $manager->$method(['frontend/page/view']); + $this->assertEquals("$prefix/frontend/page/view", $url); + } + + /** + * Test createUrl() and createAbsoluteUrl() + * with varying $showScriptName + * with empty or null parameters. + * + * @dataProvider variationsProvider + * @see https://github.com/yiisoft/yii2/issues/10935 + */ + public function testWithNullParams($method, $showScriptName, $prefix, $config) + { + $config['rules'] = [ + '<param1>/<param2>' => 'site/index', + '<param1>' => 'site/index', + ]; + $manager = $this->getUrlManager($config, $showScriptName); + + // match first rule + $url = $manager->$method(['site/index', 'param1' => 111, 'param2' => 222]); + $this->assertEquals("$prefix/111/222", $url); + $url = $manager->$method(['site/index', 'param1' => 112, 'param2' => 222]); + $this->assertEquals("$prefix/112/222", $url); + + // match second rule + $url = $manager->$method(['site/index', 'param1' => 111, 'param2' => null]); + $this->assertEquals("$prefix/111", $url); + $url = $manager->$method(['site/index', 'param1' => 123, 'param2' => null]); + $this->assertEquals("$prefix/123", $url); + + // match none of the rules + $url = $manager->$method(['site/index', 'param1' => null, 'param2' => 111]); + $this->assertEquals("$prefix/site/index?param2=111", $url); + $url = $manager->$method(['site/index', 'param1' => null, 'param2' => 123]); + $this->assertEquals("$prefix/site/index?param2=123", $url); + } + + + /** + * Test createUrl() and createAbsoluteUrl() + * with varying $showScriptName + * with empty pattern. + * + * @dataProvider variationsProvider + * @see https://github.com/yiisoft/yii2/issues/6717 + */ + public function testWithEmptyPattern($method, $showScriptName, $prefix, $config) + { + $assertations = function($manager) use ($method, $prefix) { + // match first rule + $url = $manager->$method(['front/site/index']); + $this->assertEquals("$prefix/", $url); + $url = $manager->$method(['/front/site/index']); + $this->assertEquals("$prefix/", $url); + + // match first rule with additional parameter + $url = $manager->$method(['front/site/index', 'page' => 1]); + $this->assertEquals("$prefix/?page=1", $url); + $url = $manager->$method(['/front/site/index', 'page' => 1]); + $this->assertEquals("$prefix/?page=1", $url); + }; + + // normal rule + $config['rules'] = [ + '' => 'front/site/index', + ]; + $manager = $this->getUrlManager($config, $showScriptName); + $assertations($manager); + + // rule prefixed with / + $config['rules'] = [ + '' => '/front/site/index', + ]; + $manager = $this->getUrlManager($config, $showScriptName); + $assertations($manager); + + // with suffix + $config['rules'] = [ + '' => 'front/site/index', + ]; + $config['suffix'] = '/'; + $manager = $this->getUrlManager($config, $showScriptName); + $assertations($manager); + } + + + public function absolutePatternsVariations() + { + $baseUrlConfig = [ + 'baseUrl' => '/test', + 'scriptUrl' => '/test/index.php', + ]; + + return [ + // $showScriptName, expected URL prefix + [true, '/index.php', []], + [false, '', []], + + // with different baseUrl + [true, '/test/index.php', $baseUrlConfig], + [false, '/test', $baseUrlConfig], + ]; + } + + /** + * Test rules that have host info in the patterns. + * @dataProvider absolutePatternsVariations + */ + public function testAbsolutePatterns($showScriptName, $prefix, $config) + { + $config['rules'] = [ + [ + 'pattern' => 'post/<id>/<title>', + 'route' => 'post/view', + 'host' => 'http://<lang:en|fr>.example.com', // TODO variation of scheme https://github.com/yiisoft/yii2/issues/12691 + ], + // note: baseUrl is not included in the pattern + 'http://www.example.com/login' => 'site/login', + ]; + $manager = $this->getUrlManager($config, $showScriptName); + // first rule matches + $urlParams = ['post/view', 'id' => 1, 'title' => 'sample post', 'lang' => 'en']; + $expected = "http://en.example.com$prefix/post/1/sample+post"; + $this->assertEquals($expected, $manager->createUrl($urlParams)); + $this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams)); + $this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams, true)); + $this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams, 'http')); + $this->assertEquals('https' . substr($expected, 4), $manager->createAbsoluteUrl($urlParams, 'https')); + $this->assertEquals(substr($expected, 5), $manager->createAbsoluteUrl($urlParams, '')); // protocol relative Url + + $urlParams = ['post/view', 'id' => 1, 'title' => 'sample post', 'lang' => 'en', '#' => 'testhash']; + $expected = "http://en.example.com$prefix/post/1/sample+post#testhash"; + $this->assertEquals($expected, $manager->createUrl($urlParams)); + $this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams)); + + // second rule matches + $urlParams = ['site/login']; + $expected = "http://www.example.com$prefix/login"; + $this->assertEquals($expected, $manager->createUrl($urlParams)); + $this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams)); + $this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams, true)); + $this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams, 'http')); + $this->assertEquals('https' . substr($expected, 4), $manager->createAbsoluteUrl($urlParams, 'https')); + $this->assertEquals(substr($expected, 5), $manager->createAbsoluteUrl($urlParams, '')); // protocol relative Url + + // none of the rules matches + $urlParams = ['post/index', 'page' => 1]; + $this->assertEquals("$prefix/post/index?page=1", $manager->createUrl($urlParams)); + $expected = "http://www.example.com$prefix/post/index?page=1"; + $this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams)); + $this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams, true)); + $this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams, 'http')); + $this->assertEquals('https' . substr($expected, 4), $manager->createAbsoluteUrl($urlParams, 'https')); + $this->assertEquals(substr($expected, 5), $manager->createAbsoluteUrl($urlParams, '')); // protocol relative Url + + $urlParams = ['post/index', 'page' => 1, '#' => 'testhash']; + $this->assertEquals("$prefix/post/index?page=1#testhash", $manager->createUrl($urlParams)); + $expected = "http://www.example.com$prefix/post/index?page=1#testhash"; + $this->assertEquals($expected, $manager->createAbsoluteUrl($urlParams)); + + } + + public function multipleHostsRulesDataProvider() + { + return [ + ['http://example.com'], + ['https://example.com'], + ['http://example.fr'], + ['https://example.fr'], + ]; + } + + /** + * Test matching of Url rules dependent on the current host info + * + * @dataProvider multipleHostsRulesDataProvider + * @see https://github.com/yiisoft/yii2/issues/7948 + */ + public function testMultipleHostsRules($host) + { + $manager = new UrlManager([ + 'enablePrettyUrl' => true, + 'cache' => null, + 'rules' => [ + ['host' => 'http://example.com', 'pattern' => '<slug:(search)>', 'route' => 'products/search', 'defaults' => ['lang' => 'en']], + ['host' => 'http://example.fr', 'pattern' => '<slug:(search)>', 'route' => 'products/search', 'defaults' => ['lang' => 'fr']], + ], + 'hostInfo' => $host, + 'baseUrl' => '/', + 'scriptUrl' => '', + ]); + $url = $manager->createAbsoluteUrl(['products/search', 'lang' => 'en', 'slug' => 'search'], 'https'); + $this->assertEquals('https://example.com/search', $url); + $url = $manager->createUrl(['products/search', 'lang' => 'en', 'slug' => 'search']); + $this->assertEquals('http://example.com/search', $url); + $url = $manager->createUrl(['products/search', 'lang' => 'en', 'slug' => 'search', 'param1' => 'value1']); + $this->assertEquals('http://example.com/search?param1=value1', $url); + $url = $manager->createAbsoluteUrl(['products/search', 'lang' => 'fr', 'slug' => 'search'], 'https'); + $this->assertEquals('https://example.fr/search', $url); + $url = $manager->createUrl(['products/search', 'lang' => 'fr', 'slug' => 'search']); + $this->assertEquals('http://example.fr/search', $url); + $url = $manager->createUrl(['products/search', 'lang' => 'fr', 'slug' => 'search', 'param1' => 'value1']); + $this->assertEquals('http://example.fr/search?param1=value1', $url); + } + +} diff --git a/tests/framework/web/UrlManagerParseUrlTest.php b/tests/framework/web/UrlManagerParseUrlTest.php new file mode 100644 index 0000000000..805b5c20d1 --- /dev/null +++ b/tests/framework/web/UrlManagerParseUrlTest.php @@ -0,0 +1,343 @@ +<?php + +namespace yiiunit\framework\web; + +use yii\web\Request; +use yii\web\UrlManager; +use yiiunit\TestCase; + +/** + * This class implements the tests for URL parsing with "pretty" url format. + * + * See [[UrlManagerTest]] for tests with "default" URL format. + * See [[UrlManagerCreateUrlTest]] for url creation with "pretty" URL format. + * + * Behavior of UrlManager::parseRequest() for the "pretty" URL format varies among the following options: + * - strict parsing = true / false + * - rules format + * - key => value + * - array config + * + * The following features are tested: + * - named parameters + * - as query params + * - as controller/actions '<controller:(post|comment)>/<id:\d+>/<action:(update|delete)>' => '<controller>/<action>', + * - Rules with Server Names + * - with protocol + * - without protocol i.e protocol relative, see https://github.com/yiisoft/yii2/pull/12697 + * - with parameters + * - with suffix + * - with default values + * - with HTTP methods + * + * - Adding rules dynamically + * - Test custom rules that only implement the interface + * + * NOTE: if a test is added here, you probably also need to add one in UrlManagerCreateUrlTest. + * + * @group web + */ +class UrlManagerParseUrlTest extends TestCase +{ + protected function getUrlManager($config = []) + { + // in this test class, all tests have enablePrettyUrl enabled. + $config['enablePrettyUrl'] = true; + $config['cache'] = null; + // normalizer is tested in UrlNormalizerTest + $config['normalizer'] = false; + + return new UrlManager($config); + } + + protected function getRequest($pathInfo, $hostInfo = 'http://www.example.com', $method = 'GET', $config = []) + { + $config['pathInfo'] = $pathInfo; + $config['hostInfo'] = $hostInfo; + $_POST['_method'] = $method; + return new Request($config); + } + + protected function tearDown() + { + unset($_POST['_method']); + parent::tearDown(); + } + + public function testWithoutRules() + { + $manager = $this->getUrlManager(); + + // empty pathinfo + $result = $manager->parseRequest($this->getRequest('')); + $this->assertEquals(['', []], $result); + // normal pathinfo + $result = $manager->parseRequest($this->getRequest('site/index')); + $this->assertEquals(['site/index', []], $result); + // pathinfo with module + $result = $manager->parseRequest($this->getRequest('module/site/index')); + $this->assertEquals(['module/site/index', []], $result); + // pathinfo with trailing slashes + $result = $manager->parseRequest($this->getRequest('module/site/index/')); + $this->assertEquals(['module/site/index/', []], $result); + } + + public function testWithoutRulesStrict() + { + $manager = $this->getUrlManager(); + $manager->enableStrictParsing = true; + + // empty pathinfo + $this->assertFalse($manager->parseRequest($this->getRequest(''))); + // normal pathinfo + $this->assertFalse($manager->parseRequest($this->getRequest('site/index'))); + // pathinfo with module + $this->assertFalse($manager->parseRequest($this->getRequest('module/site/index'))); + // pathinfo with trailing slashes + $this->assertFalse($manager->parseRequest($this->getRequest('module/site/index/'))); + } + + public function suffixProvider() + { + return [ + ['.html'], + ['/'], + ]; + } + + /** + * @dataProvider suffixProvider + */ + public function testWithoutRulesWithSuffix($suffix) + { + $manager = $this->getUrlManager(['suffix' => $suffix]); + + // empty pathinfo + $result = $manager->parseRequest($this->getRequest('')); + $this->assertEquals(['', []], $result); + // normal pathinfo + $result = $manager->parseRequest($this->getRequest('site/index')); + $this->assertFalse($result); + $result = $manager->parseRequest($this->getRequest("site/index$suffix")); + $this->assertEquals(['site/index', []], $result); + // pathinfo with module + $result = $manager->parseRequest($this->getRequest('module/site/index')); + $this->assertFalse($result); + $result = $manager->parseRequest($this->getRequest("module/site/index$suffix")); + $this->assertEquals(['module/site/index', []], $result); + // pathinfo with trailing slashes + if ($suffix !== '/') { + $result = $manager->parseRequest($this->getRequest('module/site/index/')); + $this->assertFalse($result); + } + $result = $manager->parseRequest($this->getRequest("module/site/index/$suffix")); + $this->assertEquals(['module/site/index/', []], $result); + } + + public function testSimpleRules() + { + $config = [ + 'rules' => [ + 'post/<id:\d+>' => 'post/view', + 'posts' => 'post/index', + 'book/<id:\d+>/<title>' => 'book/view', + ], + ]; + $manager = $this->getUrlManager($config); + + // matching pathinfo + $result = $manager->parseRequest($this->getRequest('book/123/this+is+sample')); + $this->assertEquals(['book/view', ['id' => '123', 'title' => 'this+is+sample']], $result); + // trailing slash is significant, no match + $result = $manager->parseRequest($this->getRequest('book/123/this+is+sample/')); + $this->assertEquals(['book/123/this+is+sample/', []], $result); + // empty pathinfo + $result = $manager->parseRequest($this->getRequest('')); + $this->assertEquals(['', []], $result); + // normal pathinfo + $result = $manager->parseRequest($this->getRequest('site/index')); + $this->assertEquals(['site/index', []], $result); + // pathinfo with module + $result = $manager->parseRequest($this->getRequest('module/site/index')); + $this->assertEquals(['module/site/index', []], $result); + } + + public function testSimpleRulesStrict() + { + $config = [ + 'rules' => [ + 'post/<id:\d+>' => 'post/view', + 'posts' => 'post/index', + 'book/<id:\d+>/<title>' => 'book/view', + ], + ]; + $manager = $this->getUrlManager($config); + $manager->enableStrictParsing = true; + + // matching pathinfo + $result = $manager->parseRequest($this->getRequest('book/123/this+is+sample')); + $this->assertEquals(['book/view', ['id' => '123', 'title' => 'this+is+sample']], $result); + // trailing slash is significant, no match + $result = $manager->parseRequest($this->getRequest('book/123/this+is+sample/')); + $this->assertFalse($result); + // empty pathinfo + $result = $manager->parseRequest($this->getRequest('')); + $this->assertFalse($result); + // normal pathinfo + $result = $manager->parseRequest($this->getRequest('site/index')); + $this->assertFalse($result); + // pathinfo with module + $result = $manager->parseRequest($this->getRequest('module/site/index')); + $this->assertFalse($result); + } + + /** + * @dataProvider suffixProvider + */ + public function testSimpleRulesWithSuffix($suffix) + { + $config = [ + 'rules' => [ + 'post/<id:\d+>' => 'post/view', + 'posts' => 'post/index', + 'book/<id:\d+>/<title>' => 'book/view', + ], + 'suffix' => $suffix, + ]; + $manager = $this->getUrlManager($config); + + // matching pathinfo + $result = $manager->parseRequest($this->getRequest("book/123/this+is+sample")); + $this->assertFalse($result); + $result = $manager->parseRequest($this->getRequest("book/123/this+is+sample$suffix")); + $this->assertEquals(['book/view', ['id' => '123', 'title' => 'this+is+sample']], $result); + // trailing slash is significant, no match + $result = $manager->parseRequest($this->getRequest("book/123/this+is+sample/")); + if ($suffix === '/') { + $this->assertEquals(['book/view', ['id' => '123', 'title' => 'this+is+sample']], $result); + } else { + $this->assertFalse($result); + } + $result = $manager->parseRequest($this->getRequest("book/123/this+is+sample/$suffix")); + $this->assertEquals(['book/123/this+is+sample/', []], $result); + // empty pathinfo + $result = $manager->parseRequest($this->getRequest('')); + $this->assertEquals(['', []], $result); + // normal pathinfo + $result = $manager->parseRequest($this->getRequest("site/index")); + $this->assertFalse($result); + $result = $manager->parseRequest($this->getRequest("site/index$suffix")); + $this->assertEquals(['site/index', []], $result); + // pathinfo with module + $result = $manager->parseRequest($this->getRequest("module/site/index")); + $this->assertFalse($result); + $result = $manager->parseRequest($this->getRequest("module/site/index$suffix")); + $this->assertEquals(['module/site/index', []], $result); + } + + /** + * @dataProvider suffixProvider + */ + public function testSimpleRulesWithSuffixStrict($suffix) + { + $config = [ + 'rules' => [ + 'post/<id:\d+>' => 'post/view', + 'posts' => 'post/index', + 'book/<id:\d+>/<title>' => 'book/view', + ], + 'suffix' => $suffix, + ]; + $manager = $this->getUrlManager($config); + $manager->enableStrictParsing = true; + + // matching pathinfo + $result = $manager->parseRequest($this->getRequest("book/123/this+is+sample")); + $this->assertFalse($result); + $result = $manager->parseRequest($this->getRequest("book/123/this+is+sample$suffix")); + $this->assertEquals(['book/view', ['id' => '123', 'title' => 'this+is+sample']], $result); + // trailing slash is significant, no match + $result = $manager->parseRequest($this->getRequest("book/123/this+is+sample/")); + if ($suffix === '/') { + $this->assertEquals(['book/view', ['id' => '123', 'title' => 'this+is+sample']], $result); + } else { + $this->assertFalse($result); + } + $result = $manager->parseRequest($this->getRequest("book/123/this+is+sample/$suffix")); + $this->assertFalse($result); + // empty pathinfo + $result = $manager->parseRequest($this->getRequest('')); + $this->assertFalse($result); + // normal pathinfo + $result = $manager->parseRequest($this->getRequest("site/index")); + $this->assertFalse($result); + $result = $manager->parseRequest($this->getRequest("site/index$suffix")); + $this->assertFalse($result); + // pathinfo with module + $result = $manager->parseRequest($this->getRequest("module/site/index")); + $this->assertFalse($result); + $result = $manager->parseRequest($this->getRequest("module/site/index$suffix")); + $this->assertFalse($result); + } + + + + // TODO implement with hostinfo + + + + public function testParseRESTRequest() + { + $request = new Request; + + // pretty URL rules + $manager = new UrlManager([ + 'enablePrettyUrl' => true, + 'showScriptName' => false, + 'cache' => null, + 'rules' => [ + 'PUT,POST post/<id>/<title>' => '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(['post/view', ['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(['post/create', ['id' => '123', 'title' => 'this+is+sample']], $result); + $_SERVER['REQUEST_METHOD'] = 'POST'; + $request->pathInfo = 'post/123/this+is+sample'; + $result = $manager->parseRequest($request); + $this->assertEquals(['post/create', ['id' => '123', 'title' => 'this+is+sample']], $result); + + // no wrong matching + $_SERVER['REQUEST_METHOD'] = 'POST'; + $request->pathInfo = 'POST/GET'; + $result = $manager->parseRequest($request); + $this->assertEquals(['post/get', []], $result); + + // createUrl should ignore REST rules + $this->mockApplication([ + 'components' => [ + 'request' => [ + 'hostInfo' => 'http://localhost/', + 'baseUrl' => '/app' + ] + ] + ], \yii\web\Application::className()); + $this->assertEquals('/app/post/delete?id=123', $manager->createUrl(['post/delete', 'id' => 123])); + $this->destroyApplication(); + + unset($_SERVER['REQUEST_METHOD']); + } + + + +} \ No newline at end of file diff --git a/tests/framework/web/UrlManagerTest.php b/tests/framework/web/UrlManagerTest.php index 359aca4656..27304f1ca0 100644 --- a/tests/framework/web/UrlManagerTest.php +++ b/tests/framework/web/UrlManagerTest.php @@ -1,547 +1,241 @@ <?php namespace yiiunit\framework\web; +use Yii; use yii\web\Request; use yii\web\UrlManager; use yii\web\UrlNormalizer; use yiiunit\TestCase; /** + * This tests verifies all features provided by UrlManager according to the documentation. + * + * UrlManager has two main operation modes: + * + * - "default" url format, which is the simple case. Tests in this class cover this case. + * Things to be covered in this mode are the following: + * - route only createUrl(['post/index']); + * - with params createUrl(['post/view', 'id' => 100]); + * - with anchor createUrl(['post/view', 'id' => 100, '#' => 'content']); + * Variations here are createUrl and createAbsoluteUrl, where absolute Urls also vary by schema. + * + * - "pretty" url format. This is the complex case, which involves UrlRules and url parsing. + * Url creation for "pretty" url format is covered by [[UrlManagerCreateUrlTest]]. + * Url parsing for "pretty" url format is covered by [[UrlManagerParseUrlTest]]. + * * @group web */ class UrlManagerTest extends TestCase { - protected function setUp() + protected function getUrlManager($config = [], $showScriptName = true, $enableStrictParsing = false) { - parent::setUp(); + // in this test class, all tests have enablePrettyUrl disabled. + $config['enablePrettyUrl'] = false; + $config['cache'] = null; + + // baseUrl should not be used when prettyUrl is disabled + // trigger an exception here in case it gets called + $config['baseUrl'] = null; $this->mockApplication(); + Yii::$app->set('request', function() { + $this->fail('Request component should not be accessed by UrlManager with current settings.'); + }); + + // set default values if they are not set + $config = array_merge([ + 'scriptUrl' => '/index.php', + 'hostInfo' => 'http://www.example.com', + 'showScriptName' => $showScriptName, + 'enableStrictParsing' => $enableStrictParsing, + ], $config); + + return new UrlManager($config); } - public function testCreateUrl() + /** + * $showScriptName and $enableStrictParsing should have no effect in default format. + * Passing these options ensures that. + */ + public function ignoredOptionsProvider() + { + return [ + [false, false], + [true, false], + [false, true], + [true, true], + ]; + } + + /** + * @dataProvider ignoredOptionsProvider + */ + public function testCreateUrlSimple($showScriptName, $enableStrictParsing) { // default setting with '/' as base url - $manager = new UrlManager([ - 'baseUrl' => '/', - 'scriptUrl' => '', - 'cache' => null, - ]); + $manager = $this->getUrlManager([], $showScriptName, $enableStrictParsing); + $url = $manager->createUrl('post/view'); + $this->assertEquals('/index.php?r=post%2Fview', $url); $url = $manager->createUrl(['post/view']); - $this->assertEquals('?r=post%2Fview', $url); - $url = $manager->createUrl(['post/view', 'id' => 1, 'title' => 'sample post']); - $this->assertEquals('?r=post%2Fview&id=1&title=sample+post', $url); + $this->assertEquals('/index.php?r=post%2Fview', $url); // default setting with '/test/' as base url - $manager = new UrlManager([ + $manager = $this->getUrlManager([ 'baseUrl' => '/test/', 'scriptUrl' => '/test', - 'cache' => null, - ]); + ], $showScriptName, $enableStrictParsing); + $url = $manager->createUrl('post/view'); + $this->assertEquals('/test?r=post%2Fview', $url); + $url = $manager->createUrl(['post/view']); + $this->assertEquals('/test?r=post%2Fview', $url); + } + + /** + * @dataProvider ignoredOptionsProvider + */ + public function testCreateUrlWithParams($showScriptName, $enableStrictParsing) + { + // default setting with '/' as base url + $manager = $this->getUrlManager([], $showScriptName, $enableStrictParsing); + $url = $manager->createUrl(['post/view', 'id' => 1, 'title' => 'sample post']); + $this->assertEquals('/index.php?r=post%2Fview&id=1&title=sample+post', $url); + + // default setting with '/test/' as base url + $manager = $this->getUrlManager([ + 'baseUrl' => '/test/', + 'scriptUrl' => '/test', + ], $showScriptName, $enableStrictParsing); $url = $manager->createUrl(['post/view', 'id' => 1, 'title' => 'sample post']); $this->assertEquals('/test?r=post%2Fview&id=1&title=sample+post', $url); + } - // pretty URL without rules - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'baseUrl' => '/', - 'scriptUrl' => '', - 'cache' => null, - ]); - $url = $manager->createUrl(['post/view', 'id' => 1, 'title' => 'sample post']); - $this->assertEquals('/post/view?id=1&title=sample+post', $url); - $manager = new UrlManager([ - 'enablePrettyUrl' => true, + /** + * @dataProvider ignoredOptionsProvider + * + * @see https://github.com/yiisoft/yii2/pull/9596 + */ + public function testCreateUrlWithAnchor($showScriptName, $enableStrictParsing) + { + // default setting with '/' as base url + $manager = $this->getUrlManager([], $showScriptName, $enableStrictParsing); + $url = $manager->createUrl(['post/view', '#' => 'anchor']); + $this->assertEquals('/index.php?r=post%2Fview#anchor', $url); + $url = $manager->createUrl(['post/view', 'id' => 1, 'title' => 'sample post', '#' => 'anchor']); + $this->assertEquals('/index.php?r=post%2Fview&id=1&title=sample+post#anchor', $url); + + // default setting with '/test/' as base url + $manager = $this->getUrlManager([ 'baseUrl' => '/test/', 'scriptUrl' => '/test', - 'cache' => null, - ]); - $url = $manager->createUrl(['post/view', 'id' => 1, 'title' => 'sample post']); - $this->assertEquals('/test/post/view?id=1&title=sample+post', $url); - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'baseUrl' => '/test', - 'scriptUrl' => '/test/index.php', - 'cache' => null, - ]); - $url = $manager->createUrl(['post/view', 'id' => 1, 'title' => 'sample post']); - $this->assertEquals('/test/index.php/post/view?id=1&title=sample+post', $url); - - // test showScriptName - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'baseUrl' => '/test', - 'scriptUrl' => '/test/index.php', - 'showScriptName' => true, - 'cache' => null, - ]); - $url = $manager->createUrl(['post/view', 'id' => 1, 'title' => 'sample post']); - $this->assertEquals('/test/index.php/post/view?id=1&title=sample+post', $url); - $url = $manager->createUrl(['/post/view', 'id' => 1, 'title' => 'sample post']); - $this->assertEquals('/test/index.php/post/view?id=1&title=sample+post', $url); - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'baseUrl' => '/test', - 'scriptUrl' => '/test/index.php', - 'showScriptName' => false, - 'cache' => null, - ]); - $url = $manager->createUrl(['post/view', 'id' => 1, 'title' => 'sample post']); - $this->assertEquals('/test/post/view?id=1&title=sample+post', $url); - $url = $manager->createUrl(['/post/view', 'id' => 1, 'title' => 'sample post']); - $this->assertEquals('/test/post/view?id=1&title=sample+post', $url); - - // pretty URL with rules - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'cache' => null, - 'rules' => [ - [ - 'pattern' => 'post/<id>/<title>', - 'route' => 'post/view', - ], - ], - 'baseUrl' => '/', - 'scriptUrl' => '', - ]); - $url = $manager->createUrl(['post/view', 'id' => 1, 'title' => 'sample post']); - $this->assertEquals('/post/1/sample+post', $url); - $url = $manager->createUrl(['post/index', 'page' => 1]); - $this->assertEquals('/post/index?page=1', $url); - - // rules with defaultAction - $url = $manager->createUrl(['/post', 'page' => 1]); - $this->assertEquals('/post?page=1', $url); - - // pretty URL with rules and suffix - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'cache' => null, - 'rules' => [ - [ - 'pattern' => 'post/<id>/<title>', - 'route' => 'post/view', - ], - ], - 'baseUrl' => '/', - 'scriptUrl' => '', - 'suffix' => '.html', - ]); - $url = $manager->createUrl(['post/view', 'id' => 1, 'title' => 'sample post']); - $this->assertEquals('/post/1/sample+post.html', $url); - $url = $manager->createUrl(['post/index', 'page' => 1]); - $this->assertEquals('/post/index.html?page=1', $url); - - // pretty URL with rules that have host info - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'cache' => null, - 'rules' => [ - [ - 'pattern' => 'post/<id>/<title>', - 'route' => 'post/view', - 'host' => 'http://<lang:en|fr>.example.com', - ], - ], - 'baseUrl' => '/test', - 'scriptUrl' => '/test', - ]); - $url = $manager->createUrl(['post/view', 'id' => 1, 'title' => 'sample post', 'lang' => 'en']); - $this->assertEquals('http://en.example.com/test/post/1/sample+post', $url); - $url = $manager->createUrl(['post/index', 'page' => 1]); - $this->assertEquals('/test/post/index?page=1', $url); - - // create url with the same route but different params/defaults - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'cache' => null, - 'rules' => [ - [ - 'pattern' => '', - 'route' => 'frontend/page/view', - 'defaults' => ['slug' => 'index'], - ], - 'page/<slug>' => 'frontend/page/view', - ], - 'baseUrl' => '/test', - 'scriptUrl' => '/test', - ]); - $url = $manager->createUrl(['frontend/page/view', 'slug' => 'services']); - $this->assertEquals('/test/page/services', $url); - $url = $manager->createUrl(['frontend/page/view', 'slug' => 'index']); - $this->assertEquals('/test/', $url); + ], $showScriptName, $enableStrictParsing); + $url = $manager->createUrl(['post/view', '#' => 'anchor']); + $this->assertEquals('/test?r=post%2Fview#anchor', $url); + $url = $manager->createUrl(['post/view', 'id' => 1, 'title' => 'sample post', '#' => 'anchor']); + $this->assertEquals('/test?r=post%2Fview&id=1&title=sample+post#anchor', $url); } /** - * @depends testCreateUrl - * @see https://github.com/yiisoft/yii2/issues/10935 + * @dataProvider ignoredOptionsProvider */ - public function testCreateUrlWithNullParams() + public function testCreateAbsoluteUrl($showScriptName, $enableStrictParsing) { - $manager = new UrlManager([ - 'rules' => [ - '<param1>/<param2>' => 'site/index', - '<param1>' => 'site/index', - ], - 'enablePrettyUrl' => true, - 'scriptUrl' => '/test', + $manager = $this->getUrlManager([], $showScriptName, $enableStrictParsing); + $url = $manager->createAbsoluteUrl('post/view'); + $this->assertEquals('http://www.example.com/index.php?r=post%2Fview', $url); + $url = $manager->createAbsoluteUrl(['post/view']); + $this->assertEquals('http://www.example.com/index.php?r=post%2Fview', $url); - ]); - $this->assertEquals('/test/111', $manager->createUrl(['site/index', 'param1' => 111, 'param2' => null])); - $this->assertEquals('/test/123', $manager->createUrl(['site/index', 'param1' => 123, 'param2' => null])); - $this->assertEquals('/test/111/222', $manager->createUrl(['site/index', 'param1' => 111, 'param2' => 222])); - $this->assertEquals('/test/112/222', $manager->createUrl(['site/index', 'param1' => 112, 'param2' => 222])); - } + $url = $manager->createAbsoluteUrl('post/view', true); + $this->assertEquals('http://www.example.com/index.php?r=post%2Fview', $url); + $url = $manager->createAbsoluteUrl(['post/view'], true); + $this->assertEquals('http://www.example.com/index.php?r=post%2Fview', $url); - /** - * https://github.com/yiisoft/yii2/issues/6717 - */ - public function testCreateUrlWithEmptyPattern() - { - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'cache' => null, - 'rules' => [ - '' => 'front/site/index', - ], - 'baseUrl' => '/', - 'scriptUrl' => '', - ]); - $url = $manager->createUrl(['front/site/index']); - $this->assertEquals('/', $url); - $url = $manager->createUrl(['/front/site/index']); - $this->assertEquals('/', $url); - $url = $manager->createUrl(['front/site/index', 'page' => 1]); - $this->assertEquals('/?page=1', $url); - $url = $manager->createUrl(['/front/site/index', 'page' => 1]); - $this->assertEquals('/?page=1', $url); + $url = $manager->createAbsoluteUrl('post/view', 'http'); + $this->assertEquals('http://www.example.com/index.php?r=post%2Fview', $url); + $url = $manager->createAbsoluteUrl(['post/view'], 'http'); + $this->assertEquals('http://www.example.com/index.php?r=post%2Fview', $url); - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'cache' => null, - 'rules' => [ - '' => '/front/site/index', - ], - 'baseUrl' => '/', - 'scriptUrl' => '', - ]); - $url = $manager->createUrl(['front/site/index']); - $this->assertEquals('/', $url); - $url = $manager->createUrl(['/front/site/index']); - $this->assertEquals('/', $url); - $url = $manager->createUrl(['front/site/index', 'page' => 1]); - $this->assertEquals('/?page=1', $url); - $url = $manager->createUrl(['/front/site/index', 'page' => 1]); - $this->assertEquals('/?page=1', $url); - } + $url = $manager->createAbsoluteUrl('post/view', 'https'); + $this->assertEquals('https://www.example.com/index.php?r=post%2Fview', $url); + $url = $manager->createAbsoluteUrl(['post/view'], 'https'); + $this->assertEquals('https://www.example.com/index.php?r=post%2Fview', $url); - public function testCreateAbsoluteUrl() - { - $manager = new UrlManager([ - 'baseUrl' => '/', - 'scriptUrl' => '', - 'hostInfo' => 'http://www.example.com', - 'cache' => null, - ]); - $url = $manager->createAbsoluteUrl(['post/view', 'id' => 1, 'title' => 'sample post']); - $this->assertEquals('http://www.example.com?r=post%2Fview&id=1&title=sample+post', $url); - - $url = $manager->createAbsoluteUrl(['post/view', 'id' => 1, 'title' => 'sample post'], 'https'); - $this->assertEquals('https://www.example.com?r=post%2Fview&id=1&title=sample+post', $url); - - $url = $manager->createAbsoluteUrl(['post/view', 'id' => 1, 'title' => 'sample post'], ''); - $this->assertEquals('//www.example.com?r=post%2Fview&id=1&title=sample+post', $url); + $url = $manager->createAbsoluteUrl('post/view', ''); + $this->assertEquals('//www.example.com/index.php?r=post%2Fview', $url); + $url = $manager->createAbsoluteUrl(['post/view'], ''); + $this->assertEquals('//www.example.com/index.php?r=post%2Fview', $url); $manager->hostInfo = 'https://www.example.com'; + $url = $manager->createAbsoluteUrl(['post/view', 'id' => 1, 'title' => 'sample post']); + $this->assertEquals('https://www.example.com/index.php?r=post%2Fview&id=1&title=sample+post', $url); + + $url = $manager->createAbsoluteUrl(['post/view', 'id' => 1, 'title' => 'sample post'], 'https'); + $this->assertEquals('https://www.example.com/index.php?r=post%2Fview&id=1&title=sample+post', $url); + $url = $manager->createAbsoluteUrl(['post/view', 'id' => 1, 'title' => 'sample post'], 'http'); - $this->assertEquals('http://www.example.com?r=post%2Fview&id=1&title=sample+post', $url); + $this->assertEquals('http://www.example.com/index.php?r=post%2Fview&id=1&title=sample+post', $url); $url = $manager->createAbsoluteUrl(['post/view', 'id' => 1, 'title' => 'sample post'], ''); - $this->assertEquals('//www.example.com?r=post%2Fview&id=1&title=sample+post', $url); + $this->assertEquals('//www.example.com/index.php?r=post%2Fview&id=1&title=sample+post', $url); } - public function testCreateAbsoluteUrlWithSuffix() + /** + * Test normalisation of different routes. + * @dataProvider ignoredOptionsProvider + */ + public function testCreateUrlRouteVariants($showScriptName, $enableStrictParsing) { - $manager = new UrlManager([ - 'baseUrl' => '/', - 'scriptUrl' => '', - 'hostInfo' => 'http://app.example.com', - 'cache' => null, - - 'enablePrettyUrl' => true, - 'showScriptName' => false, - 'suffix' => '/', - 'rules' => [ - 'http://app.example.com/login' => 'site/login', - ], - ]); - $url = $manager->createAbsoluteUrl(['site/login']); - $this->assertEquals('http://app.example.com/login/', $url); - $url = $manager->createUrl(['site/login']); - $this->assertEquals('http://app.example.com/login/', $url); + // default setting with '/' as base url + $manager = $this->getUrlManager([], $showScriptName, $enableStrictParsing); + $url = $manager->createUrl(['/post/view']); + $this->assertEquals('/index.php?r=post%2Fview', $url); + $url = $manager->createUrl(['/post/view/']); + $this->assertEquals('/index.php?r=post%2Fview', $url); + $url = $manager->createUrl(['/module/post/view']); + $this->assertEquals('/index.php?r=module%2Fpost%2Fview', $url); + $url = $manager->createUrl(['/post/view/']); + $this->assertEquals('/index.php?r=post%2Fview', $url); } - public function testParseRequest() + + /** + * @return array provides different names for UrlManager::$routeParam + */ + public function routeParamProvider() { - $manager = new UrlManager(['cache' => null]); + return [ + ['r'], // default value + ['route'], + ['_'], + ]; + } + + /** + * @dataProvider routeParamProvider + */ + public function testParseRequest($routeParam) + { + $manager = $this->getUrlManager(['routeParam' => $routeParam]); $request = new Request; // default setting without 'r' param - unset($_GET['r']); + $request->setQueryParams([]); $result = $manager->parseRequest($request); $this->assertEquals(['', []], $result); // default setting with 'r' param - $_GET['r'] = 'site/index'; + $request->setQueryParams([$routeParam => 'site/index']); $result = $manager->parseRequest($request); $this->assertEquals(['site/index', []], $result); // default setting with 'r' param as an array - $_GET['r'] = ['site/index']; + $request->setQueryParams([$routeParam => ['site/index']]); $result = $manager->parseRequest($request); $this->assertEquals(['', []], $result); - // pretty URL without rules - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'cache' => null, - 'normalizer' => false, - ]); - // empty pathinfo - $request->pathInfo = ''; - $result = $manager->parseRequest($request); - $this->assertEquals(['', []], $result); - // normal pathinfo - $request->pathInfo = 'site/index'; + // other parameters are not returned here + $request->setQueryParams([$routeParam => 'site/index', 'id' => 5]); $result = $manager->parseRequest($request); $this->assertEquals(['site/index', []], $result); - // pathinfo with module - $request->pathInfo = 'module/site/index'; - $result = $manager->parseRequest($request); - $this->assertEquals(['module/site/index', []], $result); - // pathinfo with trailing slashes - $request->pathInfo = '/module/site/index/'; - $result = $manager->parseRequest($request); - $this->assertEquals(['module/site/index/', []], $result); - - // trailing slash is insignificant if normalizer is enabled - $manager->normalizer = new UrlNormalizer([ - 'action' => null, - ]); - $request->pathInfo = '/module/site/index/'; - $result = $manager->parseRequest($request); - $this->assertEquals(['module/site/index', []], $result); - - // pretty URL rules - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'cache' => null, - 'normalizer' => false, - 'rules' => [ - [ - 'pattern' => 'post/<id>/<title>', - 'route' => 'post/view', - ], - ], - ]); - // matching pathinfo - $request->pathInfo = 'post/123/this+is+sample'; - $result = $manager->parseRequest($request); - $this->assertEquals(['post/view', ['id' => '123', 'title' => 'this+is+sample']], $result); - // trailing slash is significant - $request->pathInfo = 'post/123/this+is+sample/'; - $result = $manager->parseRequest($request); - $this->assertEquals(['post/123/this+is+sample/', []], $result); - // empty pathinfo - $request->pathInfo = ''; - $result = $manager->parseRequest($request); - $this->assertEquals(['', []], $result); - // normal pathinfo - $request->pathInfo = 'site/index'; - $result = $manager->parseRequest($request); - $this->assertEquals(['site/index', []], $result); - // pathinfo with module - $request->pathInfo = 'module/site/index'; - $result = $manager->parseRequest($request); - $this->assertEquals(['module/site/index', []], $result); - - // trailing slash is insignificant if normalizer is enabled - $manager->normalizer = new UrlNormalizer([ - 'action' => null, - ]); - $request->pathInfo = 'post/123/this+is+sample/'; - $result = $manager->parseRequest($request); - $this->assertEquals(['post/view', ['id' => '123', 'title' => 'this+is+sample']], $result); - - // pretty URL rules - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'suffix' => '.html', - 'cache' => null, - 'rules' => [ - [ - 'pattern' => 'post/<id>/<title>', - 'route' => 'post/view', - ], - ], - ]); - // matching pathinfo - $request->pathInfo = 'post/123/this+is+sample.html'; - $result = $manager->parseRequest($request); - $this->assertEquals(['post/view', ['id' => '123', 'title' => 'this+is+sample']], $result); - // matching pathinfo without suffix - $request->pathInfo = 'post/123/this+is+sample'; - $result = $manager->parseRequest($request); - $this->assertFalse($result); - // empty pathinfo - $request->pathInfo = ''; - $result = $manager->parseRequest($request); - $this->assertEquals(['', []], $result); - // normal pathinfo - $request->pathInfo = 'site/index.html'; - $result = $manager->parseRequest($request); - $this->assertEquals(['site/index', []], $result); - // pathinfo without suffix - $request->pathInfo = 'site/index'; - $result = $manager->parseRequest($request); - $this->assertFalse($result); - - // strict parsing - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'enableStrictParsing' => true, - 'suffix' => '.html', - 'cache' => null, - 'rules' => [ - [ - 'pattern' => 'post/<id>/<title>', - 'route' => 'post/view', - ], - ], - ]); - // matching pathinfo - $request->pathInfo = 'post/123/this+is+sample.html'; - $result = $manager->parseRequest($request); - $this->assertEquals(['post/view', ['id' => '123', 'title' => 'this+is+sample']], $result); - // unmatching pathinfo - $request->pathInfo = 'site/index.html'; - $result = $manager->parseRequest($request); - $this->assertFalse($result); - } - - public function testParseRESTRequest() - { - $request = new Request; - - // pretty URL rules - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'showScriptName' => false, - 'cache' => null, - 'rules' => [ - 'PUT,POST post/<id>/<title>' => '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(['post/view', ['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(['post/create', ['id' => '123', 'title' => 'this+is+sample']], $result); - $_SERVER['REQUEST_METHOD'] = 'POST'; - $request->pathInfo = 'post/123/this+is+sample'; - $result = $manager->parseRequest($request); - $this->assertEquals(['post/create', ['id' => '123', 'title' => 'this+is+sample']], $result); - - // no wrong matching - $_SERVER['REQUEST_METHOD'] = 'POST'; - $request->pathInfo = 'POST/GET'; - $result = $manager->parseRequest($request); - $this->assertEquals(['post/get', []], $result); - - // createUrl should ignore REST rules - $this->mockApplication([ - 'components' => [ - 'request' => [ - 'hostInfo' => 'http://localhost/', - 'baseUrl' => '/app' - ] - ] - ], \yii\web\Application::className()); - $this->assertEquals('/app/post/delete?id=123', $manager->createUrl(['post/delete', 'id' => 123])); - $this->destroyApplication(); - - unset($_SERVER['REQUEST_METHOD']); - } - - /** - * Tests if hash-anchor present - * - * https://github.com/yiisoft/yii2/pull/9596 - */ - public function testHash() - { - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'cache' => null, - 'rules' => [ - 'http://example.com/testPage' => 'site/test', - ], - 'hostInfo' => 'http://example.com', - 'scriptUrl' => '/index.php', - ]); - $url = $manager->createAbsoluteUrl(['site/test', '#' => 'testhash']); - $this->assertEquals('http://example.com/index.php/testPage#testhash', $url); - } - - /** - * @dataProvider multipleHostsRulesDataProvider - * - * @see https://github.com/yiisoft/yii2/issues/7948 - * - * @param string $host - */ - public function testMultipleHostsRules($host) - { - $manager = new UrlManager([ - 'enablePrettyUrl' => true, - 'cache' => null, - 'rules' => [ - ['host' => 'http://example.com', 'pattern' => '<slug:(search)>', 'route' => 'products/search', 'defaults' => ['lang' => 'en']], - ['host' => 'http://example.fr', 'pattern' => '<slug:(search)>', 'route' => 'products/search', 'defaults' => ['lang' => 'fr']], - ], - 'hostInfo' => $host, - 'baseUrl' => '/', - 'scriptUrl' => '', - ]); - - $url = $manager->createAbsoluteUrl(['products/search', 'lang' => 'en', 'slug' => 'search'], 'https'); - $this->assertEquals('https://example.com/search', $url); - $url = $manager->createUrl(['products/search', 'lang' => 'en', 'slug' => 'search']); - $this->assertEquals('http://example.com/search', $url); - $url = $manager->createUrl(['products/search', 'lang' => 'en', 'slug' => 'search', 'param1' => 'value1']); - $this->assertEquals('http://example.com/search?param1=value1', $url); - - $url = $manager->createAbsoluteUrl(['products/search', 'lang' => 'fr', 'slug' => 'search'], 'https'); - $this->assertEquals('https://example.fr/search', $url); - $url = $manager->createUrl(['products/search', 'lang' => 'fr', 'slug' => 'search']); - $this->assertEquals('http://example.fr/search', $url); - $url = $manager->createUrl(['products/search', 'lang' => 'fr', 'slug' => 'search', 'param1' => 'value1']); - $this->assertEquals('http://example.fr/search?param1=value1', $url); - } - - public function multipleHostsRulesDataProvider() - { - return [ - ['http://example.com'], - ['https://example.com'], - ['http://example.fr'], - ['https://example.fr'], - ]; + $this->assertEquals(5, $request->getQueryParam('id')); } } diff --git a/tests/framework/web/UrlNormalizerTest.php b/tests/framework/web/UrlNormalizerTest.php index f62608a6c1..7a172b5d27 100644 --- a/tests/framework/web/UrlNormalizerTest.php +++ b/tests/framework/web/UrlNormalizerTest.php @@ -10,6 +10,8 @@ namespace yiiunit\framework\web; use Yii; use yii\web\NotFoundHttpException; +use yii\web\Request; +use yii\web\UrlManager; use yii\web\UrlNormalizer; use yii\web\UrlNormalizerRedirectException; use yiiunit\TestCase; @@ -114,4 +116,41 @@ class UrlNormalizerTest extends TestCase $this->assertEquals($expected, $exc); } } + + /** + * Test usage of UrlNormalizer in UrlManager + * + * trailing slash is insignificant if normalizer is enabled + */ + public function testUrlManager() + { + $config = [ + 'enablePrettyUrl' => true, + 'cache' => null, + 'normalizer' => [ + 'class' => 'yii\web\UrlNormalizer', + 'action' => null, + ], + ]; + $request = new Request(); + + // pretty URL without rules + $config['rules'] = []; + $manager = new UrlManager($config); + $request->pathInfo = '/module/site/index/'; + $result = $manager->parseRequest($request); + $this->assertEquals(['module/site/index', []], $result); + + // pretty URL with rules + $config['rules'] = [ + [ + 'pattern' => 'post/<id>/<title>', + 'route' => 'post/view', + ], + ]; + $manager = new UrlManager($config); + $request->pathInfo = 'post/123/this+is+sample/'; + $result = $manager->parseRequest($request); + $this->assertEquals(['post/view', ['id' => '123', 'title' => 'this+is+sample']], $result); + } }