Skip to content

Commit 6bda3d3

Browse files
committed
Add ErrorHandler
1 parent 49c4b4d commit 6bda3d3

File tree

7 files changed

+404
-0
lines changed

7 files changed

+404
-0
lines changed

src/ErrorHandler.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace HttpSoft\ErrorHandler;
6+
7+
use ErrorException;
8+
use Psr\Http\Message\ResponseInterface;
9+
use Psr\Http\Message\ServerRequestInterface;
10+
use Psr\Http\Server\RequestHandlerInterface;
11+
12+
final class ErrorHandler implements RequestHandlerInterface
13+
{
14+
use ErrorHandlerTrait;
15+
16+
/**
17+
* @var RequestHandlerInterface
18+
*/
19+
private RequestHandlerInterface $handler;
20+
21+
/**
22+
* @var ErrorResponseGeneratorInterface
23+
*/
24+
private ErrorResponseGeneratorInterface $responseGenerator;
25+
26+
/**
27+
* @param RequestHandlerInterface $handler
28+
* @param ErrorResponseGeneratorInterface|null $responseGenerator
29+
*/
30+
public function __construct(
31+
RequestHandlerInterface $handler,
32+
ErrorResponseGeneratorInterface $responseGenerator = null
33+
) {
34+
$this->handler = $handler;
35+
$this->responseGenerator = $responseGenerator ?? new ErrorResponseGenerator();
36+
}
37+
38+
/**
39+
* @param ServerRequestInterface $request
40+
* @return ResponseInterface
41+
* @throws ErrorException
42+
*/
43+
public function handle(ServerRequestInterface $request): ResponseInterface
44+
{
45+
return $this->handleError($request, $this->handler);
46+
}
47+
}

