-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathLogErrorHandler.php
More file actions
212 lines (190 loc) · 6.36 KB
/
LogErrorHandler.php
File metadata and controls
212 lines (190 loc) · 6.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
<?php
declare(strict_types=1);
namespace Dot\ErrorHandler;
use ErrorException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Throwable;
use Laminas\Log\Logger;
use Laminas\Log\LoggerInterface;
use Laminas\Stratigility\Exception\MissingResponseException;
use function error_reporting;
use function in_array;
use function restore_error_handler;
use function set_error_handler;
/**
* Error handler middleware.
*
* Use this middleware as the outermost (or close to outermost) middleware
* layer, and use it to intercept PHP errors and exceptions.
*
* The class offers two extension points:
*
* - Error response generators.
* - Listeners.
*
* Error response generators are callables with the following signature:
*
* <code>
* function (
* Throwable $e,
* ServerRequestInterface $request,
* ResponseInterface $response
* ) : ResponseInterface
* </code>
*
* These are provided the error, and the request responsible; the response
* provided is the response prototype provided to the ErrorHandler instance
* itself, and can be used as the basis for returning an error response.
*
* An error response generator must be provided as a constructor argument;
* if not provided, an instance of Laminas\Stratigility\Middleware\ErrorResponseGenerator
* will be used.
*
* Listeners use the following signature:
*
* <code>
* function (
* Throwable $e,
* ServerRequestInterface $request,
* ResponseInterface $response
* ) : void
* </code>
*
* Listeners are given the error, the request responsible, and the generated
* error response, and can then react to them. They are best suited for
* logging and monitoring purposes.
*
* Listeners are attached using the attachListener() method, and triggered
* in the order attached.
*/
class LogErrorHandler implements MiddlewareInterface, ErrorHandlerInterface
{
/**
* @var callable[]
*/
private $listeners = [];
/**
* @var callable Routine that will generate the error response.
*/
private $responseGenerator;
/**
* @var callable
*/
private $responseFactory;
/**
* @var LoggerInterface
*/
private $logger;
/**
* @param callable $responseFactory A factory capable of returning an
* empty ResponseInterface instance to update and return when returning
* an error response.
* @param null|callable $responseGenerator Callback that will generate the final
* error response; if none is provided, ErrorResponseGenerator is used.
*/
public function __construct(callable $responseFactory, callable $responseGenerator = null, LoggerInterface $logger = null)
{
$this->responseFactory = function () use ($responseFactory) : ResponseInterface {
return $responseFactory();
};
$this->responseGenerator = $responseGenerator ?: new ErrorResponseGenerator();
$this->logger = $logger;
}
/**
* Attach an error listener.
*
* Each listener receives the following three arguments:
*
* - Throwable $error
* - ServerRequestInterface $request
* - ResponseInterface $response
*
* These instances are all immutable, and the return values of
* listeners are ignored; use listeners for reporting purposes
* only.
*/
public function attachListener(callable $listener) : void
{
if (in_array($listener, $this->listeners, true)) {
return;
}
$this->listeners[] = $listener;
}
/**
* Middleware to handle errors and exceptions in layers it wraps.
*
* Adds an error handler that will convert PHP errors to ErrorException
* instances.
*
* Internally, wraps the call to $next() in a try/catch block, catching
* all PHP Throwables.
*
* When an exception is caught, an appropriate error response is created
* and returned instead; otherwise, the response returned by $next is
* used.
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
{
set_error_handler($this->createErrorHandler());
try {
$response = $handler->handle($request);
if (! $response instanceof ResponseInterface) {
throw new MissingResponseException('Application did not return a response');
}
} catch (Throwable $e) {
$response = $this->handleThrowable($e, $request);
}
restore_error_handler();
return $response;
}
/**
* Handles all throwables, generating and returning a response.
*
* Passes the error, request, and response prototype to createErrorResponse(),
* triggers all listeners with the same arguments (but using the response
* returned from createErrorResponse()), and then returns the response.
*/
private function handleThrowable(Throwable $e, ServerRequestInterface $request) : ResponseInterface
{
$generator = $this->responseGenerator;
if ($this->logger instanceof LoggerInterface) {
$this->logger->err($e->getMessage(), (array)$e);
}
$response = $generator($e, $request, ($this->responseFactory)());
$this->triggerListeners($e, $request, $response);
return $response;
}
/**
* Creates and returns a callable error handler that raises exceptions.
*
* Only raises exceptions for errors that are within the error_reporting mask.
*/
private function createErrorHandler() : callable
{
/**
* @throws ErrorException if error is not within the error_reporting mask.
*/
return function (int $errno, string $errstr, string $errfile, int $errline) : void {
if (! (error_reporting() & $errno)) {
// error_reporting does not include this error
return;
}
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
};
}
/**
* Trigger all error listeners.
*/
private function triggerListeners(
Throwable $error,
ServerRequestInterface $request,
ResponseInterface $response
) : void {
foreach ($this->listeners as $listener) {
$listener($error, $request, $response);
}
}
}