Skip to content
This repository was archived by the owner on Jul 6, 2024. It is now read-only.

Commit 67409c9

Browse files
committed
add ExceptionMiddleware
1 parent 36dfe0d commit 67409c9

File tree

9 files changed

+499
-9
lines changed

9 files changed

+499
-9
lines changed

README.md

+10-7
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ A simple http handler implementation for API.
2020
* psr/http-factory: ^1.0.1
2121
* psr/http-message: ^1.0.1
2222
* psr/http-server-middleware: ^1.0.1
23+
* psr/log: ^1.1.2
2324

2425
## Installation
2526

2627
Through [Composer](http://getcomposer.org) as [chubbyphp/chubbyphp-api-http][1].
2728

2829
```sh
29-
composer require chubbyphp/chubbyphp-api-http "^3.3"
30+
composer require chubbyphp/chubbyphp-api-http "^3.4"
3031
```
3132

3233
## Usage
@@ -35,9 +36,10 @@ composer require chubbyphp/chubbyphp-api-http "^3.3"
3536
* [RequestManager][3]
3637
* [ResponseManager][4]
3738
* [AcceptAndContentTypeMiddleware][5]
38-
* [ApiHttpServiceFactory][6]
39-
* [ApiHttpServiceProvider][7]
40-
* [ApiProblemMapping (example)][8]
39+
* [ApiExceptionMiddleware][6]
40+
* [ApiHttpServiceFactory][7]
41+
* [ApiHttpServiceProvider][8]
42+
* [ApiProblemMapping (example)][9]
4143

4244
## Copyright
4345

@@ -48,6 +50,7 @@ Dominik Zogg 2020
4850
[3]: doc/Manager/RequestManager.md
4951
[4]: doc/Manager/ResponseManager.md
5052
[5]: doc/Middleware/AcceptAndContentTypeMiddleware.md
51-
[6]: doc/ServiceFactory/ApiHttpServiceFactory.md
52-
[7]: doc/ServiceProvider/ApiHttpServiceProvider.md
53-
[8]: doc/Serialization/ApiProblemMapping.md
53+
[6]: doc/ServiceFactory/ApiExceptionMiddleware.md
54+
[7]: doc/ServiceFactory/ApiHttpServiceFactory.md
55+
[8]: doc/ServiceProvider/ApiHttpServiceProvider.md
56+
[9]: doc/Serialization/ApiProblemMapping.md

composer.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"chubbyphp/chubbyphp-serialization": "^2.12.2",
1717
"psr/http-factory": "^1.0.1",
1818
"psr/http-message": "^1.0.1",
19-
"psr/http-server-middleware": "^1.0.1"
19+
"psr/http-server-middleware": "^1.0.1",
20+
"psr/log": "^1.1.2"
2021
},
2122
"require-dev": {
2223
"chubbyphp/chubbyphp-container": "^1.0",
@@ -42,7 +43,7 @@
4243
},
4344
"extra": {
4445
"branch-alias": {
45-
"dev-master": "3.3-dev"
46+
"dev-master": "3.4-dev"
4647
}
4748
},
4849
"scripts": {
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# ApiExceptionMiddleware
2+
3+
```php
4+
<?php
5+
6+
use Chubbyphp\ApiHttp\Manager\ResponseManagerInterface;
7+
use Chubbyphp\ApiHttp\Middleware\ApiExceptionMiddleware;
8+
use Psr\Http\Message\ResponseInterface;
9+
use Psr\Http\Message\ServerRequestInterface;
10+
use Psr\Http\Server\RequestHandlerInterface;
11+
use Psr\Log\LoggerInterface;
12+
13+
/** @var ServerRequestInterface $request */
14+
$request = ...;
15+
16+
/** @var RequestHandlerInterface $handler */
17+
$handler = ...;
18+
19+
/** @var ResponseManagerInterface $responseManager */
20+
$responseManager = ...;
21+
22+
/** @var LoggerInterface $logger */
23+
$logger = ...;
24+
25+
$middleware = new ApiExceptionMiddleware(
26+
$responseManager,
27+
true,
28+
$logger
29+
);
30+
31+
/** @var ResponseInterface $response */
32+
$response = $middleware->process($request, $handler);
33+
```

src/ApiProblem/ServerError/InternalServerError.php

+21
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88

99
final class InternalServerError extends AbstractApiProblem
1010
{
11+
/**
12+
* @var array<int, array<mixed>>|null
13+
*/
14+
private $backtrace;
15+
1116
public function __construct(?string $detail = null, ?string $instance = null)
1217
{
1318
parent::__construct(
@@ -18,4 +23,20 @@ public function __construct(?string $detail = null, ?string $instance = null)
1823
$instance
1924
);
2025
}
26+
27+
/**
28+
* @param array<int, array<mixed>>|null $backtrace
29+
*/
30+
public function setBacktrace(?array $backtrace): void
31+
{
32+
$this->backtrace = $backtrace;
33+
}
34+
35+
/**
36+
* @return array<int, array<mixed>>|null
37+
*/
38+
public function getBacktrace(): ?array
39+
{
40+
return $this->backtrace;
41+
}
2142
}
+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Chubbyphp\ApiHttp\Middleware;
6+
7+
use Chubbyphp\ApiHttp\ApiProblem\ServerError\InternalServerError;
8+
use Chubbyphp\ApiHttp\Manager\ResponseManagerInterface;
9+
use Psr\Http\Message\ResponseInterface;
10+
use Psr\Http\Message\ServerRequestInterface;
11+
use Psr\Http\Server\MiddlewareInterface;
12+
use Psr\Http\Server\RequestHandlerInterface;
13+
use Psr\Log\LoggerInterface;
14+
use Psr\Log\NullLogger;
15+
16+
final class ApiExceptionMiddleware implements MiddlewareInterface
17+
{
18+
/**
19+
* @var ResponseManagerInterface
20+
*/
21+
private $responseManager;
22+
23+
/**
24+
* @var bool
25+
*/
26+
private $debug;
27+
28+
/**
29+
* @var LoggerInterface
30+
*/
31+
private $logger;
32+
33+
public function __construct(
34+
ResponseManagerInterface $responseManager,
35+
bool $debug = false,
36+
?LoggerInterface $logger = null
37+
) {
38+
$this->responseManager = $responseManager;
39+
$this->debug = $debug;
40+
$this->logger = $logger ?? new NullLogger();
41+
}
42+
43+
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
44+
{
45+
try {
46+
return $handler->handle($request);
47+
} catch (\Throwable $exception) {
48+
return $this->handleException($request, $exception);
49+
}
50+
}
51+
52+
private function handleException(ServerRequestInterface $request, \Throwable $exception): ResponseInterface
53+
{
54+
$backtrace = $this->backtrace($exception);
55+
56+
$this->logger->error('Exception', ['backtrace' => $backtrace]);
57+
58+
if (null === $accept = $request->getAttribute('accept')) {
59+
throw $exception;
60+
}
61+
62+
if ($this->debug) {
63+
$internalServerError = new InternalServerError($exception->getMessage());
64+
$internalServerError->setBacktrace($backtrace);
65+
} else {
66+
$internalServerError = new InternalServerError();
67+
}
68+
69+
return $this->responseManager->createFromApiProblem($internalServerError, $accept);
70+
}
71+
72+
/**
73+
* @return array<int, array<string, mixed>>
74+
*/
75+
private function backtrace(\Throwable $exception): array
76+
{
77+
$exceptions = [];
78+
do {
79+
$exceptions[] = [
80+
'class' => get_class($exception),
81+
'message' => $exception->getMessage(),
82+
'code' => $exception->getCode(),
83+
'file' => $exception->getFile(),
84+
'line' => $exception->getLine(),
85+
'trace' => $exception->getTraceAsString(),
86+
];
87+
} while ($exception = $exception->getPrevious());
88+
89+
return $exceptions;
90+
}
91+
}

src/Serialization/ApiProblem/ServerError/InternalServerErrorMapping.php

+14
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,25 @@
66

77
use Chubbyphp\ApiHttp\ApiProblem\ServerError\InternalServerError;
88
use Chubbyphp\ApiHttp\Serialization\ApiProblem\AbstractApiProblemMapping;
9+
use Chubbyphp\Serialization\Mapping\NormalizationFieldMappingBuilder;
10+
use Chubbyphp\Serialization\Mapping\NormalizationFieldMappingInterface;
911

1012
final class InternalServerErrorMapping extends AbstractApiProblemMapping
1113
{
1214
public function getClass(): string
1315
{
1416
return InternalServerError::class;
1517
}
18+
19+
/**
20+
* @return array<int, NormalizationFieldMappingInterface>
21+
*/
22+
public function getNormalizationFieldMappings(string $path): array
23+
{
24+
$fieldMappings = parent::getNormalizationFieldMappings($path);
25+
26+
$fieldMappings[] = NormalizationFieldMappingBuilder::create('backtrace')->getMapping();
27+
28+
return $fieldMappings;
29+
}
1630
}

tests/Unit/ApiProblem/ServerError/InternalServerErrorTest.php

+11
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,28 @@ public function testMinimal(): void
2424
self::assertSame('Internal Server Error', $apiProblem->getTitle());
2525
self::assertNull($apiProblem->getDetail());
2626
self::assertNull($apiProblem->getInstance());
27+
self::assertNull($apiProblem->getBacktrace());
2728
}
2829

2930
public function testMaximal(): void
3031
{
32+
$backtrace = [
33+
[
34+
'class' => 'RuntimeException',
35+
'message' => 'runtime exception',
36+
'code' => 5000,
37+
],
38+
];
39+
3140
$apiProblem = new InternalServerError('detail', '/cccdfd0f-0da3-4070-8e55-61bd832b47c0');
41+
$apiProblem->setBacktrace($backtrace);
3242

3343
self::assertSame(500, $apiProblem->getStatus());
3444
self::assertSame([], $apiProblem->getHeaders());
3545
self::assertSame('https://tools.ietf.org/html/rfc2616#section-10.5.1', $apiProblem->getType());
3646
self::assertSame('Internal Server Error', $apiProblem->getTitle());
3747
self::assertSame('detail', $apiProblem->getDetail());
3848
self::assertSame('/cccdfd0f-0da3-4070-8e55-61bd832b47c0', $apiProblem->getInstance());
49+
self::assertSame($backtrace, $apiProblem->getBacktrace());
3950
}
4051
}

0 commit comments

Comments
 (0)