Skip to content

Commit da517ad

Browse files
committed
Add transformers system
1 parent b6ddba4 commit da517ad

16 files changed

Lines changed: 548 additions & 40 deletions

.travis.yml

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
language: php
22

3-
php:
4-
- 7.2
5-
- nightly
6-
73
env:
8-
matrix:
9-
-
10-
- DEPENDENCIES=--prefer-lowest
4+
global:
5+
- DEFAULT_COMPOSER_FLAGS="--optimize-autoloader --no-interaction --no-progress"
6+
- COMPOSER_FLAGS=""
7+
8+
before_install:
9+
- alias composer=composer\ --no-interaction && composer selfupdate
1110

1211
cache:
1312
directories:
@@ -18,11 +17,28 @@ matrix:
1817
allow_failures:
1918
- php: nightly
2019

21-
before_install:
22-
- alias composer=composer\ --no-interaction && composer selfupdate
23-
24-
install:
25-
- travis_retry composer update --no-progress --profile --no-scripts --no-suggest $DEPENDENCIES
20+
jobs:
21+
include:
22+
- &STANDARD_TEST_JOB
23+
stage: Test
24+
php: 7.2
25+
install:
26+
- travis_retry composer update $DEFAULT_COMPOSER_FLAGS $COMPOSER_FLAGS
27+
- composer info -D | sort
28+
script:
29+
- vendor/bin/grumphp run
30+
-
31+
<<: *STANDARD_TEST_JOB
32+
stage: Test
33+
php: 7.2
34+
env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest"
35+
-
36+
<<: *STANDARD_TEST_JOB
37+
stage: Test
38+
php: nightly
39+
env: COMPOSER_FLAGS="--ignore-platform-reqs" PHP_CS_FIXER_IGNORE_ENV=1 PHP_CS_FIXER_FUTURE_MODE=1
40+
script:
41+
- vendor/bin/grumphp run
2642

