Skip to content

Commit

Permalink
add ExceptionMiddleware
Browse files Browse the repository at this point in the history
  • Loading branch information
Dominik Zogg committed Mar 1, 2020
1 parent 36dfe0d commit 5bf135a
Show file tree
Hide file tree
Showing 10 changed files with 500 additions and 9 deletions.
1 change: 1 addition & 0 deletions .phpunit.result.cache
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
C:37:"PHPUnit\Runner\DefaultTestResultCache":1144:{a:2:{s:7:"defects";a:3:{s:104:"Chubbyphp\Tests\ApiHttp\Unit\Middleware\ExceptionMiddlewareTest::testWithoutExceptionWithDebugWithLogger";i:3;s:101:"Chubbyphp\Tests\ApiHttp\Unit\Middleware\ExceptionMiddlewareTest::testWithExceptionWithDebugWithLogger";i:3;s:114:"Chubbyphp\Tests\ApiHttp\Unit\Middleware\ExceptionMiddlewareTest::testWithExceptionWithDebugWithLoggerWithoutAccept";i:4;}s:5:"times";a:6:{s:104:"Chubbyphp\Tests\ApiHttp\Unit\Middleware\ExceptionMiddlewareTest::testWithoutExceptionWithDebugWithLogger";d:0.001;s:110:"Chubbyphp\Tests\ApiHttp\Unit\Middleware\ExceptionMiddlewareTest::testWithoutExceptionWithoutDebugWithoutLogger";d:0.059;s:101:"Chubbyphp\Tests\ApiHttp\Unit\Middleware\ExceptionMiddlewareTest::testWithExceptionWithDebugWithLogger";d:0.004;s:104:"Chubbyphp\Tests\ApiHttp\Unit\Middleware\ExceptionMiddlewareTest::testWithExceptionWithoutDebugWithLogger";d:0.002;s:107:"Chubbyphp\Tests\ApiHttp\Unit\Middleware\ExceptionMiddlewareTest::testWithExceptionWithoutDebugWithoutLogger";d:0.003;s:114:"Chubbyphp\Tests\ApiHttp\Unit\Middleware\ExceptionMiddlewareTest::testWithExceptionWithDebugWithLoggerWithoutAccept";d:0.009;}}}
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ A simple http handler implementation for API.
* psr/http-factory: ^1.0.1
* psr/http-message: ^1.0.1
* psr/http-server-middleware: ^1.0.1
* psr/log: ^1.1.2

## Installation

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

```sh
composer require chubbyphp/chubbyphp-api-http "^3.3"
composer require chubbyphp/chubbyphp-api-http "^3.4"
```

## Usage
Expand All @@ -35,9 +36,10 @@ composer require chubbyphp/chubbyphp-api-http "^3.3"
* [RequestManager][3]
* [ResponseManager][4]
* [AcceptAndContentTypeMiddleware][5]
* [ApiHttpServiceFactory][6]
* [ApiHttpServiceProvider][7]
* [ApiProblemMapping (example)][8]
* [ExceptionMiddleware][6]
* [ApiHttpServiceFactory][7]
* [ApiHttpServiceProvider][8]
* [ApiProblemMapping (example)][9]

## Copyright

