From 8102688f9ff1aa6fffa5f68927d897a2dc9d2492 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Mar 2021 17:36:46 +0100 Subject: [PATCH 1/9] opened 4.0-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e650c66..97dddd2 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "4.0-dev" } } } From 80dcb2de10ce14af6bfcd39295d0ec7601f3f5a2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 10 Jan 2023 01:44:55 +0100 Subject: [PATCH 2/9] RouteList::addRoute() added return typehint --- src/Routing/RouteList.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Routing/RouteList.php b/src/Routing/RouteList.php index 4156175..0c37ef9 100644 --- a/src/Routing/RouteList.php +++ b/src/Routing/RouteList.php @@ -220,12 +220,7 @@ protected function modify(int $index, ?Router $router): void } - /** - * @param string $mask e.g. '//' - * @param array $metadata default values or metadata - * @return static - */ - public function addRoute(string $mask, array $metadata = [], int $oneWay = 0) + public function addRoute(string $mask, array $metadata = [], int $oneWay = 0): static { $this->add(new Route($mask, $metadata), $oneWay); return $this; From 6f9b96294e516a007828edd04ba5ac7d4ad42e68 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 6 Feb 2021 01:30:29 +0100 Subject: [PATCH 3/9] Route: uses loose comparison == for default values [Closes #8] --- src/Routing/Route.php | 3 ++- tests/Route/objectParameter.phpt | 32 ++++++++++++++++++++++++++++++++ tests/Route/scalarParams.phpt | 5 ++++- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 tests/Route/objectParameter.phpt diff --git a/src/Routing/Route.php b/src/Routing/Route.php index a36f2dc..be62c68 100644 --- a/src/Routing/Route.php +++ b/src/Routing/Route.php @@ -319,7 +319,8 @@ private function preprocessParams(array &$params): bool } if ($fixity !== null) { - if ($params[$name] === $meta[self::Value]) { // remove default values; null values are retain + if ($params[$name] == $meta[self::Value]) { // default value may be object, intentionally == + // remove default values; null values are retain unset($params[$name]); continue; diff --git a/tests/Route/objectParameter.phpt b/tests/Route/objectParameter.phpt new file mode 100644 index 0000000..c24298d --- /dev/null +++ b/tests/Route/objectParameter.phpt @@ -0,0 +1,32 @@ + $object, + ]); + + Assert::same( + 'http://example.com/', + testRouteOut($route, ['param' => $object]), + ); + + Assert::same( + 'http://example.com/', + testRouteOut($route, ['param' => new stdClass]), + ); +}); diff --git a/tests/Route/scalarParams.phpt b/tests/Route/scalarParams.phpt index 4f618ec..0cff844 100644 --- a/tests/Route/scalarParams.phpt +++ b/tests/Route/scalarParams.phpt @@ -245,5 +245,8 @@ test('', function () { testRouteOut($route, ['presenter' => 'homepage', 'param' => null]), ); - Assert::null(testRouteOut($route, ['presenter' => 'homepage', 'param' => ''])); + Assert::same( + 'http://example.com/homepage/', + testRouteOut($route, ['presenter' => 'homepage', 'param' => '']), + ); }); From 8d7c91d3dcbf15f99648bc05ee3c25ff07d529cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mor=C3=A1vek?= Date: Sat, 6 Feb 2021 02:09:37 +0100 Subject: [PATCH 4/9] SimpleRouter: uses loose comparison == for default values since PHP 8.0 --- src/Routing/SimpleRouter.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Routing/SimpleRouter.php b/src/Routing/SimpleRouter.php index 57958a7..0901121 100644 --- a/src/Routing/SimpleRouter.php +++ b/src/Routing/SimpleRouter.php @@ -44,9 +44,7 @@ public function constructUrl(array $params, Nette\Http\UrlScript $refUrl): ?stri { // remove default values; null values are retain foreach ($this->defaults as $key => $value) { - if (isset($params[$key]) - && (is_scalar($params[$key]) ? (string) $params[$key] : $params[$key]) === (is_scalar($value) ? (string) $value : $value) - ) { + if (isset($params[$key]) && $params[$key] == $value) { // default value may be object, intentionally == unset($params[$key]); } } From 4563a0fe48ed88da44f9c9efaac0d2ee2c413294 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 8 Feb 2021 04:10:40 +0100 Subject: [PATCH 5/9] Route: rejects route when constant parameter is not present (BC break?) Not to generate the first URL for Homepage: default $router->addRoute('/foo', [ 'presenter' => 'Homepage', 'action' => 'default', 'week' => 'upcoming', ]); $router->addRoute('/', 'Homepage:default'); --- src/Routing/Route.php | 8 ++++++++ tests/Route/fixedParameter.phpt | 10 ++-------- tests/Route/optional.autooptional3.phpt | 5 +---- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Routing/Route.php b/src/Routing/Route.php index be62c68..0c2e38d 100644 --- a/src/Routing/Route.php +++ b/src/Routing/Route.php @@ -309,6 +309,14 @@ private function preprocessParams(array &$params): bool $fixity = $meta[self::Fixity] ?? null; if (!isset($params[$name])) { + if ($fixity === self::Constant) { + if ($meta[self::Value] === null) { + continue; + } + + return false; // wrong parameter value + } + continue; // retains null values } diff --git a/tests/Route/fixedParameter.phpt b/tests/Route/fixedParameter.phpt index 4bc1b37..705f396 100644 --- a/tests/Route/fixedParameter.phpt +++ b/tests/Route/fixedParameter.phpt @@ -21,10 +21,7 @@ testRouteIn($route, '/?const=foo', ['const' => 'hello', 'test' => 'testvalue'], testRouteIn($route, '/?const=hello', ['const' => 'hello', 'test' => 'testvalue'], '/?test=testvalue'); -Assert::same( - 'http://example.com/', - testRouteOut($route, []) -); +Assert::null(testRouteOut($route, [])); Assert::null(testRouteOut($route, ['const' => 'foo'])); @@ -33,7 +30,4 @@ Assert::same( testRouteOut($route, ['const' => 'hello']), ); -Assert::same( - 'http://example.com/', - testRouteOut($route, ['const' => null]) -); +Assert::null(testRouteOut($route, ['const' => null])); diff --git a/tests/Route/optional.autooptional3.phpt b/tests/Route/optional.autooptional3.phpt index 41caeac..80db780 100644 --- a/tests/Route/optional.autooptional3.phpt +++ b/tests/Route/optional.autooptional3.phpt @@ -13,9 +13,7 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -$route = new Route('//', [ - 'action' => 'default', -]); +$route = new Route('//', []); testRouteIn($route, '/presenter/'); testRouteIn($route, '/presenter/abc'); @@ -24,7 +22,6 @@ testRouteIn($route, '/presenter/abc/'); testRouteIn($route, '/presenter/abc/xyy', [ 'presenter' => 'presenter', 'default' => 'abc', - 'action' => 'default', 'test' => 'testvalue', 'required' => 'xyy', ], '/presenter/abc/xyy?test=testvalue'); From 1c929dd70f3ad3010fd2b4bb8b725e591d37afbb Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 27 Nov 2022 22:25:55 +0100 Subject: [PATCH 6/9] Route: added port to %host%, %domain%, %tld% WIP [Closes #10][Closes nette/application#297] --- src/Routing/Route.php | 7 ++++--- tests/Route/ports.phpt | 29 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 tests/Route/ports.phpt diff --git a/src/Routing/Route.php b/src/Routing/Route.php index 0c2e38d..0e054e6 100644 --- a/src/Routing/Route.php +++ b/src/Routing/Route.php @@ -272,12 +272,13 @@ public function constructUrl(array $params, Nette\Http\UrlScript $refUrl): ?stri $parts = ip2long($host) ? [$host] : array_reverse(explode('.', $host)); + $port = $refUrl->getDefaultPort() === ($tmp = $refUrl->getPort()) ? '' : ':' . $tmp; $url = strtr($url, [ '/%basePath%/' => $refUrl->getBasePath(), - '%tld%' => $parts[0], - '%domain%' => isset($parts[1]) ? "$parts[1].$parts[0]" : $parts[0], + '%tld%' => $parts[0] . $port, + '%domain%' => (isset($parts[1]) ? "$parts[1].$parts[0]" : $parts[0]) . $port, '%sld%' => $parts[1] ?? '', - '%host%' => $host, + '%host%' => $host . $port, ]); } diff --git a/tests/Route/ports.phpt b/tests/Route/ports.phpt new file mode 100644 index 0000000..c13f941 --- /dev/null +++ b/tests/Route/ports.phpt @@ -0,0 +1,29 @@ +constructUrl( + [], + new UrlScript('https://example.org:8000'), +); +Assert::same('https://example.org:8000', $url); + +$url = $route->constructUrl( + [], + new UrlScript('https://localhost:8000'), +); +Assert::same('https://localhost:8000', $url); From a6f14fd0063c89ed36f154fe6d785566ae0287e0 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 10 Jan 2023 02:10:13 +0100 Subject: [PATCH 7/9] RouteList: parameter $oneWay is bool (BC break) --- .phpstorm.meta.php | 8 -------- readme.md | 8 ++++---- src/Routing/RouteList.php | 10 +++++----- src/Routing/Router.php | 4 ++-- tests/RouteList/addRoute.phpt | 4 ++-- tests/RouteList/cache.phpt | 2 +- tests/RouteList/oneWay.phpt | 6 +++--- 7 files changed, 17 insertions(+), 25 deletions(-) delete mode 100644 .phpstorm.meta.php diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php deleted file mode 100644 index cd9d3a7..0000000 --- a/.phpstorm.meta.php +++ /dev/null @@ -1,8 +0,0 @@ -addRoute('product-info', [...], $router::ONE_WAY); +$router->addRoute('product-info', [...], oneWay: true); // new URL /product/123 $router->addRoute('product/', [...]); ``` @@ -450,7 +450,7 @@ SEO and Canonization The framework increases SEO (search engine optimization) by preventing duplication of content at different URLs. If multiple addresses link to a same destination, eg `/index` and `/index.html`, the framework determines the first one as primary (canonical) and redirects the others to it using HTTP code 301. Thanks to this, search engines will not index pages twice and do not break their page rank. . -This process is called canonization. The canonical URL is the one generated by the router, i.e. by the first matching route in the [collection ](#route-collection) without the ONE_WAY flag. Therefore, in the collection, we list **primary routes first**. +This process is called canonization. The canonical URL is the one generated by the router, i.e. by the first matching route in the [collection ](#route-collection) without the OneWay flag. Therefore, in the collection, we list **primary routes first**. Canonization is performed by the controller, more in the chapter [canonization ](controllers#Canonization). diff --git a/src/Routing/RouteList.php b/src/Routing/RouteList.php index 0c37ef9..17eac8e 100644 --- a/src/Routing/RouteList.php +++ b/src/Routing/RouteList.php @@ -187,7 +187,7 @@ public function warmupCache(): void /** * Adds a router. */ - public function add(Router $router, int $oneWay = 0): static + public function add(Router $router, bool $oneWay = false): static { $this->list[] = [$router, $oneWay]; $this->ranks = null; @@ -198,7 +198,7 @@ public function add(Router $router, int $oneWay = 0): static /** * Prepends a router. */ - public function prepend(Router $router, int $oneWay = 0): void + public function prepend(Router $router, bool $oneWay = false): void { array_splice($this->list, 0, 0, [[$router, $oneWay]]); $this->ranks = null; @@ -220,7 +220,7 @@ protected function modify(int $index, ?Router $router): void } - public function addRoute(string $mask, array $metadata = [], int $oneWay = 0): static + public function addRoute(string $mask, array $metadata = [], bool $oneWay = false): static { $this->add(new Route($mask, $metadata), $oneWay); return $this; @@ -268,11 +268,11 @@ public function getRouters(): array /** - * @return int[] + * @return bool[][] */ public function getFlags(): array { - return array_column($this->list, 1); + return array_map(fn($info) => ['oneWay' => (bool) $info[1]], $this->list); } diff --git a/src/Routing/Router.php b/src/Routing/Router.php index 2ea81a9..61a2a04 100644 --- a/src/Routing/Router.php +++ b/src/Routing/Router.php @@ -17,8 +17,8 @@ */ interface Router { - /** for back compatibility */ - public const ONE_WAY = 0b0001; + /** @deprecated */ + public const ONE_WAY = true; /** * Maps HTTP request to an array. diff --git a/tests/RouteList/addRoute.phpt b/tests/RouteList/addRoute.phpt index e3b63d9..f44e2c3 100644 --- a/tests/RouteList/addRoute.phpt +++ b/tests/RouteList/addRoute.phpt @@ -9,8 +9,8 @@ require __DIR__ . '/../bootstrap.php'; $list = new RouteList; -$list->addRoute('foo', ['route' => 'foo'], RouteList::ONE_WAY); -$list->addRoute('bar', ['route' => 'bar'], RouteList::ONE_WAY); +$list->addRoute('foo', ['route' => 'foo'], oneWay: true); +$list->addRoute('bar', ['route' => 'bar'], oneWay: true); $list->addRoute('hello', ['route' => 'hello']); diff --git a/tests/RouteList/cache.phpt b/tests/RouteList/cache.phpt index 1b5ef75..a0f618e 100644 --- a/tests/RouteList/cache.phpt +++ b/tests/RouteList/cache.phpt @@ -14,7 +14,7 @@ $list = new RouteList; $list->add(new Route('bar', ['presenter' => 'bar'])); $list->add(new Route('', ['presenter' => 'foo'])); $list->add(new Route('/', ['presenter' => 'xxx'])); -$list->add(new Route('oneway'), $list::ONE_WAY); +$list->add(new Route('oneway'), oneWay: true); [$r1, $r2, $r3, $r4] = $list->getRouters(); diff --git a/tests/RouteList/oneWay.phpt b/tests/RouteList/oneWay.phpt index 8c824e5..88084a0 100644 --- a/tests/RouteList/oneWay.phpt +++ b/tests/RouteList/oneWay.phpt @@ -11,11 +11,11 @@ require __DIR__ . '/../bootstrap.php'; $list = new RouteList; -$list->add(new Route('foo', ['route' => 'foo']), RouteList::ONE_WAY); -$list->addRoute('bar', ['route' => 'bar'], RouteList::ONE_WAY); +$list->add(new Route('foo', ['route' => 'foo']), oneWay: true); +$list->addRoute('bar', ['route' => 'bar'], oneWay: true); $list->add(new Route('hello', ['route' => 'hello'])); -Assert::same([RouteList::ONE_WAY, RouteList::ONE_WAY, 0], $list->getFlags()); +Assert::same([['oneWay' => true], ['oneWay' => true], ['oneWay' => false]], $list->getFlags()); testRouteIn($list, '/foo', ['route' => 'foo', 'test' => 'testvalue']); testRouteIn($list, '/bar', ['route' => 'bar', 'test' => 'testvalue']); From 9e4dc62024419177b7b7ba6ebc7b55f98cf21b26 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 6 Oct 2023 00:30:11 +0200 Subject: [PATCH 8/9] RouteList::match() is final (BC break) --- src/Routing/RouteList.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Routing/RouteList.php b/src/Routing/RouteList.php index 17eac8e..3c4bc42 100644 --- a/src/Routing/RouteList.php +++ b/src/Routing/RouteList.php @@ -37,9 +37,8 @@ public function __construct() /** * Maps HTTP request to an array. - * @final */ - public function match(Nette\Http\IRequest $httpRequest): ?array + final public function match(Nette\Http\IRequest $httpRequest): ?array { if ($httpRequest = $this->prepareRequest($httpRequest)) { foreach ($this->list as [$router]) { From 9ce8580d98bbb99528549017650ed6508c1b9678 Mon Sep 17 00:00:00 2001 From: "milos.brecher" Date: Tue, 3 Dec 2024 02:39:49 +0100 Subject: [PATCH 9/9] added ending slash to all usages of UrlScript::getBasePath(), after normalization of this method and removing ending slash --- src/Routing/Route.php | 10 +++++----- src/Routing/RouteList.php | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Routing/Route.php b/src/Routing/Route.php index 0e054e6..4937930 100644 --- a/src/Routing/Route.php +++ b/src/Routing/Route.php @@ -167,7 +167,7 @@ public function match(Nette\Http\IRequest $httpRequest): ?array ? [$host] : array_reverse(explode('.', $host)); $re = strtr($re, [ - '/%basePath%/' => preg_quote($url->getBasePath(), '#'), + '/%basePath%/' => preg_quote($url->getBasePath() . '/', '#'), '%tld%' => preg_quote($parts[0], '#'), '%domain%' => preg_quote(isset($parts[1]) ? "$parts[1].$parts[0]" : $parts[0], '#'), '%sld%' => preg_quote($parts[1] ?? '', '#'), @@ -176,11 +176,11 @@ public function match(Nette\Http\IRequest $httpRequest): ?array } elseif ($this->type === self::Relative) { $basePath = $url->getBasePath(); - if (strncmp($url->getPath(), $basePath, strlen($basePath)) !== 0) { + if (strncmp($url->getPath(), $basePath.'/', strlen($basePath.'/')) !== 0) { return null; } - $path = substr($url->getPath(), strlen($basePath)); + $path = substr($url->getPath(), strlen($basePath.'/')); } else { $path = $url->getPath(); @@ -262,7 +262,7 @@ public function constructUrl(array $params, Nette\Http\UrlScript $refUrl): ?stri // absolutize if ($this->type === self::Relative) { - $url = (($tmp = $refUrl->getAuthority()) ? "//$tmp" : '') . $refUrl->getBasePath() . $url; + $url = (($tmp = $refUrl->getAuthority()) ? "//$tmp" : '') . $refUrl->getBasePath() . '/' . $url; } elseif ($this->type === self::Path) { $url = (($tmp = $refUrl->getAuthority()) ? "//$tmp" : '') . $url; @@ -274,7 +274,7 @@ public function constructUrl(array $params, Nette\Http\UrlScript $refUrl): ?stri : array_reverse(explode('.', $host)); $port = $refUrl->getDefaultPort() === ($tmp = $refUrl->getPort()) ? '' : ':' . $tmp; $url = strtr($url, [ - '/%basePath%/' => $refUrl->getBasePath(), + '/%basePath%/' => $refUrl->getBasePath() . '/', '%tld%' => $parts[0] . $port, '%domain%' => (isset($parts[1]) ? "$parts[1].$parts[0]" : $parts[0]) . $port, '%sld%' => $parts[1] ?? '', diff --git a/src/Routing/RouteList.php b/src/Routing/RouteList.php index 3c4bc42..179fd8a 100644 --- a/src/Routing/RouteList.php +++ b/src/Routing/RouteList.php @@ -67,7 +67,7 @@ protected function prepareRequest(Nette\Http\IRequest $httpRequest): ?Nette\Http $url = $httpRequest->getUrl(); $relativePath = $url->getRelativePath(); if (strncmp($relativePath, $this->path, strlen($this->path)) === 0) { - $url = $url->withPath($url->getPath(), $url->getBasePath() . $this->path); + $url = $url->withPath($url->getPath(), $url->getBasePath() . '/' . $this->path); } elseif ($relativePath . '/' === $this->path) { $url = $url->withPath($url->getPath() . '/'); } else { @@ -104,7 +104,7 @@ public function constructUrl(array $params, Nette\Http\UrlScript $refUrl): ?stri if ($this->path) { if (!isset($this->refUrlCache[$refUrl])) { - $this->refUrlCache[$refUrl] = $refUrl->withPath($refUrl->getBasePath() . $this->path); + $this->refUrlCache[$refUrl] = $refUrl->withPath($refUrl->getBasePath() . '/' . $this->path); } $refUrl = $this->refUrlCache[$refUrl];