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); + } }