diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php deleted file mode 100644 index cd9d3a75..00000000 --- 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/Route.php b/src/Routing/Route.php index a36f2dc4..4937930a 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; @@ -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], + '/%basePath%/' => $refUrl->getBasePath() . '/', + '%tld%' => $parts[0] . $port, + '%domain%' => (isset($parts[1]) ? "$parts[1].$parts[0]" : $parts[0]) . $port, '%sld%' => $parts[1] ?? '', - '%host%' => $host, + '%host%' => $host . $port, ]); } @@ -309,6 +310,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 } @@ -319,7 +328,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/src/Routing/RouteList.php b/src/Routing/RouteList.php index 41561757..179fd8a2 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]) { @@ -68,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 { @@ -105,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]; @@ -187,7 +186,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 +197,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,12 +219,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 = [], bool $oneWay = false): static { $this->add(new Route($mask, $metadata), $oneWay); return $this; @@ -273,11 +267,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 2ea81a95..61a2a04f 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/src/Routing/SimpleRouter.php b/src/Routing/SimpleRouter.php index 57958a77..0901121d 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]); } } diff --git a/tests/Route/fixedParameter.phpt b/tests/Route/fixedParameter.phpt index 4bc1b37b..705f396a 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/objectParameter.phpt b/tests/Route/objectParameter.phpt new file mode 100644 index 00000000..c24298d8 --- /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/optional.autooptional3.phpt b/tests/Route/optional.autooptional3.phpt index 41caeac8..80db780d 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'); diff --git a/tests/Route/ports.phpt b/tests/Route/ports.phpt new file mode 100644 index 00000000..c13f9413 --- /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); diff --git a/tests/Route/scalarParams.phpt b/tests/Route/scalarParams.phpt index 4f618ecd..0cff8445 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' => '']), + ); }); diff --git a/tests/RouteList/addRoute.phpt b/tests/RouteList/addRoute.phpt index e3b63d94..f44e2c37 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 1b5ef75f..a0f618e6 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 8c824e5f..88084a07 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']);