Skip to content

Commit 9a68967

Browse files
committed
Extract handlers, adopt Fluent NamespaceLookup, update docs
Move Error, Exception and Status from Routes/ to Handlers/ as standalone handler classes. Replace the internal sideRoutes array with appendHandler() on Router. Rename exceptionRoute()/errorRoute()/ statusRoute() to onException()/onError()/onStatus(). Use Fluent's NamespaceLookup for routine instantiation in AbstractRoute, replacing manual reflection-based class resolution. Update docs/README.md error handling section to reflect the new handler API and add onStatus() example.
1 parent 0f1843b commit 9a68967

15 files changed

Lines changed: 146 additions & 107 deletions

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"require": {
2424
"php": ">=8.5",
2525
"psr/container": "^2.0",
26+
"respect/fluent": "^2.0",
2627
"psr/http-factory": "^1.0",
2728
"psr/http-message": "^2.0",
2829
"psr/http-server-handler": "^1.0",

docs/README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -546,24 +546,32 @@ You can use any combination of the above but also need to implement the `Routina
546546

547547
## Error Handling
548548

549-
Respect\Rest provides two special ways to handle errors. The first one is using exception
550-
routes:
549+
Respect\Rest provides handlers for exceptions and errors. Register an exception
550+
handler with `onException`:
551551

552552
```php
553-
$r3->exceptionRoute('InvalidArgumentException', function (InvalidArgumentException $e) {
553+
$r3->onException('InvalidArgumentException', function (InvalidArgumentException $e) {
554554
return 'Sorry, this error happened: ' . $e->getMessage();
555555
});
556556
```
557557

558558
Whenever an uncaught exception appears on any route, it will be caught and forwarded to
559-
this side route. Similarly, there is a route for PHP errors:
559+
this handler. Similarly, there is a handler for PHP errors:
560560

561561
```php
562-
$r3->errorRoute(function (array $err) {
562+
$r3->onError(function (array $err) {
563563
return 'Sorry, these errors happened: ' . var_export($err, true);
564564
});
565565
```
566566

567+
You can also handle specific HTTP status codes:
568+
569+
```php
570+
$r3->onStatus(404, function () {
571+
return 'Page not found';
572+
});
573+
```
574+
567575
***
568576

569577
See also:

example/full.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,11 @@ public function get(string $id): string
184184
throw new RuntimeException('Something went wrong!');
185185
});
186186

187-
$r3->exceptionRoute('RuntimeException', function (RuntimeException $e) {
187+
$r3->onException('RuntimeException', function (RuntimeException $e) {
188188
return 'Caught exception: ' . $e->getMessage();
189189
});
190190

191-
$r3->errorRoute(function (array $err) {
191+
$r3->onError(function (array $err) {
192192
return 'Error occurred: ' . ($err[0]['message'] ?? 'unknown');
193193
});
194194

src/DispatchContext.php

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@
1010
use Psr\Http\Message\ServerRequestInterface;
1111
use Psr\Http\Message\StreamFactoryInterface;
1212
use Respect\Parameter\Resolver;
13+
use Respect\Rest\Handlers\ErrorHandler;
14+
use Respect\Rest\Handlers\ExceptionHandler;
15+
use Respect\Rest\Handlers\StatusHandler;
1316
use Respect\Rest\Routes\AbstractRoute;
1417
use Throwable;
1518

19+
use function in_array;
1620
use function is_a;
1721
use function rawurldecode;
1822
use function rtrim;
@@ -50,7 +54,7 @@ final class DispatchContext implements ContainerInterface
5054
private string $effectivePath = '';
5155

5256
/** @var array<int, AbstractRoute> */
53-
private array $sideRoutes = [];
57+
private array $handlers = [];
5458

5559
private Resolver|null $resolver = null;
5660

@@ -138,9 +142,10 @@ public function response(): ResponseInterface|null
138142
}
139143

140144
$route = $this->route;
145+
$isHandler = in_array($route, $this->handlers, true);
141146

142147
try {
143-
$errorHandler = $this->prepareForErrorForwards($route);
148+
$errorHandler = $isHandler ? null : $this->prepareForErrorForwards();
144149
$preRoutineResult = $this->routinePipeline()->processBy($this, $route);
145150

146151
if ($preRoutineResult !== null) {
@@ -164,20 +169,25 @@ public function response(): ResponseInterface|null
164169
}
165170

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

169-
if ($errorResponse !== null) {
170-
return $errorResponse;
173+
if (!$isHandler) {
174+
$errorResponse = $this->forwardErrors($errorHandler);
175+
176+
if ($errorResponse !== null) {
177+
return $errorResponse;
178+
}
171179
}
172180

173181
return $this->finalizeResponse($processedResult);
174182
} catch (Throwable $e) {
175-
$exceptionResponse = $this->catchExceptions($e, $route);
176-
if ($exceptionResponse === null) {
177-
throw $e;
183+
if (!$isHandler) {
184+
$exceptionResponse = $this->catchExceptions($e);
185+
if ($exceptionResponse !== null) {
186+
return $exceptionResponse;
187+
}
178188
}
179189

180-
return $exceptionResponse;
190+
throw $e;
181191
}
182192
}
183193

@@ -193,10 +203,10 @@ public function setRoutinePipeline(RoutinePipeline $routinePipeline): void
193203
$this->routinePipeline = $routinePipeline;
194204
}
195205

196-
/** @param array<int, AbstractRoute> $sideRoutes */
197-
public function setSideRoutes(array $sideRoutes): void
206+
/** @param array<int, AbstractRoute> $handlers */
207+
public function setHandlers(array $handlers): void
198208
{
199-
$this->sideRoutes = $sideRoutes;
209+
$this->handlers = $handlers;
200210
}
201211

202212
public function setResponder(Responder $responder): void
@@ -229,18 +239,18 @@ public function get(string $id): mixed
229239
}
230240

231241
/** @return callable|null The previous error handler, or null */
232-
protected function prepareForErrorForwards(AbstractRoute $route): callable|null
242+
protected function prepareForErrorForwards(): callable|null
233243
{
234-
foreach ($route->sideRoutes as $sideRoute) {
235-
if ($sideRoute instanceof Routes\Error) {
244+
foreach ($this->handlers as $handler) {
245+
if ($handler instanceof ErrorHandler) {
236246
return set_error_handler(
237247
static function (
238248
int $errno,
239249
string $errstr,
240250
string $errfile = '',
241251
int $errline = 0,
242-
) use ($sideRoute): bool {
243-
$sideRoute->errors[] = [$errno, $errstr, $errfile, $errline];
252+
) use ($handler): bool {
253+
$handler->errors[] = [$errno, $errstr, $errfile, $errline];
244254

245255
return true;
246256
},
@@ -251,32 +261,32 @@ static function (
251261
return null;
252262
}
253263

254-
protected function forwardErrors(callable|null $errorHandler, AbstractRoute $route): ResponseInterface|null
264+
protected function forwardErrors(callable|null $errorHandler): ResponseInterface|null
255265
{
256266
if ($errorHandler !== null) {
257267
set_error_handler($errorHandler);
258268
}
259269

260-
foreach ($route->sideRoutes as $sideRoute) {
261-
if ($sideRoute instanceof Routes\Error && $sideRoute->errors) {
262-
return $this->forward($sideRoute);
270+
foreach ($this->handlers as $handler) {
271+
if ($handler instanceof ErrorHandler && $handler->errors) {
272+
return $this->forward($handler);
263273
}
264274
}
265275

266276
return null;
267277
}
268278

269-
protected function catchExceptions(Throwable $e, AbstractRoute $route): ResponseInterface|null
279+
protected function catchExceptions(Throwable $e): ResponseInterface|null
270280
{
271-
foreach ($route->sideRoutes as $sideRoute) {
272-
if (!$sideRoute instanceof Routes\Exception) {
281+
foreach ($this->handlers as $handler) {
282+
if (!$handler instanceof ExceptionHandler) {
273283
continue;
274284
}
275285

276-
if (is_a($e, $sideRoute->class)) {
277-
$sideRoute->exception = $e;
286+
if (is_a($e, $handler->class)) {
287+
$handler->exception = $e;
278288

279-
return $this->forward($sideRoute);
289+
return $this->forward($handler);
280290
}
281291
}
282292

@@ -287,18 +297,18 @@ protected function forwardToStatusRoute(ResponseInterface $preparedResponse): Re
287297
{
288298
$statusCode = $preparedResponse->getStatusCode();
289299

290-
foreach ($this->sideRoutes as $sideRoute) {
300+
foreach ($this->handlers as $handler) {
291301
if (
292-
$sideRoute instanceof Routes\Status
293-
&& ($sideRoute->statusCode === $statusCode || $sideRoute->statusCode === null)
302+
$handler instanceof StatusHandler
303+
&& ($handler->statusCode === $statusCode || $handler->statusCode === null)
294304
) {
295305
$this->hasStatusOverride = true;
296306

297307
// Run routine negotiation (e.g. Accept) before forwarding,
298308
// since the normal route-selection phase was skipped
299-
$this->routinePipeline()->matches($this, $sideRoute, $this->params);
309+
$this->routinePipeline()->matches($this, $handler, $this->params);
300310

301-
$result = $this->forward($sideRoute);
311+
$result = $this->forward($handler);
302312

303313
// Preserve the original status code on the forwarded response
304314
return $result?->withStatus($statusCode);

src/DispatchEngine.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public function dispatchContext(DispatchContext $context): DispatchContext
6060
}
6161

6262
$context->setRoutinePipeline($this->routinePipeline);
63-
$context->setSideRoutes($this->routeProvider->getSideRoutes());
63+
$context->setHandlers($this->routeProvider->getHandlers());
6464

6565
if (!$this->isRoutelessDispatch($context) && $context->route === null) {
6666
$this->routeDispatch($context);
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
declare(strict_types=1);
44

5-
namespace Respect\Rest\Routes;
5+
namespace Respect\Rest\Handlers;
66

77
use Respect\Rest\DispatchContext;
8+
use Respect\Rest\Routes\Callback;
89

9-
final class Error extends Callback
10+
final class ErrorHandler extends Callback
1011
{
1112
/** @var callable */
1213
public $callback;
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
declare(strict_types=1);
44

5-
namespace Respect\Rest\Routes;
5+
namespace Respect\Rest\Handlers;
66

77
use Respect\Rest\DispatchContext;
8+
use Respect\Rest\Routes\Callback;
89
use Throwable;
910

10-
final class Exception extends Callback
11+
final class ExceptionHandler extends Callback
1112
{
1213
/** @var callable */
1314
public $callback;
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
declare(strict_types=1);
44

5-
namespace Respect\Rest\Routes;
5+
namespace Respect\Rest\Handlers;
66

7-
final class Status extends Callback
7+
use Respect\Rest\Routes\Callback;
8+
9+
final class StatusHandler extends Callback
810
{
911
/** @var callable */
1012
public $callback;

src/RouteProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ interface RouteProvider
1212
public function getRoutes(): array;
1313

1414
/** @return array<int, AbstractRoute> */
15-
public function getSideRoutes(): array;
15+
public function getHandlers(): array;
1616

1717
public function getBasePath(): string;
1818
}

0 commit comments

Comments
 (0)