From 7991530761734d694b6c9a719468337f0e95dbba Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Wed, 10 Jan 2024 23:20:17 -0700 Subject: [PATCH 1/6] Route alias and phpstan updates --- flight/database/PdoWrapper.php | 22 +++++++-------- flight/net/Request.php | 5 ++-- flight/net/Route.php | 34 ++++++++++++++++++++++- flight/net/Router.php | 44 ++++++++++++++++++++++++++++-- tests/RouterTest.php | 50 ++++++++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 17 deletions(-) diff --git a/flight/database/PdoWrapper.php b/flight/database/PdoWrapper.php index ac87aff..7189cbd 100644 --- a/flight/database/PdoWrapper.php +++ b/flight/database/PdoWrapper.php @@ -13,7 +13,7 @@ class PdoWrapper extends PDO { * @param string $dsn - Ex: 'mysql:host=localhost;port=3306;dbname=testdb;charset=utf8mb4' * @param string $username - Ex: 'root' * @param string $password - Ex: 'password' - * @param array $options - PDO options you can pass in + * @param array $options - PDO options you can pass in */ public function __construct(string $dsn, ?string $username = null, ?string $password = null, array $options = []) { parent::__construct($dsn, $username, $password, $options); @@ -31,7 +31,7 @@ class PdoWrapper extends PDO { * $db->runQuery("UPDATE table SET name = ? WHERE id = ?", [ $name, $id ]); * * @param string $sql - Ex: "SELECT * FROM table WHERE something = ?" - * @param array $params - Ex: [ $something ] + * @param array $params - Ex: [ $something ] * @return PDOStatement */ public function runQuery(string $sql, array $params = []): PDOStatement { @@ -49,12 +49,12 @@ class PdoWrapper extends PDO { * Ex: $id = $db->fetchField("SELECT id FROM table WHERE something = ?", [ $something ]); * * @param string $sql - Ex: "SELECT id FROM table WHERE something = ?" - * @param array $params - Ex: [ $something ] + * @param array $params - Ex: [ $something ] * @return mixed */ public function fetchField(string $sql, array $params = []) { $data = $this->fetchRow($sql, $params); - return is_array($data) ? reset($data) : null; + return reset($data); } /** @@ -63,13 +63,13 @@ class PdoWrapper extends PDO { * Ex: $row = $db->fetchRow("SELECT * FROM table WHERE something = ?", [ $something ]); * * @param string $sql - Ex: "SELECT * FROM table WHERE something = ?" - * @param array $params - Ex: [ $something ] - * @return array + * @param array $params - Ex: [ $something ] + * @return array */ public function fetchRow(string $sql, array $params = []): array { $sql .= stripos($sql, 'LIMIT') === false ? ' LIMIT 1' : ''; $result = $this->fetchAll($sql, $params); - return is_array($result) && count($result) ? $result[0] : []; + return count($result) > 0 ? $result[0] : []; } /** @@ -81,8 +81,8 @@ class PdoWrapper extends PDO { * } * * @param string $sql - Ex: "SELECT * FROM table WHERE something = ?" - * @param array $params - Ex: [ $something ] - * @return array + * @param array $params - Ex: [ $something ] + * @return array> */ public function fetchAll(string $sql, array $params = []): array { $processed_sql_data = $this->processInStatementSql($sql, $params); @@ -101,8 +101,8 @@ class PdoWrapper extends PDO { * Converts this to "SELECT * FROM table WHERE id = ? AND something IN(?,?,?)" * * @param string $sql the sql statement - * @param array $params the params for the sql statement - * @return array{sql:string,params:array} + * @param array $params the params for the sql statement + * @return array> */ protected function processInStatementSql(string $sql, array $params = []): array { diff --git a/flight/net/Request.php b/flight/net/Request.php index 8a40a50..88be447 100644 --- a/flight/net/Request.php +++ b/flight/net/Request.php @@ -145,7 +145,6 @@ final class Request * Constructor. * * @param array $config Request configuration - * @param string */ public function __construct($config = array()) { @@ -210,7 +209,7 @@ final class Request // Check for JSON input if (0 === strpos($this->type, 'application/json')) { $body = $this->getBody(); - if ('' !== $body && null !== $body) { + if ('' !== $body) { $data = json_decode($body, true); if (is_array($data)) { $this->data->setData($data); @@ -226,7 +225,7 @@ final class Request * * @return string Raw HTTP request body */ - public function getBody(): ?string + public function getBody(): string { $body = $this->body; diff --git a/flight/net/Route.php b/flight/net/Route.php index fbfc20b..b3758c3 100644 --- a/flight/net/Route.php +++ b/flight/net/Route.php @@ -52,6 +52,11 @@ final class Route */ public bool $pass = false; + /** + * @var string The alias is a way to identify the route using a simple name ex: 'login' instead of /admin/login + */ + public string $alias = ''; + /** * Constructor. * @@ -60,12 +65,13 @@ final class Route * @param array $methods HTTP methods * @param bool $pass Pass self in callback parameters */ - public function __construct(string $pattern, $callback, array $methods, bool $pass) + public function __construct(string $pattern, $callback, array $methods, bool $pass, string $alias = '') { $this->pattern = $pattern; $this->callback = $callback; $this->methods = $methods; $this->pass = $pass; + $this->alias = $alias; } /** @@ -153,4 +159,30 @@ final class Route { return \count(array_intersect([$method, '*'], $this->methods)) > 0; } + + /** + * Checks if an alias matches the route alias. + * + * @param string $alias [description] + * @return boolean + */ + public function matchAlias(string $alias): bool + { + return $this->alias === $alias; + } + + /** + * Hydrates the route url with the given parameters + * + * @param array $params the parameters to pass to the route + * @return string + */ + public function hydrateUrl(array $params = []): string { + $url = preg_replace_callback("/(?:@([a-zA-Z]+)(?:\:([^\/]+))?)?/i", function($match) use ($params) { + if(isset($match[1]) && isset($params[$match[1]])) { + return $params[$match[1]]; + } + }, $this->pattern); + return $url; + } } diff --git a/flight/net/Router.php b/flight/net/Router.php index 078948c..f54d898 100644 --- a/flight/net/Router.php +++ b/flight/net/Router.php @@ -10,6 +10,9 @@ declare(strict_types=1); namespace flight\net; +use Exception; +use flight\net\Route; + /** * The Router class is responsible for routing an HTTP request to * an assigned callback function. The Router tries to match the @@ -63,9 +66,10 @@ class Router * @param string $pattern URL pattern to match * @param callable $callback Callback function * @param bool $pass_route Pass the matching route object to the callback + * @param string $route_alias Alias for the route * @return void */ - public function map(string $pattern, callable $callback, bool $pass_route = false): void + public function map(string $pattern, callable $callback, bool $pass_route = false, string $route_alias = ''): void { $url = trim($pattern); $methods = ['*']; @@ -76,7 +80,7 @@ class Router $methods = explode('|', $method); } - $this->routes[] = new Route($this->group_prefix.$url, $callback, $methods, $pass_route); + $this->routes[] = new Route($this->group_prefix.$url, $callback, $methods, $pass_route, $route_alias); } /** @@ -173,6 +177,42 @@ class Router return false; } + /** + * Gets the URL for a given route alias + * + * @param string $alias the alias to match + * @param array $params the parameters to pass to the route + * @return string + */ + public function getUrlByAlias(string $alias, array $params = []): string { + while ($route = $this->current()) { + if ($route->matchAlias($alias)) { + return $route->hydrateUrl($params); + } + $this->next(); + } + + throw new Exception('No route found with alias: ' . $alias); + } + + /** + * Rewinds the current route index. + */ + public function rewind(): void + { + $this->index = 0; + } + + /** + * Checks if more routes can be iterated. + * + * @return bool More routes + */ + public function valid(): bool + { + return isset($this->routes[$this->index]); + } + /** * Gets the current route. * diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 5cc222d..a970990 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -461,4 +461,54 @@ class RouterTest extends PHPUnit\Framework\TestCase $this->request->method = 'POST'; $this->check('123abc'); } + + public function testRewindAndValid() { + $this->router->map('/path1', [$this, 'ok']); + $this->router->map('/path2', [$this, 'ok']); + $this->router->map('/path3', [$this, 'ok']); + + $this->router->next(); + $this->router->next(); + $result = $this->router->valid(); + $this->assertTrue($result); + $this->router->next(); + $result = $this->router->valid(); + $this->assertFalse($result); + + $this->router->rewind(); + $result = $this->router->valid(); + $this->assertTrue($result); + + } + + public function testGetUrlByAliasNoMatches() { + $this->router->map('/path1', [$this, 'ok'], false, 'path1'); + $this->expectException(\Exception::class); + $this->expectExceptionMessage('No route found with alias: path2'); + $this->router->getUrlByAlias('path2'); + } + + public function testGetUrlByAliasNoParams() { + $this->router->map('/path1', [$this, 'ok'], false, 'path1'); + $url = $this->router->getUrlByAlias('path1'); + $this->assertEquals('/path1', $url); + } + + public function testGetUrlByAliasSimpleParams() { + $this->router->map('/path1/@id', [$this, 'ok'], false, 'path1'); + $url = $this->router->getUrlByAlias('path1', ['id' => 123]); + $this->assertEquals('/path1/123', $url); + } + + public function testGetUrlByAliasMultipleParams() { + $this->router->map('/path1/@id/@name', [$this, 'ok'], false, 'path1'); + $url = $this->router->getUrlByAlias('path1', ['id' => 123, 'name' => 'abc']); + $this->assertEquals('/path1/123/abc', $url); + } + + public function testGetUrlByAliasMultipleComplexParams() { + $this->router->map('/path1/@id:[0-9]+/@name:[a-zA-Z0-9]{5}', [$this, 'ok'], false, 'path1'); + $url = $this->router->getUrlByAlias('path1', ['id' => '123', 'name' => 'abc']); + $this->assertEquals('/path1/123/abc', $url); + } } From fd4ce43c44a07e275a6fa5eb0e6a3a38d224fb05 Mon Sep 17 00:00:00 2001 From: n0nag0n Date: Wed, 10 Jan 2024 23:39:10 -0700 Subject: [PATCH 2/6] added route alias to engine/flight --- flight/Engine.php | 30 ++++++++++++++++++++++-------- flight/Flight.php | 13 +++++++------ tests/EngineTest.php | 7 +++++++ tests/FlightTest.php | 6 ++++++ 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/flight/Engine.php b/flight/Engine.php index 2628a11..44affc1 100644 --- a/flight/Engine.php +++ b/flight/Engine.php @@ -32,13 +32,14 @@ use Throwable; * @method void halt(int $code = 200, string $message = '') Stops processing and returns a given response. * * Routing - * @method void route(string $pattern, callable $callback, bool $pass_route = false) Routes a URL to a callback function. + * @method void route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a URL to a callback function with all applicable methods * @method void group(string $pattern, callable $callback) Groups a set of routes together under a common prefix. - * @method void post(string $pattern, callable $callback, bool $pass_route = false) Routes a POST URL to a callback function. - * @method void put(string $pattern, callable $callback, bool $pass_route = false) Routes a PUT URL to a callback function. - * @method void patch(string $pattern, callable $callback, bool $pass_route = false) Routes a PATCH URL to a callback function. - * @method void delete(string $pattern, callable $callback, bool $pass_route = false) Routes a DELETE URL to a callback function. + * @method void post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a POST URL to a callback function. + * @method void put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a PUT URL to a callback function. + * @method void patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a PATCH URL to a callback function. + * @method void delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a DELETE URL to a callback function. * @method Router router() Gets router + * @method string getUrl(string $alias) Gets a url from an alias * * Views * @method void render(string $file, array $data = null, string $key = null) Renders template @@ -151,7 +152,7 @@ class Engine $methods = [ 'start', 'stop', 'route', 'halt', 'error', 'notFound', 'render', 'redirect', 'etag', 'lastModified', 'json', 'jsonp', - 'post', 'put', 'patch', 'delete', 'group', + 'post', 'put', 'patch', 'delete', 'group', 'getUrl', ]; foreach ($methods as $name) { $this->dispatcher->set($name, [$this, '_' . $name]); @@ -462,10 +463,11 @@ class Engine * @param string $pattern URL pattern to match * @param callable $callback Callback function * @param bool $pass_route Pass the matching route object to the callback + * @param string $alias the alias for the route */ - public function _route(string $pattern, callable $callback, bool $pass_route = false): void + public function _route(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): void { - $this->router()->map($pattern, $callback, $pass_route); + $this->router()->map($pattern, $callback, $pass_route, $alias); } /** @@ -701,4 +703,16 @@ class Engine $this->halt(304); } } + + /** + * Gets a url from an alias that's supplied. + * + * @param string $alias the route alias + * @param array the params for the route if applicable + * @return string + */ + public function _getUrl(string $alias, array $params = []): string + { + return $this->router()->getUrlByAlias($alias, $params); + } } diff --git a/flight/Flight.php b/flight/Flight.php index b1a9b1f..399b151 100644 --- a/flight/Flight.php +++ b/flight/Flight.php @@ -23,13 +23,14 @@ use flight\template\View; * @method static void stop() Stops the framework and sends a response. * @method static void halt(int $code = 200, string $message = '') Stop the framework with an optional status code and message. * - * @method static void route(string $pattern, callable $callback, bool $pass_route = false) Maps a URL pattern to a callback. + * @method static void route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Maps a URL pattern to a callback with all applicable methods. * @method static void group(string $pattern, callable $callback) Groups a set of routes together under a common prefix. - * @method void post(string $pattern, callable $callback, bool $pass_route = false) Routes a POST URL to a callback function. - * @method void put(string $pattern, callable $callback, bool $pass_route = false) Routes a PUT URL to a callback function. - * @method void patch(string $pattern, callable $callback, bool $pass_route = false) Routes a PATCH URL to a callback function. - * @method void delete(string $pattern, callable $callback, bool $pass_route = false) Routes a DELETE URL to a callback function. - * @method static Router router() Returns Router instance. + * @method void post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a POST URL to a callback function. + * @method void put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a PUT URL to a callback function. + * @method void patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a PATCH URL to a callback function. + * @method void delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a DELETE URL to a callback function. + * @method static Router router() Returns Router instance. + * @method string getUrl(string $alias) Gets a url from an alias * * @method static void map(string $name, callable $callback) Creates a custom framework method. * diff --git a/tests/EngineTest.php b/tests/EngineTest.php index cf2b13a..e1b9aaf 100644 --- a/tests/EngineTest.php +++ b/tests/EngineTest.php @@ -265,4 +265,11 @@ class EngineTest extends PHPUnit\Framework\TestCase $this->assertEquals('Fri, 13 Feb 2009 23:31:30 GMT', $engine->response()->headers()['Last-Modified']); $this->assertEquals(304, $engine->response()->status()); } + + public function testGetUrl() { + $engine = new Engine; + $engine->route('/path1/@param:[0-9]{3}', function() { echo 'I win'; }, false, 'path1'); + $url = $engine->getUrl('path1', [ 'param' => 123 ]); + $this->assertEquals('/path1/123', $url); + } } diff --git a/tests/FlightTest.php b/tests/FlightTest.php index 12b2772..29e90ca 100644 --- a/tests/FlightTest.php +++ b/tests/FlightTest.php @@ -187,4 +187,10 @@ class FlightTest extends PHPUnit\Framework\TestCase $this->expectOutputString('test delete'); Flight::start(); } + + public function testGetUrl() { + Flight::route('/path1/@param:[a-zA-Z0-9]{2,3}', function() { echo 'I win'; }, false, 'path1'); + $url = Flight::getUrl('path1', [ 'param' => 123 ]); + $this->assertEquals('/path1/123', $url); + } } From 387f15bd8bf99df0c1b8c96b0a40c06d402187cc Mon Sep 17 00:00:00 2001 From: Austin Collier Date: Thu, 11 Jan 2024 12:07:57 -0700 Subject: [PATCH 3/6] fixed optional params with aliasing --- flight/net/Route.php | 7 ++++++- tests/RouterTest.php | 30 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/flight/net/Route.php b/flight/net/Route.php index b3758c3..4ff28d7 100644 --- a/flight/net/Route.php +++ b/flight/net/Route.php @@ -178,11 +178,16 @@ final class Route * @return string */ public function hydrateUrl(array $params = []): string { - $url = preg_replace_callback("/(?:@([a-zA-Z]+)(?:\:([^\/]+))?)?/i", function($match) use ($params) { + $url = preg_replace_callback("/(?:@([a-zA-Z]+)(?:\:([^\/]+))?\)*)/i", function($match) use ($params) { if(isset($match[1]) && isset($params[$match[1]])) { return $params[$match[1]]; } }, $this->pattern); + + // catches potential optional parameter + $url = str_replace('(/', '/', $url); + // trim any trailing slashes + $url = rtrim($url, '/'); return $url; } } diff --git a/tests/RouterTest.php b/tests/RouterTest.php index a970990..821d358 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -500,6 +500,18 @@ class RouterTest extends PHPUnit\Framework\TestCase $this->assertEquals('/path1/123', $url); } + public function testGetUrlByAliasSimpleOptionalParamsWithParam() { + $this->router->map('/path1(/@id)', [$this, 'ok'], false, 'path1'); + $url = $this->router->getUrlByAlias('path1', ['id' => 123]); + $this->assertEquals('/path1/123', $url); + } + + public function testGetUrlByAliasSimpleOptionalParamsNoParam() { + $this->router->map('/path1(/@id)', [$this, 'ok'], false, 'path1'); + $url = $this->router->getUrlByAlias('path1'); + $this->assertEquals('/path1', $url); + } + public function testGetUrlByAliasMultipleParams() { $this->router->map('/path1/@id/@name', [$this, 'ok'], false, 'path1'); $url = $this->router->getUrlByAlias('path1', ['id' => 123, 'name' => 'abc']); @@ -511,4 +523,22 @@ class RouterTest extends PHPUnit\Framework\TestCase $url = $this->router->getUrlByAlias('path1', ['id' => '123', 'name' => 'abc']); $this->assertEquals('/path1/123/abc', $url); } + + public function testGetUrlByAliasMultipleComplexOptionalParamsMissingOne() { + $this->router->map('/path1(/@id:[0-9]+(/@name(/@crazy:[a-z]{5})))', [$this, 'ok'], false, 'path1'); + $url = $this->router->getUrlByAlias('path1', ['id' => '123', 'name' => 'abc']); + $this->assertEquals('/path1/123/abc', $url); + } + + public function testGetUrlByAliasMultipleComplexOptionalParamsAllParams() { + $this->router->map('/path1(/@id:[0-9]+(/@name(/@crazy:[a-z]{5})))', [$this, 'ok'], false, 'path1'); + $url = $this->router->getUrlByAlias('path1', ['id' => '123', 'name' => 'abc', 'crazy' => 'xyz']); + $this->assertEquals('/path1/123/abc/xyz', $url); + } + + public function testGetUrlByAliasMultipleComplexOptionalParamsNoParams() { + $this->router->map('/path1(/@id:[0-9]+(/@name(/@crazy:[a-z]{5})))', [$this, 'ok'], false, 'path1'); + $url = $this->router->getUrlByAlias('path1'); + $this->assertEquals('/path1', $url); + } } From 74d2fd70025cbade99b33d50651540698673ee7a Mon Sep 17 00:00:00 2001 From: Austin Collier Date: Thu, 11 Jan 2024 12:21:55 -0700 Subject: [PATCH 4/6] Fixes for group routing --- flight/Flight.php | 12 ++++++------ flight/net/Router.php | 27 ++++++++++++++++----------- tests/FlightTest.php | 27 +++++++++++++++++++++++++++ tests/RouterTest.php | 9 +++++++++ 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/flight/Flight.php b/flight/Flight.php index 399b151..fd3772c 100644 --- a/flight/Flight.php +++ b/flight/Flight.php @@ -25,12 +25,12 @@ use flight\template\View; * * @method static void route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Maps a URL pattern to a callback with all applicable methods. * @method static void group(string $pattern, callable $callback) Groups a set of routes together under a common prefix. - * @method void post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a POST URL to a callback function. - * @method void put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a PUT URL to a callback function. - * @method void patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a PATCH URL to a callback function. - * @method void delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a DELETE URL to a callback function. - * @method static Router router() Returns Router instance. - * @method string getUrl(string $alias) Gets a url from an alias + * @method static void post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a POST URL to a callback function. + * @method static void put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a PUT URL to a callback function. + * @method static void patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a PATCH URL to a callback function. + * @method static void delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') Routes a DELETE URL to a callback function. + * @method static Router router() Returns Router instance. + * @method static string getUrl(string $alias) Gets a url from an alias * * @method static void map(string $name, callable $callback) Creates a custom framework method. * diff --git a/flight/net/Router.php b/flight/net/Router.php index f54d898..f825616 100644 --- a/flight/net/Router.php +++ b/flight/net/Router.php @@ -45,7 +45,7 @@ class Router /** * Gets mapped routes. * - * @return array Array of routes + * @return array Array of routes */ public function getRoutes(): array { @@ -89,10 +89,11 @@ class Router * @param string $pattern URL pattern to match * @param callable $callback Callback function * @param bool $pass_route Pass the matching route object to the callback + * @param string $alias Alias for the route * @return void */ - public function get(string $pattern, callable $callback, bool $pass_route = false): void { - $this->map('GET ' . $pattern, $callback, $pass_route); + public function get(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): void { + $this->map('GET ' . $pattern, $callback, $pass_route, $alias); } /** @@ -101,10 +102,11 @@ class Router * @param string $pattern URL pattern to match * @param callable $callback Callback function * @param bool $pass_route Pass the matching route object to the callback + * @param string $alias Alias for the route * @return void */ - public function post(string $pattern, callable $callback, bool $pass_route = false): void { - $this->map('POST ' . $pattern, $callback, $pass_route); + public function post(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): void { + $this->map('POST ' . $pattern, $callback, $pass_route, $alias); } /** @@ -113,10 +115,11 @@ class Router * @param string $pattern URL pattern to match * @param callable $callback Callback function * @param bool $pass_route Pass the matching route object to the callback + * @param string $alias Alias for the route * @return void */ - public function put(string $pattern, callable $callback, bool $pass_route = false): void { - $this->map('PUT ' . $pattern, $callback, $pass_route); + public function put(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): void { + $this->map('PUT ' . $pattern, $callback, $pass_route, $alias); } /** @@ -125,10 +128,11 @@ class Router * @param string $pattern URL pattern to match * @param callable $callback Callback function * @param bool $pass_route Pass the matching route object to the callback + * @param string $alias Alias for the route * @return void */ - public function patch(string $pattern, callable $callback, bool $pass_route = false): void { - $this->map('PATCH ' . $pattern, $callback, $pass_route); + public function patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): void { + $this->map('PATCH ' . $pattern, $callback, $pass_route, $alias); } /** @@ -137,10 +141,11 @@ class Router * @param string $pattern URL pattern to match * @param callable $callback Callback function * @param bool $pass_route Pass the matching route object to the callback + * @param string $alias Alias for the route * @return void */ - public function delete(string $pattern, callable $callback, bool $pass_route = false): void { - $this->map('DELETE ' . $pattern, $callback, $pass_route); + public function delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = ''): void { + $this->map('DELETE ' . $pattern, $callback, $pass_route, $alias); } /** diff --git a/tests/FlightTest.php b/tests/FlightTest.php index 29e90ca..7a3df8b 100644 --- a/tests/FlightTest.php +++ b/tests/FlightTest.php @@ -193,4 +193,31 @@ class FlightTest extends PHPUnit\Framework\TestCase $url = Flight::getUrl('path1', [ 'param' => 123 ]); $this->assertEquals('/path1/123', $url); } + + public function testRouteGetUrlWithGroupSimpleParams() { + Flight::group('/path1/@id', function() { + Flight::route('/@name', function() { echo 'whatever'; }, false, 'path1'); + }); + $url = Flight::getUrl('path1', ['id' => 123, 'name' => 'abc']); + + $this->assertEquals('/path1/123/abc', $url); + } + + public function testRouteGetUrlNestedGroups() { + Flight::group('/user', function () { + Flight::group('/all_users', function () { + Flight::group('/check_user', function () { + Flight::group('/check_one', function () { + Flight::route("/normalpath", function () { + echo "normalpath"; + },false,"normalpathalias"); + }); + }); + }); + }); + + $url = Flight::getUrl('normalpathalias'); + + $this->assertEquals('/user/all_users/check_user/check_one/normalpath', $url); + } } diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 821d358..c827302 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -541,4 +541,13 @@ class RouterTest extends PHPUnit\Framework\TestCase $url = $this->router->getUrlByAlias('path1'); $this->assertEquals('/path1', $url); } + + public function testGetUrlByAliasWithGroupSimpleParams() { + $this->router->group('/path1/@id', function($router) { + $router->get('/@name', [$this, 'ok'], false, 'path1'); + }); + $url = $this->router->getUrlByAlias('path1', ['id' => 123, 'name' => 'abc']); + + $this->assertEquals('/path1/123/abc', $url); + } } From 8bd7fd035bca1256bef89a891cab4f1e27668210 Mon Sep 17 00:00:00 2001 From: Austin Collier Date: Thu, 11 Jan 2024 12:24:19 -0700 Subject: [PATCH 5/6] fixed params with numbers --- flight/net/Route.php | 2 +- tests/RouterTest.php | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/flight/net/Route.php b/flight/net/Route.php index 4ff28d7..2d14bd2 100644 --- a/flight/net/Route.php +++ b/flight/net/Route.php @@ -178,7 +178,7 @@ final class Route * @return string */ public function hydrateUrl(array $params = []): string { - $url = preg_replace_callback("/(?:@([a-zA-Z]+)(?:\:([^\/]+))?\)*)/i", function($match) use ($params) { + $url = preg_replace_callback("/(?:@([a-zA-Z0-9]+)(?:\:([^\/]+))?\)*)/i", function($match) use ($params) { if(isset($match[1]) && isset($params[$match[1]])) { return $params[$match[1]]; } diff --git a/tests/RouterTest.php b/tests/RouterTest.php index c827302..42e3781 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -500,18 +500,36 @@ class RouterTest extends PHPUnit\Framework\TestCase $this->assertEquals('/path1/123', $url); } + public function testGetUrlByAliasSimpleParamsWithNumber() { + $this->router->map('/path1/@id1', [$this, 'ok'], false, 'path1'); + $url = $this->router->getUrlByAlias('path1', ['id1' => 123]); + $this->assertEquals('/path1/123', $url); + } + public function testGetUrlByAliasSimpleOptionalParamsWithParam() { $this->router->map('/path1(/@id)', [$this, 'ok'], false, 'path1'); $url = $this->router->getUrlByAlias('path1', ['id' => 123]); $this->assertEquals('/path1/123', $url); } + public function testGetUrlByAliasSimpleOptionalParamsWithNumberWithParam() { + $this->router->map('/path1(/@id1)', [$this, 'ok'], false, 'path1'); + $url = $this->router->getUrlByAlias('path1', ['id1' => 123]); + $this->assertEquals('/path1/123', $url); + } + public function testGetUrlByAliasSimpleOptionalParamsNoParam() { $this->router->map('/path1(/@id)', [$this, 'ok'], false, 'path1'); $url = $this->router->getUrlByAlias('path1'); $this->assertEquals('/path1', $url); } + public function testGetUrlByAliasSimpleOptionalParamsWithNumberNoParam() { + $this->router->map('/path1(/@id1)', [$this, 'ok'], false, 'path1'); + $url = $this->router->getUrlByAlias('path1'); + $this->assertEquals('/path1', $url); + } + public function testGetUrlByAliasMultipleParams() { $this->router->map('/path1/@id/@name', [$this, 'ok'], false, 'path1'); $url = $this->router->getUrlByAlias('path1', ['id' => 123, 'name' => 'abc']); @@ -524,6 +542,12 @@ class RouterTest extends PHPUnit\Framework\TestCase $this->assertEquals('/path1/123/abc', $url); } + public function testGetUrlByAliasMultipleComplexParamsWithNumbers() { + $this->router->map('/path1/@5id:[0-9]+/@n1ame:[a-zA-Z0-9]{5}', [$this, 'ok'], false, 'path1'); + $url = $this->router->getUrlByAlias('path1', ['5id' => '123', 'n1ame' => 'abc']); + $this->assertEquals('/path1/123/abc', $url); + } + public function testGetUrlByAliasMultipleComplexOptionalParamsMissingOne() { $this->router->map('/path1(/@id:[0-9]+(/@name(/@crazy:[a-z]{5})))', [$this, 'ok'], false, 'path1'); $url = $this->router->getUrlByAlias('path1', ['id' => '123', 'name' => 'abc']); From c7a143db0425b9b6a89e39f95a87ebfeff0f7e31 Mon Sep 17 00:00:00 2001 From: Austin Collier Date: Thu, 11 Jan 2024 15:55:30 -0700 Subject: [PATCH 6/6] fixed issue with multiline possibilities in url query --- flight/net/Route.php | 2 +- tests/RouterTest.php | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/flight/net/Route.php b/flight/net/Route.php index 2d14bd2..63b1bf0 100644 --- a/flight/net/Route.php +++ b/flight/net/Route.php @@ -135,7 +135,7 @@ final class Route } // Attempt to match route and named parameters - if (preg_match('#^' . $regex . '(?:\?.*)?$#' . (($case_sensitive) ? '' : 'i'), $url, $matches)) { + if (preg_match('#^' . $regex . '(?:\?[\s\S]*)?$#' . (($case_sensitive) ? '' : 'i'), $url, $matches)) { foreach ($ids as $k => $v) { $this->params[$k] = (\array_key_exists($k, $matches)) ? urldecode($matches[$k]) : null; } diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 42e3781..6125f53 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -255,6 +255,13 @@ class RouterTest extends PHPUnit\Framework\TestCase $this->check('OK'); } + public function testRouteWithLongQueryParamWithMultilineEncoded() + { + $this->router->map('GET /api/intune/hey', [$this, 'ok']); + $this->request->url = '/api/intune/hey?error=access_denied&error_description=AADSTS65004%3a+User+declined+to+consent+to+access+the+app.%0d%0aTrace+ID%3a+747c0cc1-ccbd-4e53-8e2f-48812eb24100%0d%0aCorrelation+ID%3a+362e3cb3-20ef-400b-904e-9983bd989184%0d%0aTimestamp%3a+2022-09-08+09%3a58%3a12Z&error_uri=https%3a%2f%2flogin.microsoftonline.com%2ferror%3fcode%3d65004&admin_consent=True&state=x2EUE0fcSj#'; + $this->check('OK'); + } + // Check if route object was passed public function testRouteObjectPassing() {