-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBody.php
More file actions
130 lines (110 loc) · 3.9 KB
/
Body.php
File metadata and controls
130 lines (110 loc) · 3.9 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
<?php
declare(strict_types=1);
namespace TinyBlocks\Http;
use JsonException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TinyBlocks\Http\Internal\Server\Stream\StreamFactory;
/**
* Decoded payload of an HTTP message, represented as an associative array.
*
* Supports construction from raw arrays, PSR-7 server requests, and PSR-7 responses.
* Individual entries are accessed as typed {@see Attribute} wrappers.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages
*/
final readonly class Body
{
private const int MAX_JSON_DEPTH = 64;
private function __construct(private array $data)
{
}
/**
* Creates a Body from an associative array of decoded data.
*
* @param array<string, mixed> $data The decoded body data.
* @return Body A Body wrapping the supplied data.
*/
public static function fromArray(array $data): Body
{
return new Body(data: $data);
}
/**
* Creates a Body from a PSR-7 response, decoding the JSON payload and degrading to empty on failure.
*
* @param ResponseInterface $response The PSR-7 response whose body is decoded.
* @return Body A Body carrying the decoded payload, or an empty Body when decoding fails.
*/
public static function fromResponse(ResponseInterface $response): Body
{
$stream = $response->getBody();
if ($stream->isSeekable()) {
$stream->rewind();
}
$raw = $stream->getContents();
if ($stream->isSeekable()) {
$stream->rewind();
}
try {
$decoded = json_decode(
$raw,
true,
Body::MAX_JSON_DEPTH,
JSON_THROW_ON_ERROR
);
} catch (JsonException) {
return new Body(data: []);
}
return new Body(data: is_array($decoded) ? $decoded : []);
}
/**
* Creates a Body from a PSR-7 server request, decoding the JSON payload up to 64 levels deep.
*
* When the raw body is empty, falls back to the parsed body and degrades to an empty Body
* when the parsed body is not an array. JSON decoding uses <code>JSON_THROW_ON_ERROR</code>;
* any decoding failure degrades to an empty Body rather than propagating the exception.
*
* @param ServerRequestInterface $request The incoming PSR-7 server request.
* @return Body A Body carrying the decoded payload, or an empty Body when decoding fails or
* the payload is not an array.
*/
public static function fromServerRequest(ServerRequestInterface $request): Body
{
$streamFactory = StreamFactory::fromStream(stream: $request->getBody());
if (!$streamFactory->isEmptyContent()) {
try {
$decoded = json_decode(
$streamFactory->content(),
true,
Body::MAX_JSON_DEPTH,
JSON_THROW_ON_ERROR
);
} catch (JsonException) {
return new Body(data: []);
}
return new Body(data: is_array($decoded) ? $decoded : []);
}
$parsedBody = $request->getParsedBody();
return new Body(data: is_array($parsedBody) ? $parsedBody : []);
}
/**
* Returns the Attribute associated with the given key.
*
* @param string $key The key to look up in the body.
* @return Attribute The Attribute wrapping the value, or wrapping <code>null</code> when absent.
*/
public function get(string $key): Attribute
{
$attributeValue = ($this->data[$key] ?? null);
return Attribute::from(value: $attributeValue);
}
/**
* Returns the Body as an associative array.
*
* @return array<string, mixed> The decoded body data.
*/
public function toArray(): array
{
return $this->data;
}
}