Expand All @@ -48,6 +50,7 @@ Dominik Zogg 2020
[3]: doc/Manager/RequestManager.md
[4]: doc/Manager/ResponseManager.md
[5]: doc/Middleware/AcceptAndContentTypeMiddleware.md
[6]: doc/ServiceFactory/ApiHttpServiceFactory.md
[7]: doc/ServiceProvider/ApiHttpServiceProvider.md
[8]: doc/Serialization/ApiProblemMapping.md
[6]: doc/ServiceFactory/ExceptionMiddleware.md
[7]: doc/ServiceFactory/ApiHttpServiceFactory.md
[8]: doc/ServiceProvider/ApiHttpServiceProvider.md
[9]: doc/Serialization/ApiProblemMapping.md
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"chubbyphp/chubbyphp-serialization": "^2.12.2",
"psr/http-factory": "^1.0.1",
"psr/http-message": "^1.0.1",
"psr/http-server-middleware": "^1.0.1"
"psr/http-server-middleware": "^1.0.1",
"psr/log": "^1.1.2"
},
"require-dev": {
"chubbyphp/chubbyphp-container": "^1.0",
Expand All @@ -42,7 +43,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
"dev-master": "3.4-dev"
}
},
"scripts": {
Expand Down
33 changes: 33 additions & 0 deletions doc/Middleware/ExceptionMiddleware.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# ExceptionMiddleware

```php
<?php

use Chubbyphp\ApiHttp\Manager\ResponseManagerInterface;
use Chubbyphp\ApiHttp\Middleware\ExceptionMiddleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;

/** @var ServerRequestInterface $request */
$request = ...;

/** @var RequestHandlerInterface $handler */
$handler = ...;

/** @var ResponseManagerInterface $responseManager */
$responseManager = ...;

/** @var LoggerInterface $logger */
$logger = ...;

$middleware = new ExceptionMiddleware(
$responseManager,
true,
$logger
);

/** @var ResponseInterface $response */
$response = $middleware->process($request, $handler);
```
21 changes: 21 additions & 0 deletions src/ApiProblem/ServerError/InternalServerError.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

final class InternalServerError extends AbstractApiProblem
{
/**
* @var array<int, array<mixed>>|null
*/
private $backtrace;

public function __construct(?string $detail = null, ?string $instance = null)
{
parent::__construct(
Expand All @@ -18,4 +23,20 @@ public function __construct(?string $detail = null, ?string $instance = null)
$instance
);
}

/**
* @param array<int, array<mixed>>|null $backtrace
*/
public function setBacktrace(?array $backtrace): void
{
$this->backtrace = $backtrace;
}

/**
* @return array<int, array<mixed>>|null
*/
public function getBacktrace(): ?array
{
return $this->backtrace;
}
}
91 changes: 91 additions & 0 deletions src/Middleware/ExceptionMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

declare(strict_types=1);

namespace Chubbyphp\ApiHttp\Middleware;

use Chubbyphp\ApiHttp\ApiProblem\ServerError\InternalServerError;
use Chubbyphp\ApiHttp\Manager\ResponseManagerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

final class ExceptionMiddleware implements MiddlewareInterface
{
/**
* @var ResponseManagerInterface
*/
private $responseManager;

/**
* @var bool
*/
private $debug;

/**
* @var LoggerInterface
*/
private $logger;

public function __construct(
ResponseManagerInterface $responseManager,
bool $debug = false,
?LoggerInterface $logger = null
) {
$this->responseManager = $responseManager;
$this->debug = $debug;
$this->logger = $logger ?? new NullLogger();
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
try {
return $handler->handle($request);
} catch (\Throwable $exception) {
return $this->handleException($request, $exception);
}
}

private function handleException(ServerRequestInterface $request, \Throwable $exception): ResponseInterface
{
$backtrace = $this->backtrace($exception);

$this->logger->error('Exception', ['backtrace' => $backtrace]);

if (null === $accept = $request->getAttribute('accept')) {
throw $exception;
}

if ($this->debug) {
$internalServerError = new InternalServerError($exception->getMessage());
$internalServerError->setBacktrace($backtrace);
} else {
$internalServerError = new InternalServerError();
}

return $this->responseManager->createFromApiProblem($internalServerError, $accept);
}

/**
* @return array<int, array<string, mixed>>
*/
private function backtrace(\Throwable $exception): array
{
$exceptions = [];
do {
$exceptions[] = [
'class' => get_class($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $exception->getTraceAsString(),
];
} while ($exception = $exception->getPrevious());

return $exceptions;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,25 @@

use Chubbyphp\ApiHttp\ApiProblem\ServerError\InternalServerError;
use Chubbyphp\ApiHttp\Serialization\ApiProblem\AbstractApiProblemMapping;
use Chubbyphp\Serialization\Mapping\NormalizationFieldMappingBuilder;
use Chubbyphp\Serialization\Mapping\NormalizationFieldMappingInterface;

final class InternalServerErrorMapping extends AbstractApiProblemMapping
{
public function getClass(): string
{
return InternalServerError::class;
}

/**
* @return array<int, NormalizationFieldMappingInterface>
*/
public function getNormalizationFieldMappings(string $path): array
{
$fieldMappings = parent::getNormalizationFieldMappings($path);

$fieldMappings[] = NormalizationFieldMappingBuilder::create('backtrace')->getMapping();

return $fieldMappings;
}
}
11 changes: 11 additions & 0 deletions tests/Unit/ApiProblem/ServerError/InternalServerErrorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,28 @@ public function testMinimal(): void
self::assertSame('Internal Server Error', $apiProblem->getTitle());
self::assertNull($apiProblem->getDetail());
self::assertNull($apiProblem->getInstance());
self::assertNull($apiProblem->getBacktrace());
}

public function testMaximal(): void
{
$backtrace = [
[
'class' => 'RuntimeException',
'message' => 'runtime exception',
'code' => 5000,
],
];

$apiProblem = new InternalServerError('detail', '/cccdfd0f-0da3-4070-8e55-61bd832b47c0');
$apiProblem->setBacktrace($backtrace);

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

0 comments on commit 5bf135a

Please sign in to comment.