tests/ErrorHandlerTest.php

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace HttpSoft\Tests\ErrorHandler;
6+
7+
use HttpSoft\ErrorHandler\ErrorHandler;
8+
use HttpSoft\ErrorHandler\ErrorResponseGeneratorInterface;
9+
use HttpSoft\Request\ServerRequestFactory;
10+
use HttpSoft\Response\ResponseStatusCodeInterface;
11+
use HttpSoft\Tests\ErrorHandler\TestAsset\ErrorRequestHandler;
12+
use HttpSoft\Tests\ErrorHandler\TestAsset\ThrowableRequestHandler;
13+
use HttpSoft\Tests\ErrorHandler\TestAsset\FirstErrorListener;
14+
use HttpSoft\Tests\ErrorHandler\TestAsset\RequestHandler;
15+
use HttpSoft\Tests\ErrorHandler\TestAsset\SecondErrorListener;
16+
use PHPUnit\Framework\TestCase;
17+
use Psr\Http\Message\ResponseInterface;
18+
use Psr\Http\Message\ServerRequestInterface;
19+
20+
use function error_reporting;
21+
22+
use const E_ERROR;
23+
use const E_NOTICE;
24+
25+
class ErrorHandlerTest extends TestCase implements ResponseStatusCodeInterface
26+
{
27+
/**
28+
* @var int
29+
*/
30+
private int $errorReporting = 0;
31+
32+
/**
33+
* @var ServerRequestInterface
34+
*/
35+
private ServerRequestInterface $request;
36+
37+
public function setUp(): void
38+
{
39+
$this->request = ServerRequestFactory::create();
40+
$this->errorReporting = error_reporting();
41+
}
42+
43+
public function tearDown(): void
44+
{
45+
error_reporting($this->errorReporting);
46+
}
47+
48+
public function testWithRequestHandlerAndWithStatusOk(): void
49+
{
50+
$errorHandler = $this->createWithRequestHandler();
51+
$response = $errorHandler->handle($this->request);
52+
$this->assertInstanceOf(ResponseInterface::class, $response);
53+
$this->assertSame(self::STATUS_OK, $response->getStatusCode());
54+
$this->assertSame(self::PHRASES[self::STATUS_OK], $response->getReasonPhrase());
55+
}
56+
57+
public function testWithRequestHandlerAndWithStatusCreated(): void
58+
{
59+
$errorHandler = $this->createWithRequestHandler(self::STATUS_CREATED);
60+
$response = $errorHandler->handle($this->request);
61+
$this->assertInstanceOf(ResponseInterface::class, $response);
62+
$this->assertSame(self::STATUS_CREATED, $response->getStatusCode());
63+
$this->assertSame(self::PHRASES[self::STATUS_CREATED], $response->getReasonPhrase());
64+
}
65+
66+
public function testWithRequestHandlerAndWithErrorResponseGeneratorMock(): void
67+
{
68+
$errorResponseGenerator = $this->createMock(ErrorResponseGeneratorInterface::class);
69+
$errorHandler = new ErrorHandler(new RequestHandler(), $errorResponseGenerator);
70+
$response = $errorHandler->handle($this->request);
71+
$this->assertInstanceOf(ResponseInterface::class, $response);
72+
$this->assertSame(self::STATUS_OK, $response->getStatusCode());
73+
$this->assertSame(self::PHRASES[self::STATUS_OK], $response->getReasonPhrase());
74+
}
75+
76+
public function testWithThrowableRequestHandlerAndWithDefaultError(): void
77+
{
78+
$errorHandler = $this->createWithThrowableRequestHandler();
79+
$response = $errorHandler->handle($this->request);
80+
$this->assertInstanceOf(ResponseInterface::class, $response);
81+
$this->assertSame(self::STATUS_INTERNAL_SERVER_ERROR, $response->getStatusCode());
82+
$this->assertSame(self::PHRASES[self::STATUS_INTERNAL_SERVER_ERROR], $response->getReasonPhrase());
83+
}
84+
85+
public function testWithThrowableRequestHandlerAndWithDefaultErrorIfNotValidStatusCode(): void
86+
{
87+
$errorHandler = $this->createWithThrowableRequestHandler(self::STATUS_MOVED_PERMANENTLY);
88+
$response = $errorHandler->handle($this->request);
89+
$this->assertInstanceOf(ResponseInterface::class, $response);
90+
$this->assertSame(self::STATUS_INTERNAL_SERVER_ERROR, $response->getStatusCode());
91+
$this->assertSame(self::PHRASES[self::STATUS_INTERNAL_SERVER_ERROR], $response->getReasonPhrase());
92+
}
93+
94+
public function testWithThrowableRequestHandlerAndWithNotFoundError(): void
95+
{
96+
$errorHandler = $this->createWithThrowableRequestHandler(self::STATUS_NOT_FOUND);
97+
$response = $errorHandler->handle($this->request);
98+
$this->assertInstanceOf(ResponseInterface::class, $response);
99+
$this->assertSame(self::STATUS_NOT_FOUND, $response->getStatusCode());
100+
$this->assertSame(self::PHRASES[self::STATUS_NOT_FOUND], $response->getReasonPhrase());
101+
}
102+
103+
public function testWithErrorRequestHandlerAndWithoutCaughtError(): void
104+
{
105+
$errorHandler = $this->createWithErrorRequestHandler(E_ERROR);
106+
$response = $errorHandler->handle($this->request);
107+
$this->assertInstanceOf(ResponseInterface::class, $response);
108+
$this->assertSame(self::STATUS_OK, $response->getStatusCode());
109+
$this->assertSame(self::PHRASES[self::STATUS_OK], $response->getReasonPhrase());
110+
}
111+
112+
public function testWithErrorRequestHandlerAndWithCaughtError(): void
113+
{
114+
$errorHandler = $this->createWithErrorRequestHandler(E_NOTICE);
115+
$response = $errorHandler->handle($this->request);
116+
$this->assertInstanceOf(ResponseInterface::class, $response);
117+
$this->assertSame(self::STATUS_INTERNAL_SERVER_ERROR, $response->getStatusCode());
118+
$this->assertSame(self::PHRASES[self::STATUS_INTERNAL_SERVER_ERROR], $response->getReasonPhrase());
119+
}
120+
121+
public function testWithRequestHandlerAndWithAdditionOfListeners(): void
122+
{
123+
$errorHandler = $this->createWithRequestHandler();
124+
$errorHandler->addListener($firstListener = new FirstErrorListener());
125+
$errorHandler->addListener($secondListener = new SecondErrorListener());
126+
$response = $errorHandler->handle($this->request);
127+
128+
$this->assertInstanceOf(ResponseInterface::class, $response);
129+
$this->assertSame(self::STATUS_OK, $response->getStatusCode());
130+
$this->assertSame(self::PHRASES[self::STATUS_OK], $response->getReasonPhrase());
131+
132+
$this->assertFalse($firstListener->triggered());
133+
$this->assertFalse($secondListener->triggered());
134+
}
135+
136+
public function testWithThrowableRequestHandlerAndWithAdditionOfListeners(): void
137+
{
138+
$errorHandler = $this->createWithThrowableRequestHandler();
139+
$errorHandler->addListener($firstListener = new FirstErrorListener());
140+
$errorHandler->addListener($secondListener = new SecondErrorListener());
141+
$response = $errorHandler->handle($this->request);
142+
143+
$this->assertInstanceOf(ResponseInterface::class, $response);
144+
$this->assertSame(self::STATUS_INTERNAL_SERVER_ERROR, $response->getStatusCode());
145+
$this->assertSame(self::PHRASES[self::STATUS_INTERNAL_SERVER_ERROR], $response->getReasonPhrase());
146+
147+
$this->assertTrue($firstListener->triggered());
148+
$this->assertTrue($secondListener->triggered());
149+
}
150+
151+
public function testWithErrorRequestHandlerAndWithAdditionOfListeners(): void
152+
{
153+
$errorHandler = $this->createWithErrorRequestHandler(E_NOTICE);
154+
$errorHandler->addListener($firstListener = new FirstErrorListener());
155+
$errorHandler->addListener($secondListener = new SecondErrorListener());
156+
$response = $errorHandler->handle($this->request);
157+
158+
$this->assertInstanceOf(ResponseInterface::class, $response);
159+
$this->assertSame(self::STATUS_INTERNAL_SERVER_ERROR, $response->getStatusCode());
160+
$this->assertSame(self::PHRASES[self::STATUS_INTERNAL_SERVER_ERROR], $response->getReasonPhrase());
161+
162+
$this->assertTrue($firstListener->triggered());
163+
$this->assertTrue($secondListener->triggered());
164+
}
165+
166+
/**
167+
* @param int|null $code
168+
* @return ErrorHandler
169+
*/
170+
private function createWithRequestHandler(int $code = null): ErrorHandler
171+
{
172+
return new ErrorHandler(new RequestHandler($code));
173+
}
174+
175+
/**
176+
* @param int|null $code
177+
* @return ErrorHandler
178+
*/
179+
private function createWithThrowableRequestHandler(int $code = null): ErrorHandler
180+
{
181+
return new ErrorHandler(new ThrowableRequestHandler($code));
182+
}
183+
184+
/**
185+
* @param int $level
186+
* @return ErrorHandler
187+
*/
188+
private function createWithErrorRequestHandler(int $level): ErrorHandler
189+
{
190+
error_reporting($level);
191+
return new ErrorHandler(new ErrorRequestHandler());
192+
}
193+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace HttpSoft\Tests\ErrorHandler\TestAsset;
6+
7+
use HttpSoft\Response\ResponseFactory;
8+
use Psr\Http\Message\ResponseInterface;
9+
use Psr\Http\Message\ServerRequestInterface;
10+
use Psr\Http\Server\RequestHandlerInterface;
11+
use RuntimeException;
12+
13+
class ErrorRequestHandler implements RequestHandlerInterface
14+
{
15+
/**
16+
* @param ServerRequestInterface $request
17+
* @return ResponseInterface
18+
* @throws RuntimeException
19+
*/
20+
public function handle(ServerRequestInterface $request): ResponseInterface
21+
{
22+
$variable = $undefined;
23+
return ResponseFactory::create();
24+
}
25+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace HttpSoft\Tests\ErrorHandler\TestAsset;
6+
7+
use HttpSoft\ErrorHandler\ErrorListenerInterface;
8+
use Psr\Http\Message\ServerRequestInterface;
9+
use Throwable;
10+
11+
class FirstErrorListener implements ErrorListenerInterface
12+
{
13+
/**
14+
* @var bool
15+
*/
16+
private bool $triggered = false;
17+
18+
/**
19+
* @return bool
20+
*/
21+
public function triggered(): bool
22+
{
23+
return $this->triggered;
24+
}
25+
26+
/**
27+
* @param Throwable $error
28+
* @param ServerRequestInterface $request
29+
*/
30+
public function trigger(Throwable $error, ServerRequestInterface $request): void
31+
{
32+
$this->triggered = true;
33+
}
34+
}

tests/TestAsset/RequestHandler.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace HttpSoft\Tests\ErrorHandler\TestAsset;
6+
7+
use HttpSoft\Response\ResponseFactory;
8+
use Psr\Http\Message\ResponseInterface;
9+
use Psr\Http\Message\ServerRequestInterface;
10+
use Psr\Http\Server\RequestHandlerInterface;
11+
12+
class RequestHandler implements RequestHandlerInterface
13+
{
14+
/**
15+
* @var int|null
16+
*/
17+
private ?int $code;
18+
19+
/**
20+
* @param int|null $code
21+
*/
22+
public function __construct(int $code = null)
23+
{
24+
$this->code = $code;
25+
}
26+
27+
/**
28+
* @param ServerRequestInterface $request
29+
* @return ResponseInterface
30+
*/
31+
public function handle(ServerRequestInterface $request): ResponseInterface
32+
{
33+
return $this->code ? ResponseFactory::create($this->code) : ResponseFactory::create();
34+
}
35+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace HttpSoft\Tests\ErrorHandler\TestAsset;
6+
7+
use HttpSoft\ErrorHandler\ErrorListenerInterface;
8+
use Psr\Http\Message\ServerRequestInterface;
9+
use Throwable;
10+
11+
class SecondErrorListener implements ErrorListenerInterface
12+
{
13+
/**
14+
* @var bool
15+
*/
16+
private bool $triggered = false;
17+
18+
/**
19+
* @return bool
20+
*/
21+
public function triggered(): bool
22+
{
23+
return $this->triggered;
24+
}
25+
26+
/**
27+
* @param Throwable $error
28+
* @param ServerRequestInterface $request
29+
*/
30+
public function trigger(Throwable $error, ServerRequestInterface $request): void
31+
{
32+
$this->triggered = true;
33+
}
34+
}

0 commit comments

Comments
 (0)