27-
script:
28-
- vendor/bin/grumphp run
43+
allow_failures:
44+
- php: nightly

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,36 @@ Body:
6767
}
6868
```
6969

70+
## Adding custom exception transformations
71+
72+
Currently, we automatically transform exceptions from following packages to an ApiProblem instance:
73+
74+
- phpro/api-problem
75+
- symfony/http-kernel
76+
- symfony/security
77+
78+
Besides that, all other errors are transformed to a basic `ExceptionApiProblem` instance.
79+
80+
If you want to add custom transformations, you can implement the `ExceptionTransformerInterface`
81+
and register it in the symfony container with the `phpro.api_problem.exception_transformer` tag.
82+
83+
```php
84+
use Phpro\ApiProblemBundle\Transformer\ExceptionTransformerInterface;
85+
86+
class MyTransformer implements ExceptionTransformerInterface
87+
{
88+
public function transform(\Throwable $exception): ApiProblemInterface
89+
{
90+
return new MyApiProblem($exception);
91+
}
92+
93+
public function accepts(\Throwable $exception): bool
94+
{
95+
return $exception instanceof MyException;
96+
}
97+
}
98+
```
99+
70100
## About
71101

72102
### Submitting bugs and feature requests

composer.json

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@
22
"name": "phpro/api-problem-bundle",
33
"description": "RFC7807 Problem details integration for Symfony",
44
"type": "library",
5-
"require-dev": {
6-
"friendsofphp/php-cs-fixer": "^2.12",
7-
"matthiasnoback/symfony-dependency-injection-test": "^3.0",
8-
"phpro/grumphp": "^0.14.1",
9-
"phpunit/phpunit": "^7.2"
10-
},
115
"license": "MIT",
126
"authors": [
137
{
@@ -17,11 +11,18 @@
1711
],
1812
"require": {
1913
"php": "^7.2",
20-
"phpro/api-problem": "dev-master@dev",
14+
"phpro/api-problem": "^1.0",
2115
"symfony/dependency-injection": "^4.1",
2216
"symfony/event-dispatcher": "^4.1",
2317
"symfony/http-kernel": "^4.1"
2418
},
19+
"require-dev": {
20+
"friendsofphp/php-cs-fixer": "^2.12",
21+
"matthiasnoback/symfony-dependency-injection-test": "^3.0",
22+
"phpro/grumphp": "^0.14.1",
23+
"phpunit/phpunit": "^7.2",
24+
"symfony/security": "^4.1"
25+
},
2526
"config": {
2627
"sort-packages": true
2728
},
@@ -34,11 +35,5 @@
3435
"psr-4": {
3536
"PhproTest\\ApiProblemBundle\\": "test/"
3637
}
37-
},
38-
"repositories": [
39-
{
40-
"type": "git",
41-
"url": "https://github.com/phpro/api-problem.git"
42-
}
43-
]
38+
}
4439
}

config/services.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,21 @@
77
id="Phpro\ApiProblemBundle\EventListener\JsonApiProblemExceptionListener"
88
class="Phpro\ApiProblemBundle\EventListener\JsonApiProblemExceptionListener"
99
>
10+
<argument key="$exceptionTransformer" type="service" id="Phpro\ApiProblemBundle\Transformer\Chain" />
1011
<argument key="$debug" >%kernel.debug%</argument>
1112
<tag name="kernel.event_listener" event="kernel.exception" method="onKernelException" />
1213
</service>
14+
<service id="Phpro\ApiProblemBundle\Transformer\Chain" class="Phpro\ApiProblemBundle\Transformer\Chain">
15+
<argument type="tagged" tag="phpro.api_problem.exception_transformer" />
16+
</service>
17+
<service id="Phpro\ApiProblemBundle\Transformer\ApiProblemExceptionTransformer" class="Phpro\ApiProblemBundle\Transformer\ApiProblemExceptionTransformer">
18+
<tag name="phpro.api_problem.exception_transformer" />
19+
</service>
20+
<service id="Phpro\ApiProblemBundle\Transformer\HttpExceptionTransformer" class="Phpro\ApiProblemBundle\Transformer\HttpExceptionTransformer">
21+
<tag name="phpro.api_problem.exception_transformer" />
22+
</service>
23+
<service id="Phpro\ApiProblemBundle\Transformer\SecurityExceptionTransformer" class="Phpro\ApiProblemBundle\Transformer\SecurityExceptionTransformer">
24+
<tag name="phpro.api_problem.exception_transformer" />
25+
</service>
1326
</services>
1427
</container>

src/EventListener/JsonApiProblemExceptionListener.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,27 @@
66

77
use Phpro\ApiProblem\ApiProblemInterface;
88
use Phpro\ApiProblem\DebuggableApiProblemInterface;
9-
use Phpro\ApiProblem\Exception\ApiProblemException;
109
use Phpro\ApiProblem\Http\ExceptionApiProblem;
10+
use Phpro\ApiProblemBundle\Transformer\ExceptionTransformerInterface;
1111
use Symfony\Component\HttpFoundation\JsonResponse;
1212
use Symfony\Component\HttpFoundation\Response;
1313
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
1414

1515
class JsonApiProblemExceptionListener
1616
{
17+
/**
18+
* @var ExceptionTransformerInterface
19+
*/
20+
private $exceptionTransformer;
21+
1722
/**
1823
* @var bool
1924
*/
2025
private $debug;
2126

22-
public function __construct(bool $debug)
27+
public function __construct(ExceptionTransformerInterface $exceptionTransformer, bool $debug)
2328
{
29+
$this->exceptionTransformer = $exceptionTransformer;
2430
$this->debug = $debug;
2531
}
2632

@@ -40,11 +46,11 @@ public function onKernelException(GetResponseForExceptionEvent $event): void
4046

4147
private function convertExceptionToProblem(\Throwable $exception): ApiProblemInterface
4248
{
43-
if ($exception instanceof ApiProblemException) {
44-
return $exception->getApiProblem();
49+
if (!$this->exceptionTransformer->accepts($exception)) {
50+
return new ExceptionApiProblem($exception);
4551
}
4652

47-
return new ExceptionApiProblem($exception);
53+
return $this->exceptionTransformer->transform($exception);
4854
}
4955

5056
private function generateResponse(ApiProblemInterface $apiProblem): JsonResponse
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phpro\ApiProblemBundle\Transformer;
6+
7+
use Phpro\ApiProblem\ApiProblemInterface;
8+
use Phpro\ApiProblem\Exception\ApiProblemException;
9+
10+
class ApiProblemExceptionTransformer implements ExceptionTransformerInterface
11+
{
12+
/**
13+
* @param ApiProblemException $exception
14+
*/
15+
public function transform(\Throwable $exception): ApiProblemInterface
16+
{
17+
return $exception->getApiProblem();
18+
}
19+
20+
public function accepts(\Throwable $exception): bool
21+
{
22+
return $exception instanceof ApiProblemException;
23+
}
24+
}

src/Transformer/Chain.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phpro\ApiProblemBundle\Transformer;
6+
7+
use Phpro\ApiProblem\ApiProblemInterface;
8+
use Phpro\ApiProblem\Http\ExceptionApiProblem;
9+
10+
class Chain implements ExceptionTransformerInterface
11+
{
12+
/**
13+
* @var ExceptionTransformerInterface[]
14+
*/
15+
private $transformers;
16+
17+
public function __construct(ExceptionTransformerInterface ...$transformers)
18+
{
19+
$this->transformers = $transformers;
20+
}
21+
22+
public function transform(\Throwable $exception): ApiProblemInterface
23+
{
24+
foreach ($this->transformers as $transformer) {
25+
if ($transformer->accepts($exception)) {
26+
return $transformer->transform($exception);
27+
}
28+
}
29+
30+
return new ExceptionApiProblem($exception);
31+
}
32+
33+
public function accepts(\Throwable $exception): bool
34+
{
35+
return true;
36+
}
37+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phpro\ApiProblemBundle\Transformer;
6+
7+
use Phpro\ApiProblem\ApiProblemInterface;
8+
9+
interface ExceptionTransformerInterface
10+
{
11+
public function transform(\Throwable $exception): ApiProblemInterface;
12+
13+
public function accepts(\Throwable $exception): bool;
14+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phpro\ApiProblemBundle\Transformer;
6+
7+
use Phpro\ApiProblem\ApiProblemInterface;
8+
use Phpro\ApiProblem\Http\ExceptionApiProblem;
9+
use Symfony\Component\HttpKernel\Exception\HttpException;
10+
11+
class HttpExceptionTransformer implements ExceptionTransformerInterface
12+
{
13+
/**
14+
* @param HttpException $exception
15+
*/
16+
public function transform(\Throwable $exception): ApiProblemInterface
17+
{
18+
return new ExceptionApiProblem(
19+
new HttpException(
20+
$exception->getStatusCode(),
21+
$exception->getMessage(),
22+
$exception,
23+
$exception->getHeaders(),
24+
$exception->getStatusCode()
25+
)
26+
);
27+
}
28+
29+
public function accepts(\Throwable $exception): bool
30+
{
31+
return $exception instanceof HttpException;
32+
}
33+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phpro\ApiProblemBundle\Transformer;
6+
7+
use Phpro\ApiProblem\ApiProblemInterface;
8+
use Phpro\ApiProblem\Http\ForbiddenProblem;
9+
use Phpro\ApiProblem\Http\UnauthorizedProblem;
10+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
11+
use Symfony\Component\Security\Core\Exception\ExceptionInterface as SecurityException;
12+
13+
class SecurityExceptionTransformer implements ExceptionTransformerInterface
14+
{
15+
public function transform(\Throwable $exception): ApiProblemInterface
16+
{
17+
if ($exception instanceof AuthenticationException) {
18+
return new UnauthorizedProblem($exception->getMessage());
19+
}
20+
21+
return new ForbiddenProblem($exception->getMessage());
22+
}
23+
24+
public function accepts(\Throwable $exception): bool
25+
{
26+
return $exception instanceof SecurityException;
27+
}
28+
}

0 commit comments

Comments
 (0)