Skip to content

Commit

Permalink
Tweaked the structure of the route array: 'id' must now be included i…
Browse files Browse the repository at this point in the history
…n the array.
  • Loading branch information
danbettles committed Nov 23, 2022
1 parent 0b6b80f commit a5596ec
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 120 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

No unreleased changes.

## [2.0.0] - 2022-11-23

### Changed

- Changed the structure of the route array: `id` must now be included in the array.

## [1.0.1] - 2022-10-22

### Added
Expand All @@ -22,6 +28,7 @@ No unreleased changes.

First stable release.

[unreleased]: https://github.com/danbettles/marigold/compare/v1.0.1...HEAD
[unreleased]: https://github.com/danbettles/marigold/compare/v2.0.0...HEAD
[2.0.0]: https://github.com/danbettles/marigold/compare/v1.0.1...v2.0.0
[1.0.1]: https://github.com/danbettles/marigold/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/danbettles/marigold/releases/tag/v1.0.0
69 changes: 42 additions & 27 deletions src/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,40 +30,42 @@
* An array of routes looks like:
*
* [
* 'posts' => [
* [
* 'id' => 'showBlogPost',
* 'path' => '/posts/{postId}',
* 'action' => ['foo', 'bar'],
* 'action' => ['FooBar', 'baz'],
* ],
* // ...
* ]
*
* The key of a route is its 'ID'. `action` can be anything: the name of a method; a callable; whatever's appropriate
* for the app.
* `action` can be anything: the name of a method; a callable; whatever's appropriate for the app.
*
* A matched route, the return value of `match()`, will have an additional element, `parameters`, containing the values
* of any parameters found in the path.
*/
class Router
{
/**
* @var array{path: null, action: null}>
* @var array{id: null, path: null, action: null}
*/
private const EMPTY_ROUTE = [
'id' => null,
'path' => null,
'action' => null,
];

/**
* @var array<string|int, array{path: string, action: mixed}>
* @var array<string, array{id: string, path: string, action: mixed}>
*/
private array $routes;

/**
* @var array<string|int, array<string, string>>
* @var array<string, array<string, string>>
*/
private array $placeholdersByRouteId = [];

/**
* @param array<string|int, array{path: string, action: mixed}> $routes
* @param array<array{id: string, path: string, action: mixed}> $routes
*/
public function __construct(array $routes)
{
Expand All @@ -76,8 +78,8 @@ private function countPathParts(string $path): int
}

/**
* @param array<string|int, array{path: string, action: mixed}> $routes
* @return array<string|int, array{path: string, action: mixed}>
* @param array<string, array{id: string, path: string, action: mixed}> $routes
* @return array<string, array{id: string, path: string, action: mixed}>
*/
private function eliminateUnmatchableRoutes(string $path, array $routes): array
{
Expand All @@ -91,7 +93,21 @@ private function eliminateUnmatchableRoutes(string $path, array $routes): array
}

/**
* @return array{path: string, action: mixed, parameters: string[]}|null
* @param array{id: string, path: string, action: mixed} $baseRoute
* @param array<string, string> $parameters
* @return array{id: string, path: string, action: mixed, parameters: array<string, string>}
*/
private function createMatchedRoute(
array $baseRoute,
array $parameters = []
): array {
$baseRoute['parameters'] = $parameters;

return $baseRoute;
}

/**
* @return array{id: string, path: string, action: mixed, parameters: array<string, string>}|null
* @throws OutOfBoundsException If there is no request URI in the server vars.
* @throws InvalidArgumentException If the request URI is invalid.
*/
Expand Down Expand Up @@ -126,9 +142,7 @@ public function match(HttpRequest $request): ?array
}

if ($path === $route['path']) {
$route['parameters'] = [];

return $route;
return $this->createMatchedRoute($route);
}
}

Expand All @@ -154,21 +168,21 @@ public function match(HttpRequest $request): ?array
continue;
}

$route['parameters'] = array_filter($pathParameterMatches, '\is_string', ARRAY_FILTER_USE_KEY);

return $route;
return $this->createMatchedRoute(
$route,
array_filter($pathParameterMatches, '\is_string', ARRAY_FILTER_USE_KEY)
);
}

return null;
}

/**
* @param string|int $routeId
* @param array<string, string|int> $parameters
* @throws OutOfBoundsException If the route does not exist.
* @throws InvalidArgumentException If parameter values were missing.
*/
public function generatePath($routeId, array $parameters = []): string
public function generatePath(string $routeId, array $parameters = []): string
{
if (!array_key_exists($routeId, $this->getRoutes())) {
throw new OutOfBoundsException("The route, `{$routeId}`, does not exist.");
Expand Down Expand Up @@ -199,7 +213,7 @@ public function generatePath($routeId, array $parameters = []): string
}

/**
* @param array<string|int, array{path: string, action: mixed}> $routes
* @param array<array{id: string, path: string, action: mixed}> $routes
* @throws InvalidArgumentException If there are no routes.
* @throws InvalidArgumentException If a route is missing elements.
*/
Expand All @@ -211,38 +225,39 @@ private function setRoutes(array $routes): self

$numExpectedRouteEls = count(self::EMPTY_ROUTE);

foreach ($routes as $id => $route) {
foreach ($routes as $i => $route) {
$filteredRoute = array_intersect_key($route, self::EMPTY_ROUTE);

// In reality, some routes may not be what we were hoping for, hence why we need to adjust PHPStan's
// expectations.
/** @phpstan-var mixed[] $filteredRoute */
if ($numExpectedRouteEls !== count($filteredRoute)) {
throw new InvalidArgumentException(
"Route `{$id}` is missing elements. Required: " . implode(', ', array_keys(self::EMPTY_ROUTE)) . '.'
"The route at index `{$i}` is missing elements. " .
'Required: ' . implode(', ', array_keys(self::EMPTY_ROUTE)) . '.'
);
}

/** @var array{path: string, action: mixed} $filteredRoute */
$this->routes[$id] = $filteredRoute;
$routeId = $route['id'];
/** @var array{id: string, path: string, action: mixed} $filteredRoute */
$this->routes[$routeId] = $filteredRoute;
}

return $this;
}

/**
* @return array<string|int, array{path: string, action: mixed}>
* @return array<string, array{id: string, path: string, action: mixed}>
*/
public function getRoutes(): array
{
return $this->routes;
}

/**
* @param string|int $routeId
* @return array<string, string>
*/
private function getRoutePlaceholders($routeId): array
private function getRoutePlaceholders(string $routeId): array
{
if (!array_key_exists($routeId, $this->placeholdersByRouteId)) {
$route = $this->getRoutes()[$routeId];
Expand Down
Loading

0 comments on commit a5596ec

Please sign in to comment.