forked from zendframework/zend-expressive
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTemplatedErrorHandler.php
293 lines (268 loc) · 9.24 KB
/
TemplatedErrorHandler.php
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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
<?php
/**
* @see https://github.com/zendframework/zend-expressive for the canonical source repository
* @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-expressive/blob/master/LICENSE.md New BSD License
*/
namespace Zend\Expressive;
use Psr\Http\Message\RequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Zend\Diactoros\Stream;
use Zend\Stratigility\Utils;
/**
* Final handler with templated page capabilities.
*
* Provides the optional ability to render a template for each of 404 and
* general error conditions. If no template renderer is provided, returns
* empty responses with appropriate status codes.
*
* @deprecated since 1.1.0, to be removed in 2.0.0. The "final handler" concept
* will be replaced with a "default delegate", which will be an
* implementation of Interop\Http\ServerMiddleware\DelegateInterface that
* returns a canned response. Expressive will provide tools to migrate your
* code to use default delegates for 2.0; you will only need to manually
* change your code if you are extending this class.
*/
class TemplatedErrorHandler
{
/**
* Body size on the original response; used to compare against received
* response in order to determine if changes have been made.
*
* @var int
*/
private $bodySize;
/**
* Original response against which to compare when determining if the
* received response is a different instance, and thus should be directly
* returned.
*
* @var Response
*/
private $originalResponse;
/**
* Template renderer to use when rendering error pages; if not provided,
* only the status will be updated.
*
* @var Template\TemplateRendererInterface
*/
private $renderer;
/**
* Name of 404 template to use when creating 404 response content with the
* template renderer.
*
* @var string
*/
private $template404;
/**
* Name of error template to use when creating response content for pages
* with errors.
*
* @var string
*/
private $templateError;
/**
* @param null|Template\TemplateRendererInterface $renderer Template renderer.
* @param null|string $template404 Template to use for 404 responses.
* @param null|string $templateError Template to use for general errors.
* @param null|Response $originalResponse Original response (used to
* calculate if the response has changed during middleware
* execution).
*/
public function __construct(
Template\TemplateRendererInterface $renderer = null,
$template404 = 'error::404',
$templateError = 'error::error',
Response $originalResponse = null
) {
$this->renderer = $renderer;
$this->template404 = $template404;
$this->templateError = $templateError;
if ($originalResponse) {
$this->setOriginalResponse($originalResponse);
}
}
/**
* Set the original response for comparisons.
*
* @param Response $response
*/
public function setOriginalResponse(Response $response)
{
$this->bodySize = $response->getBody()->getSize();
$this->originalResponse = $response;
}
/**
* Final handler for an application.
*
* @param Request $request
* @param Response $response
* @param null|mixed $err
* @return Response
*/
public function __invoke(Request $request, Response $response, $err = null)
{
if (! $err) {
return $this->handlePotentialSuccess($request, $response);
}
return $this->handleErrorResponse($err, $request, $response);
}
/**
* Handle a non-exception error.
*
* If a template renderer is present, passes the following to the template
* specified in the $templateError property:
*
* - error (the error itself)
* - uri
* - status (response status)
* - reason (reason associated with response status)
* - request (full PSR-7 request instance)
* - response (full PSR-7 response instance)
*
* The results of rendering are then written to the response body.
*
* This method may be used as an extension point.
*
* @param mixed $error
* @param Request $request
* @param Response $response
* @return Response
*/
protected function handleError($error, Request $request, Response $response)
{
if ($this->renderer) {
$stream = new Stream('php://temp', 'wb+');
$stream->write(
$this->renderer->render($this->templateError, [
'uri' => $request->getUri(),
'error' => $error,
'status' => $response->getStatusCode(),
'reason' => $response->getReasonPhrase(),
'request' => $request,
'response' => $response,
])
);
return $response->withBody($stream);
}
return $response;
}
/**
* Prepare the exception for display.
*
* Proxies to `handleError()`; exists primarily to as an extension point
* for other handlers.
*
* @param \Throwable $exception
* @param Request $request
* @param Response $response
* @return Response
*/
protected function handleException($exception, Request $request, Response $response)
{
return $this->handleError($exception, $request, $response);
}
/**
* Handle a non-error condition.
*
* Non-error conditions mean either all middleware called $next(), and we
* have a complete response, or no middleware was able to handle the
* request.
*
* This method determines which occurred, returning the response in the
* first instance, and returning a 404 response in the second.
*
* @param Request $request
* @param Response $response
* @return Response
*/
private function handlePotentialSuccess(Request $request, Response $response)
{
if (! $this->originalResponse) {
// No original response detected; decide whether we have a
// response to return
return $this->marshalReceivedResponse($request, $response);
}
$originalResponse = $this->originalResponse;
$decoratedResponse = $request->getAttribute('originalResponse', $response);
if ($originalResponse !== $response
&& $originalResponse !== $decoratedResponse
) {
// Response does not match either the original response or the
// decorated response; return it verbatim.
return $response;
}
if (($originalResponse === $response || $decoratedResponse === $response)
&& $this->bodySize !== $response->getBody()->getSize()
) {
// Response matches either the original response or the
// decorated response; but the body size has changed; return it
// verbatim.
return $response;
}
return $this->create404($request, $response);
}
/**
* Determine whether to return the given response, or a 404.
*
* If no original response was present, we check to see if we have a 200
* response with empty content; if so, we treat it as a 404.
*
* Otherwise, we return the response intact.
*
* @param Request $request
* @param Response $response
* @return Response
*/
private function marshalReceivedResponse(Request $request, Response $response)
{
if ($response->getStatusCode() === 200
&& $response->getBody()->getSize() === 0
) {
return $this->create404($request, $response);
}
return $response;
}
/**
* Create a 404 response.
*
* If we have a template renderer composed, renders the 404 template into
* the response.
*
* @param Request $request
* @param Response $response
* @return Response
*/
private function create404(Request $request, Response $response)
{
if ($this->renderer) {
$stream = new Stream('php://temp', 'wb+');
$stream->write(
$this->renderer->render($this->template404, [ 'uri' => $request->getUri() ])
);
$response = $response->withBody($stream);
}
return $response->withStatus(404);
}
/**
* Handle an error response.
*
* Marshals the response status from the error.
*
* If the error is not an exception, it then proxies to handleError();
* otherwise, it proxies to handleException().
*
* @param mixed $error
* @param Request $request
* @param Response $response
* @return Response
*/
private function handleErrorResponse($error, Request $request, Response $response)
{
$response = $response->withStatus(Utils::getStatusCode($error, $response));
if (! $error instanceof \Exception && ! $error instanceof \Throwable) {
return $this->handleError($error, $request, $response);
}
return $this->handleException($error, $request, $response);
}
}