-
-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathThrowableResponseFactory.php
More file actions
190 lines (166 loc) · 6.33 KB
/
ThrowableResponseFactory.php
File metadata and controls
190 lines (166 loc) · 6.33 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
<?php
declare(strict_types=1);
namespace Yiisoft\ErrorHandler\Factory;
use Throwable;
use InvalidArgumentException;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Yiisoft\ErrorHandler\ErrorHandler;
use Yiisoft\ErrorHandler\HeadersProvider;
use Yiisoft\ErrorHandler\Renderer\HeaderRenderer;
use Yiisoft\ErrorHandler\Renderer\HtmlRenderer;
use Yiisoft\ErrorHandler\Renderer\JsonRenderer;
use Yiisoft\ErrorHandler\Renderer\PlainTextRenderer;
use Yiisoft\ErrorHandler\Renderer\XmlRenderer;
use Yiisoft\ErrorHandler\ThrowableRendererInterface;
use Yiisoft\ErrorHandler\ThrowableResponseFactoryInterface;
use Yiisoft\Http\Header;
use Yiisoft\Http\HeaderValueHelper;
use Yiisoft\Http\Method;
use Yiisoft\Http\Status;
use function array_key_exists;
use function count;
use function is_subclass_of;
use function sprintf;
use function strtolower;
use function trim;
/**
* `ThrowableResponseFactory` renders `Throwable` object
* and produces a response according to the content type provided by the client.
*
* @deprecated Use {@see \Yiisoft\ErrorHandler\ThrowableResponseFactory} instead.
*/
final class ThrowableResponseFactory implements ThrowableResponseFactoryInterface
{
private HeadersProvider $headersProvider;
/**
* @psalm-var array<string,class-string<ThrowableRendererInterface>>
*/
private array $renderers = [
'application/json' => JsonRenderer::class,
'application/xml' => XmlRenderer::class,
'text/xml' => XmlRenderer::class,
'text/plain' => PlainTextRenderer::class,
'text/html' => HtmlRenderer::class,
'*/*' => HtmlRenderer::class,
];
private ?string $contentType = null;
public function __construct(
private readonly ResponseFactoryInterface $responseFactory,
private readonly ErrorHandler $errorHandler,
private readonly ContainerInterface $container,
?HeadersProvider $headersProvider = null,
) {
$this->headersProvider = $headersProvider ?? new HeadersProvider();
}
public function create(Throwable $throwable, ServerRequestInterface $request): ResponseInterface
{
$contentType = $this->contentType ?? $this->getContentType($request);
$renderer = $request->getMethod() === Method::HEAD ? new HeaderRenderer() : $this->getRenderer($contentType);
$data = $this->errorHandler->handle($throwable, $renderer, $request);
$response = $this->responseFactory->createResponse(Status::INTERNAL_SERVER_ERROR);
foreach ($this->headersProvider->getAll() as $name => $value) {
$response = $response->withHeader($name, $value);
}
return $data->addToResponse($response->withHeader(Header::CONTENT_TYPE, $contentType));
}
/**
* Returns a new instance with the specified content type and renderer class.
*
* @param string $contentType The content type to add associated renderers for.
* @param string $rendererClass The classname implementing the {@see ThrowableRendererInterface}.
*/
public function withRenderer(string $contentType, string $rendererClass): self
{
if (!is_subclass_of($rendererClass, ThrowableRendererInterface::class)) {
throw new InvalidArgumentException(sprintf(
'Class "%s" does not implement "%s".',
$rendererClass,
ThrowableRendererInterface::class,
));
}
$new = clone $this;
$new->renderers[$this->normalizeContentType($contentType)] = $rendererClass;
return $new;
}
/**
* Returns a new instance without renderers by the specified content types.
*
* @param string[] $contentTypes The content types to remove associated renderers for.
* If not specified, all renderers will be removed.
*/
public function withoutRenderers(string ...$contentTypes): self
{
$new = clone $this;
if (count($contentTypes) === 0) {
$new->renderers = [];
return $new;
}
foreach ($contentTypes as $contentType) {
unset($new->renderers[$this->normalizeContentType($contentType)]);
}
return $new;
}
/**
* Force content type to respond with regardless of request.
*
* @param string $contentType The content type to respond with regardless of request.
*/
public function forceContentType(string $contentType): self
{
$contentType = $this->normalizeContentType($contentType);
if (!isset($this->renderers[$contentType])) {
throw new InvalidArgumentException(sprintf('The renderer for %s is not set.', $contentType));
}
$new = clone $this;
$new->contentType = $contentType;
return $new;
}
/**
* Returns the renderer by the specified content type, or null if the renderer was not set.
*
* @param string $contentType The content type associated with the renderer.
*/
private function getRenderer(string $contentType): ?ThrowableRendererInterface
{
if (isset($this->renderers[$contentType])) {
/** @var ThrowableRendererInterface */
return $this->container->get($this->renderers[$contentType]);
}
return null;
}
/**
* Returns the priority content type from the accept request header.
*
* @return string The priority content type.
*/
private function getContentType(ServerRequestInterface $request): string
{
try {
foreach (HeaderValueHelper::getSortedAcceptTypes($request->getHeader(Header::ACCEPT)) as $header) {
if (array_key_exists($header, $this->renderers)) {
return $header;
}
}
} catch (InvalidArgumentException) {
// The Accept header contains an invalid q factor.
}
return '*/*';
}
/**
* Normalizes the content type.
*
* @param string $contentType The raw content type.
*
* @return string Normalized content type.
*/
private function normalizeContentType(string $contentType): string
{
if (!str_contains($contentType, '/')) {
throw new InvalidArgumentException('Invalid content type.');
}
return strtolower(trim($contentType));
}
}