Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 29 additions & 28 deletions src/DispatchContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use Respect\Rest\Routines\Routinable;
use Throwable;

use function assert;
use function rawurldecode;
use function rtrim;
use function set_error_handler;
Expand Down Expand Up @@ -121,17 +120,19 @@ public function prepareResponse(int $status, array $headers = []): void
/** Generates the PSR-7 response from the current route */
public function response(): ResponseInterface|null
{
try {
if (!$this->route instanceof AbstractRoute) {
if ($this->responseDraft !== null) {
return $this->finalizeResponse($this->responseDraft);
}

return null;
if (!$this->route instanceof AbstractRoute) {
if ($this->responseDraft !== null) {
return $this->finalizeResponse($this->responseDraft);
}

$errorHandler = $this->prepareForErrorForwards();
$preRoutineResult = $this->routinePipeline()->processBy($this);
return null;
}

$route = $this->route;

try {
$errorHandler = $this->prepareForErrorForwards($route);
$preRoutineResult = $this->routinePipeline()->processBy($this, $route);

if ($preRoutineResult !== null) {
if ($preRoutineResult instanceof AbstractRoute) {
Expand All @@ -147,22 +148,22 @@ public function response(): ResponseInterface|null
}
}

$rawResult = $this->route->dispatchTarget($this->method(), $this->params, $this);
$rawResult = $route->dispatchTarget($this->method(), $this->params, $this);

if ($rawResult instanceof AbstractRoute) {
return $this->forward($rawResult);
}

$processedResult = $this->routinePipeline()->processThrough($this, $rawResult);
$errorResponse = $this->forwardErrors($errorHandler);
$processedResult = $this->routinePipeline()->processThrough($this, $route, $rawResult);
$errorResponse = $this->forwardErrors($errorHandler, $route);

if ($errorResponse !== null) {
return $errorResponse;
}

return $this->finalizeResponse($processedResult);
} catch (Throwable $e) {
$exceptionResponse = $this->catchExceptions($e);
$exceptionResponse = $this->catchExceptions($e, $route);
if ($exceptionResponse === null) {
throw $e;
}
Expand All @@ -172,10 +173,14 @@ public function response(): ResponseInterface|null
}

/** @param array<int, mixed> $params */
public function routineCall(string $type, string $method, Routinable $routine, array &$params): mixed
{
assert($this->route !== null);
$reflection = $this->route->getTargetReflection($method);
public function routineCall(
string $type,
string $method,
Routinable $routine,
array &$params,
AbstractRoute $route,
): mixed {
$reflection = $route->getTargetReflection($method);

$callbackParameters = [];

Expand Down Expand Up @@ -212,10 +217,9 @@ public function setResponder(Responder $responder): void
}

/** @return callable|null The previous error handler, or null */
protected function prepareForErrorForwards(): callable|null
protected function prepareForErrorForwards(AbstractRoute $route): callable|null
{
assert($this->route !== null);
foreach ($this->route->sideRoutes as $sideRoute) {
foreach ($route->sideRoutes as $sideRoute) {
if ($sideRoute instanceof Routes\Error) {
return set_error_handler(
static function (
Expand All @@ -235,15 +239,13 @@ static function (
return null;
}

protected function forwardErrors(callable|null $errorHandler): ResponseInterface|null
protected function forwardErrors(callable|null $errorHandler, AbstractRoute $route): ResponseInterface|null
{
if ($errorHandler !== null) {
set_error_handler($errorHandler);
}

assert($this->route !== null);

foreach ($this->route->sideRoutes as $sideRoute) {
foreach ($route->sideRoutes as $sideRoute) {
if ($sideRoute instanceof Routes\Error && $sideRoute->errors) {
return $this->forward($sideRoute);
}
Expand All @@ -252,10 +254,9 @@ protected function forwardErrors(callable|null $errorHandler): ResponseInterface
return null;
}

protected function catchExceptions(Throwable $e): ResponseInterface|null
protected function catchExceptions(Throwable $e, AbstractRoute $route): ResponseInterface|null
{
assert($this->route !== null);
foreach ($this->route->sideRoutes as $sideRoute) {
foreach ($route->sideRoutes as $sideRoute) {
if (!$sideRoute instanceof Routes\Exception) {
continue;
}
Expand Down
26 changes: 16 additions & 10 deletions src/DispatchEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Respect\Rest;

use Closure;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
Expand All @@ -27,10 +28,12 @@ final class DispatchEngine implements RequestHandlerInterface
{
private RoutinePipeline $routinePipeline;

/** @param (Closure(DispatchContext): void)|null $onContextReady */
public function __construct(
private Router $router,
private RouteProvider $routeProvider,
private ResponseFactoryInterface $responseFactory,
private StreamFactoryInterface $streamFactory,
private Closure|null $onContextReady = null,
) {
$this->routinePipeline = new RoutinePipeline();
}
Expand Down Expand Up @@ -59,7 +62,10 @@ public function handle(ServerRequestInterface $request): ResponseInterface

public function dispatchContext(DispatchContext $context): DispatchContext
{
$this->router->context = $context;
if ($this->onContextReady !== null) {
($this->onContextReady)($context);
}

$context->setRoutinePipeline($this->routinePipeline);

if (!$this->isRoutelessDispatch($context) && $context->route === null) {
Expand Down Expand Up @@ -104,7 +110,7 @@ private function isRoutelessDispatch(DispatchContext $context): bool
return false;
}

$allowedMethods = $this->getAllowedMethods($this->router->getRoutes());
$allowedMethods = $this->getAllowedMethods($this->routeProvider->getRoutes());

if ($allowedMethods) {
$context->prepareResponse(
Expand All @@ -120,7 +126,7 @@ private function isRoutelessDispatch(DispatchContext $context): bool

private function routeDispatch(DispatchContext $context): void
{
$this->applyVirtualHost($context);
$this->applyBasePath($context);

$matchedByPath = $this->getMatchedRoutesByPath($context);
/** @var array<int, AbstractRoute> $matchedArray */
Expand All @@ -140,16 +146,16 @@ private function routeDispatch(DispatchContext $context): void
}
}

private function applyVirtualHost(DispatchContext $context): void
private function applyBasePath(DispatchContext $context): void
{
$virtualHost = $this->router->getVirtualHost();
if ($virtualHost === null) {
$basePath = $this->routeProvider->getBasePath();
if ($basePath === null) {
return;
}

$context->setPath(
preg_replace(
'#^' . preg_quote($virtualHost) . '#',
'#^' . preg_quote($basePath) . '#',
'',
$context->path(),
) ?? $context->path(),
Expand All @@ -174,7 +180,7 @@ private function getMatchedRoutesByPath(DispatchContext $context): SplObjectStor
/** @var SplObjectStorage<AbstractRoute, array<int, mixed>> $matched */
$matched = new SplObjectStorage();

foreach ($this->router->getRoutes() as $route) {
foreach ($this->routeProvider->getRoutes() as $route) {
$params = [];
if (!$this->matchRoute($context, $route, $params)) {
continue;
Expand Down Expand Up @@ -297,7 +303,7 @@ private function routineMatch(
$tempParams = $matchedByPath[$route];
$context->clearResponseMeta();
$context->route = $route;
if ($this->routinePipeline->matches($context, $tempParams)) {
if ($this->routinePipeline->matches($context, $route, $tempParams)) {
return $this->configureContext(
$context,
$route,
Expand Down
15 changes: 15 additions & 0 deletions src/RouteProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Respect\Rest;

use Respect\Rest\Routes\AbstractRoute;

interface RouteProvider
{
/** @return array<int, AbstractRoute> */
public function getRoutes(): array;

public function getBasePath(): string|null;
}
16 changes: 8 additions & 8 deletions src/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
* @method AbstractRoute patch(string $path, mixed $routeTarget)
* @method AbstractRoute any(string $path, mixed $routeTarget)
*/
final class Router implements MiddlewareInterface
final class Router implements MiddlewareInterface, RouteProvider
{
public DispatchContext|null $context = null;

Expand All @@ -54,12 +54,9 @@ final class Router implements MiddlewareInterface
/** @var array<int, AbstractRoute> */
protected array $sideRoutes = [];

/** Used by tests for named route attributes */
public mixed $allMembers = null;

private DispatchEngine|null $dispatchEngine = null;

public function __construct(private HttpFactories $httpFactories, protected string|null $virtualHost = null)
public function __construct(private HttpFactories $httpFactories, protected string|null $basePath = null)
{
}

Expand All @@ -82,7 +79,7 @@ public function appendRoute(AbstractRoute $route): static
{
$this->routes[] = $route;
$route->sideRoutes = &$this->sideRoutes;
$route->virtualHost = $this->virtualHost;
$route->basePath = $this->basePath;

foreach ($this->globalRoutines as $routine) {
$route->appendRoutine($routine);
Expand Down Expand Up @@ -196,9 +193,9 @@ public function getRoutes(): array
return $this->routes;
}

public function getVirtualHost(): string|null
public function getBasePath(): string|null
{
return $this->virtualHost;
return $this->basePath;
}

public function dispatchEngine(): DispatchEngine
Expand All @@ -207,6 +204,9 @@ public function dispatchEngine(): DispatchEngine
$this,
$this->responseFactory(),
$this->streamFactory(),
function (DispatchContext $ctx): void {
$this->context = $ctx;
},
);
}

Expand Down
4 changes: 2 additions & 2 deletions src/Routes/AbstractRoute.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ abstract class AbstractRoute
/** @var array<int, AbstractRoute> */
public array $sideRoutes = [];

public string|null $virtualHost = null;
public string|null $basePath = null;

public function __construct(string $method, public string $pattern = '')
{
Expand Down Expand Up @@ -157,7 +157,7 @@ public function createUri(mixed ...$params): string
$params = preg_replace('#(?<!^)/? *$#', '', $params);

// phpcs:ignore SlevomatCodingStandard.PHP.OptimizedFunctionsWithoutUnpacking.UnpackingUsed
return rtrim((string) $this->virtualHost, ' /') . sprintf($this->regexForReplace, ...$params);
return rtrim((string) $this->basePath, ' /') . sprintf($this->regexForReplace, ...$params);
}

/** @param array<int, mixed> $params */
Expand Down
23 changes: 9 additions & 14 deletions src/RoutinePipeline.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,17 @@
use Respect\Rest\Routines\ProxyableThrough;
use Respect\Rest\Routines\ProxyableWhen;

use function assert;
use function is_callable;

final class RoutinePipeline
{
/** @param array<int, mixed> $params */
public function matches(DispatchContext $context, array $params = []): bool
public function matches(DispatchContext $context, AbstractRoute $route, array $params = []): bool
{
assert($context->route !== null);

foreach ($context->route->routines as $routine) {
foreach ($route->routines as $routine) {
if (
$routine instanceof ProxyableWhen
&& !$context->routineCall('when', $context->method(), $routine, $params)
&& !$context->routineCall('when', $context->method(), $routine, $params, $route)
) {
return false;
}
Expand All @@ -32,11 +29,9 @@ public function matches(DispatchContext $context, array $params = []): bool
return true;
}

public function processBy(DispatchContext $context): mixed
public function processBy(DispatchContext $context, AbstractRoute $route): mixed
{
assert($context->route !== null);

foreach ($context->route->routines as $routine) {
foreach ($route->routines as $routine) {
if (!$routine instanceof ProxyableBy) {
continue;
}
Expand All @@ -46,6 +41,7 @@ public function processBy(DispatchContext $context): mixed
$context->method(),
$routine,
$context->params,
$route,
);

if ($result instanceof AbstractRoute || $result instanceof ResponseInterface || $result === false) {
Expand All @@ -56,11 +52,9 @@ public function processBy(DispatchContext $context): mixed
return null;
}

public function processThrough(DispatchContext $context, mixed $response): mixed
public function processThrough(DispatchContext $context, AbstractRoute $route, mixed $response): mixed
{
assert($context->route !== null);

foreach ($context->route->routines as $routine) {
foreach ($route->routines as $routine) {
if (!($routine instanceof ProxyableThrough)) {
continue;
}
Expand All @@ -70,6 +64,7 @@ public function processThrough(DispatchContext $context, mixed $response): mixed
$context->method(),
$routine,
$context->params,
$route,
);

if (!is_callable($proxyCallback)) {
Expand Down
Loading
Loading