Skip to content

Commit

Permalink
allow pathTo to append extra parameters as the path query
Browse files Browse the repository at this point in the history
  • Loading branch information
IngeniozIT committed Feb 28, 2024
1 parent adeb45b commit 782a55a
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 174 deletions.
4 changes: 2 additions & 2 deletions src/Exception/InvalidRouteParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ final class InvalidRouteParameter extends InvalidArgumentException
{
public function __construct(
string $routeName,
string $parameterName,
string $missingParameters,
string $pattern,
?Throwable $previous = null
) {
parent::__construct(
"Parameter '$parameterName' for route with name '$routeName' does not match the pattern '$pattern'.",
"Parameter '$missingParameters' for route with name '$routeName' does not match the pattern '$pattern'.",
previous: $previous,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@
use InvalidArgumentException;
use Throwable;

final class MissingRouteParameter extends InvalidArgumentException
final class MissingRouteParameters extends InvalidArgumentException
{
/**
* @param string[] $missingParameters
*/
public function __construct(
string $routeName,
string $parameterName,
array $missingParameters,
?Throwable $previous = null
) {
parent::__construct(
"Missing parameter '$parameterName' for route with name '$routeName'.",
"Missing parameters " . implode(', ', $missingParameters) . " for route with name '$routeName'.",
previous: $previous,
);
}
Expand Down
138 changes: 19 additions & 119 deletions src/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

namespace IngeniozIT\Router;

use Psr\Http\Message\ServerRequestInterface;

final readonly class Route
{
public const GET = 0b0000001;
Expand All @@ -24,7 +22,7 @@

public const ANY = 0b1111111;

private const METHODS = [
public const METHODS = [
'GET' => self::GET,
'POST' => self::POST,
'PUT' => self::PUT,
Expand All @@ -34,185 +32,87 @@
'OPTIONS' => self::OPTIONS,
];

/** @var array<string, string> */
public array $where;
public string $path;
public bool $hasParameters;

/**
* @param array<string, string> $where
* @param array<string, string> $with
*/
public static function get(string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): self
public static function get(string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): RouteElement
{
return new self(self::GET, $path, $callback, $where, $with, $name);
return new RouteElement(self::GET, $path, $callback, $where, $with, $name);
}

/**
* @param array<string, string> $where
* @param array<string, string> $with
*/
public static function post(string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): self
public static function post(string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): RouteElement
{
return new self(self::POST, $path, $callback, $where, $with, $name);
return new RouteElement(self::POST, $path, $callback, $where, $with, $name);
}

/**
* @param array<string, string> $where
* @param array<string, string> $with
*/
public static function put(string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): self
public static function put(string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): RouteElement
{
return new self(self::PUT, $path, $callback, $where, $with, $name);
return new RouteElement(self::PUT, $path, $callback, $where, $with, $name);
}

/**
* @param array<string, string> $where
* @param array<string, string> $with
*/
public static function patch(string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): self
public static function patch(string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): RouteElement
{
return new self(self::PATCH, $path, $callback, $where, $with, $name);
return new RouteElement(self::PATCH, $path, $callback, $where, $with, $name);
}

/**
* @param array<string, string> $where
* @param array<string, string> $with
*/
public static function delete(string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): self
public static function delete(string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): RouteElement
{
return new self(self::DELETE, $path, $callback, $where, $with, $name);
return new RouteElement(self::DELETE, $path, $callback, $where, $with, $name);
}

/**
* @param array<string, string> $where
* @param array<string, string> $with
*/
public static function head(string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): self
public static function head(string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): RouteElement
{
return new self(self::HEAD, $path, $callback, $where, $with, $name);
return new RouteElement(self::HEAD, $path, $callback, $where, $with, $name);
}

/**
* @param array<string, string> $where
* @param array<string, string> $with
*/
public static function options(string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): self
public static function options(string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): RouteElement
{
return new self(self::OPTIONS, $path, $callback, $where, $with, $name);
return new RouteElement(self::OPTIONS, $path, $callback, $where, $with, $name);
}

/**
* @param array<string, string> $where
* @param array<string, string> $with
*/
public static function any(string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): self
public static function any(string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): RouteElement
{
return new self(self::ANY, $path, $callback, $where, $with, $name);
return new RouteElement(self::ANY, $path, $callback, $where, $with, $name);
}

/**
* @param string[] $methods
* @param array<string, string> $where
* @param array<string, string> $with
*/
public static function some(array $methods, string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): self
public static function some(array $methods, string $path, mixed $callback, array $where = [], array $with = [], ?string $name = null): RouteElement
{
$method = array_reduce($methods, fn($carry, $methodString) => $carry | self::METHODS[strtoupper($methodString)], 0);

return new self($method, $path, $callback, $where, $with, $name);
}

/**
* @param array<string, string> $where
* @param array<string, string> $with
*/
public function __construct(
public int $method,
string $path,
public mixed $callback,
array $where = [],
public array $with = [],
public ?string $name = null,
) {
$this->hasParameters = str_contains($path, '{');
[$this->where, $this->path] = $this->extractPatterns($where, $path);
}

private function extractPatterns(array $where, string $path): array
{
if ($this->hasParameters && str_contains($path, ':') && preg_match_all('#{(\w+):([^}]+)}#', $path, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$path = str_replace($match[0], '{' . $match[1] . '}', $path);
$where[$match[1]] = $match[2];
}
}
return [$where, $path];
}

/**
* @return false|array<string, string>
*/
public function match(ServerRequestInterface $request): false|array
{
if (!$this->httpMethodMatches($request->getMethod())) {
return false;
}

$path = $request->getUri()->getPath();

if (!$this->hasParameters) {
return $path === $this->path ? [] : false;
}

$parameters = $this->extractParametersFromPath($this->path);
$extractedParameters = $this->extractParametersValue($parameters, $path);
return $extractedParameters === [] ? false : $extractedParameters;
}

private function httpMethodMatches(string $method): bool
{
return ($this->method & self::METHODS[$method]) !== 0;
}

/**
* @return string[][]
*/
private function extractParametersFromPath(string $path): array
{
preg_match_all('/{([^:]+)}/U', $path, $matches, PREG_SET_ORDER);
return $matches;
}

/**
* @param string[][] $parameters
* @return array<string, string>
*/
private function extractParametersValue(array $parameters, string $path): array
{
preg_match($this->buildRegex($parameters), $path, $parameters);
return array_filter($parameters, 'is_string', ARRAY_FILTER_USE_KEY);
}

/**
* @param string[][] $parameters
*/
private function buildRegex(array $parameters): string
{
$regex = '#' . preg_quote($this->path, '#') . '#';
foreach ($parameters as $parameter) {
$regex = str_replace(
preg_quote($parameter[0], '#'),
'(?<' . $parameter[1] . '>' . $this->parameterPattern($parameter[1]) . ')',
$regex
);
}

return $regex;
}

public function parameterPattern(string $parameterName): string
{
return $this->where[$parameterName] ?? '[^/]+';
return new RouteElement($method, $path, $callback, $where, $with, $name);
}
}
Loading

0 comments on commit 782a55a

Please sign in to comment.