Skip to content

Commit 64ab168

Browse files
authored
Merge pull request #9 from tjveldhuizen/feature-httpexception
Added Exception class implementing HttpExceptionInterface
2 parents a0fef11 + e19de04 commit 64ab168

4 files changed

Lines changed: 122 additions & 3 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# Api Problem Bundle
77

88
This package provides a [RFC7807](https://tools.ietf.org/html/rfc7807) Problem details exception listener for Symfony.
9-
Internal, this package uses the models provided by `phpro/api-problem`](https://www.github.com/phpro/api-problem).
9+
Internal, this package uses the models provided by [`phpro/api-problem`](https://www.github.com/phpro/api-problem).
1010
When an `ApiProblemException` is triggered, this bundle will return the correct response.
1111

1212

@@ -69,6 +69,9 @@ Body:
6969
}
7070
```
7171

72+
As an alternative, use ```ApiProblemHttpException``` instead of ```ApiProblemException```, to make it possible to
73+
[exclude the specific status code from the log](https://symfony.com/doc/current/logging/monolog_exclude_http_codes.html)
74+
7275
## Adding custom exception transformations
7376

7477
Currently, we automatically transform exceptions from following packages to an ApiProblem instance:
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phpro\ApiProblemBundle\Exception;
6+
7+
use Phpro\ApiProblem\ApiProblemInterface;
8+
use Symfony\Component\HttpFoundation\Response;
9+
use Symfony\Component\HttpKernel\Exception\HttpException;
10+
11+
class ApiProblemHttpException extends HttpException
12+
{
13+
private $apiProblem;
14+
15+
public function __construct(ApiProblemInterface $apiProblem)
16+
{
17+
$data = $apiProblem->toArray();
18+
$message = $data['detail'] ?? ($data['title'] ?? '');
19+
$code = (int) ($data['status'] ?? 0);
20+
21+
parent::__construct($code, $message);
22+
$this->apiProblem = $apiProblem;
23+
}
24+
25+
public function getApiProblem(): ApiProblemInterface
26+
{
27+
return $this->apiProblem;
28+
}
29+
30+
public function getStatusCode()
31+
{
32+
return parent::getStatusCode() > 0 ? parent::getStatusCode() : Response::HTTP_BAD_REQUEST;
33+
}
34+
35+
public function getHeaders()
36+
{
37+
return ['Content-Type' => 'application/problem+json'];
38+
}
39+
}

src/Transformer/ApiProblemExceptionTransformer.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66

77
use Phpro\ApiProblem\ApiProblemInterface;
88
use Phpro\ApiProblem\Exception\ApiProblemException;
9+
use Phpro\ApiProblemBundle\Exception\ApiProblemHttpException;
910

1011
class ApiProblemExceptionTransformer implements ExceptionTransformerInterface
1112
{
1213
/**
13-
* @param ApiProblemException $exception
14+
* @param ApiProblemException|ApiProblemHttpException $exception
1415
*/
1516
public function transform(\Throwable $exception): ApiProblemInterface
1617
{
@@ -19,6 +20,6 @@ public function transform(\Throwable $exception): ApiProblemInterface
1920

2021
public function accepts(\Throwable $exception): bool
2122
{
22-
return $exception instanceof ApiProblemException;
23+
return $exception instanceof ApiProblemException || $exception instanceof ApiProblemHttpException;
2324
}
2425
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhproTest\ApiProblemBundle\Exception;
6+
7+
use Phpro\ApiProblem\Http\HttpApiProblem;
8+
use Phpro\ApiProblemBundle\Exception\ApiProblemHttpException;
9+
use Phpro\ApiProblemBundle\Transformer\ApiProblemExceptionTransformer;
10+
use PHPUnit\Framework\TestCase;
11+
use Prophecy\Prophecy\ObjectProphecy;
12+
use Symfony\Component\HttpKernel\Exception\HttpException;
13+
14+
class ApiProblemHttpExceptionTest extends TestCase
15+
{
16+
/**
17+
* @var HttpApiProblem|ObjectProphecy
18+
*/
19+
private $apiProblem;
20+
21+
protected function setUp(): void/* The :void return type declaration that should be here would cause a BC issue */
22+
{
23+
$this->apiProblem = $this->prophesize(HttpApiProblem::class);
24+
$this->apiProblem->toArray()->willReturn([]);
25+
}
26+
27+
/** @test */
28+
public function it_is_accepted_by_the_ApiProblemExceptionTransformer(): void
29+
{
30+
$transformer = new ApiProblemExceptionTransformer();
31+
32+
$this->assertTrue($transformer->accepts(new ApiProblemHttpException($this->apiProblem->reveal())));
33+
}
34+
35+
/** @test */
36+
public function it_is_an_instance_of_HttpException(): void
37+
{
38+
$exception = new ApiProblemHttpException($this->apiProblem->reveal());
39+
40+
$this->assertInstanceOf(HttpException::class, $exception);
41+
}
42+
43+
/** @test */
44+
public function it_contains_an_api_problem(): void
45+
{
46+
$apiProblem = $this->apiProblem->reveal();
47+
48+
$exception = new ApiProblemHttpException($apiProblem);
49+
$this->assertEquals($apiProblem, $exception->getApiProblem());
50+
}
51+
52+
/** @test */
53+
public function it_returns_the_correct_http_headers(): void
54+
{
55+
$exception = new ApiProblemHttpException($this->apiProblem->reveal());
56+
57+
$this->assertEquals(['Content-Type' => 'application/problem+json'], $exception->getHeaders());
58+
}
59+
60+
/** @test */
61+
public function it_returns_the_correct_default_http_statuscode(): void
62+
{
63+
$exception = new ApiProblemHttpException($this->apiProblem->reveal());
64+
65+
$this->assertEquals(400, $exception->getStatusCode());
66+
}
67+
68+
/** @test */
69+
public function it_returns_the_correct_specified_http_statuscode(): void
70+
{
71+
$this->apiProblem->toArray()->willReturn(['status' => 401]);
72+
$exception = new ApiProblemHttpException($this->apiProblem->reveal());
73+
74+
$this->assertEquals(401, $exception->getStatusCode());
75+
}
76+
}

0 commit comments

Comments
 (0)