Issues (41)

src/Message/MessageTrait.php (8 issues)

1
<?php
2
3
namespace DMT\Aura\Psr\Message;
4
5
use Aura\Web\Request as AuraRequest;
6
use Aura\Web\Response as AuraResponse;
7
use DMT\Aura\Psr\Helpers\HelperFactory;
8
use InvalidArgumentException;
9
use Psr\Http\Message\StreamInterface;
10
11
/**
12
 * Trait MessageTrait
13
 *
14
 * @package DMT\Aura\Psr\Message
15
 */
16
trait MessageTrait
17
{
18
    /** @var StreamInterface $body */
19
    private $body;
20
    /** @var AuraRequest|AuraResponse $object */
21
    private $object;
22
23
    /**
24
     * @return AuraRequest|AuraResponse
25
     */
26
    abstract public function getInnerObject();
27
28
    /**
29
     * Retrieves the HTTP protocol version as a string.
30
     *
31
     * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
32
     *
33
     * @return string HTTP protocol version.
34
     */
35 3
    public function getProtocolVersion(): string
36
    {
37 3
        $version = '';
38
39 3
        $innerObject = $this->getInnerObject();
40 3
        if ($innerObject instanceof AuraRequest) {
41 1
            $version = str_replace('HTTP/', '', $innerObject->server->get('SERVER_PROTOCOL', '1.1'));
42 2
        } elseif ($innerObject instanceof AuraResponse) {
0 ignored issues
show
$innerObject is always a sub-type of Aura\Web\Response.
Loading history...
43 2
            $version = $innerObject->status->getVersion();
44
        }
45
46 3
        return $version;
47
    }
48
49
    /**
50
     * Return an instance with the specified HTTP protocol version.
51
     *
52
     * @param string $version HTTP protocol version
53
     * @return static
54
     */
55 3
    public function withProtocolVersion($version): self
56
    {
57 3
        $instance = clone($this);
58
59 3
        $innerObject = $instance->getInnerObject();
60 3
        if ($innerObject instanceof AuraRequest) {
61 1
            $server = $innerObject->server;
62 1
            $server['SERVER_PROTOCOL'] = 'HTTP/' . $version;
63 2
        } elseif ($innerObject instanceof AuraResponse) {
0 ignored issues
show
$innerObject is always a sub-type of Aura\Web\Response.
Loading history...
64 2
            $instance->setObjectProperty($innerObject->status, 'version', $version);
65
        }
66
67 3
        return $instance;
68
    }
69
70
    /**
71
     * Retrieves all message header values.
72
     *
73
     * While header names are not case-sensitive, getHeaders() will preserve the
74
     * exact case in which headers were originally specified.
75
     *
76
     * @return string[][] Returns an associative array of the message's headers. Each
77
     *     key MUST be a header name, and each value MUST be an array of strings
78
     *     for that header.
79
     */
80 28
    public function getHeaders(): array
81
    {
82 28
        return $this->getInnerObject()->headers->get();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getInnerObject()->headers->get() could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
83
    }
84
85
    /**
86
     * Checks if a header exists by the given case-insensitive name.
87
     *
88
     * @param string $name Case-insensitive header field name.
89
     * @return bool Returns true if any header names match the given header
90
     *     name using a case-insensitive string comparison. Returns false if
91
     *     no matching header name is found in the message.
92
     */
93 35
    public function hasHeader($name): bool
94
    {
95 35
        if (empty($name) || !is_string($name) || !preg_match('~^[a-z\-]+$~i', $name)) {
96 8
            return false;
97
        }
98
99 27
        return !!preg_grep("~^{$name}$~i", array_keys($this->getHeaders()));
100
    }
101
102
    /**
103
     * Retrieves a message header value by the given case-insensitive name.
104
     *
105
     * This method returns an array of all the header values of the given
106
     * case-insensitive header name.
107
     *
108
     * If the header does not appear in the message, this method MUST return an
109
     * empty array.
110
     *
111
     * @param string $name Case-insensitive header field name.
112
     * @return string[] An array of string values as provided for the given
113
     *    header. If the header does not appear in the message, this method MUST
114
     *    return an empty array.
115
     */
116 18
    public function getHeader($name): array
117
    {
118 18
        $headers = $this->getHeaders();
119 18
        if ($key = current(preg_grep("~^" . preg_quote($name) . "$~i", array_keys($headers)))) {
120 17
            $name = $key;
121
        }
122
123 18
        if (!array_key_exists($name, $headers)) {
124 5
            return [];
125
        }
126
127 17
        return $this->normalizeHeaderValue($headers[$name]);
128
    }
129
130
    /**
131
     * Retrieves a comma-separated string of the values for a single header.
132
     *
133
     * @param string $name Case-insensitive header field name.
134
     * @return string A string of values as provided for the given header
135
     *    concatenated together using a comma. If the header does not appear in
136
     *    the message, this method MUST return an empty string.
137
     */
138 14
    public function getHeaderLine($name): string
139
    {
140 14
        return implode(',', $this->getHeader($name));
141
    }
142
143
    /**
144
     * Return an instance with the provided value replacing the specified header.
145
     *
146
     * @param string $name Case-insensitive header field name.
147
     * @param string|string[] $value Header value(s).
148
     * @return static
149
     * @throws InvalidArgumentException
150
     */
151 49
    public function withHeader($name, $value): self
152
    {
153 49
        if (!is_string($name) || $name === '' || !preg_match('~^[a-z\-]+$~i', $name)) {
0 ignored issues
show
The condition is_string($name) is always true.
Loading history...
154 16
            throw new InvalidArgumentException('invalid header name');
155
        }
156
157 33
        if ((!is_string($value) && !is_array($value)) || $value === []) {
0 ignored issues
show
The condition is_array($value) is always true.
Loading history...
158 12
            throw new InvalidArgumentException('invalid header value');
159
        }
160
161 21
        $headers = $this->getHeaders();
162 21
        if ($key = current(preg_grep("~^{$name}$~i", array_keys($headers)))) {
163 12
           unset($headers[$key]);
164
        }
165 21
        $headers[$name] = $value;
166
167 21
        $instance = clone($this);
168 21
        $this->setObjectProperty(
169 21
            $instance->getInnerObject()->headers,
170 21
            $this->getInnerObject() instanceof AuraResponse ? 'headers' : 'data',
171 21
            $headers
172
        );
173
174 21
        return $instance;
175
    }
176
177
    /**
178
     * Return an instance with the specified header appended with the given value.
179
     *
180
     * Existing values for the specified header will be maintained. The new
181
     * value(s) will be appended to the existing list. If the header did not
182
     * exist previously, it will be added.
183
     *
184
     * @param string $name Case-insensitive header field name to add.
185
     * @param string|string[] $value Header value(s).
186
     * @return static
187
     * @throws InvalidArgumentException for invalid header names or values.
188
     */
189 30
    public function withAddedHeader($name, $value): self
190
    {
191 30
        if ($this->hasHeader($name)) {
192 12
            $value = array_merge_recursive(array_values($this->getHeader($name)), (array)$value);
193
        }
194
195 30
        return $this->withHeader($name, $value);
196
    }
197
198
    /**
199
     * Return an instance without the specified header.
200
     *
201
     * @param string $name Case-insensitive header field name to remove.
202
     * @return static
203
     */
204 2
    public function withoutHeader($name): self
205
    {
206 2
        if (!is_string($name) || $name === '' || !preg_match('~[a-z\-]~i', $name)) {
0 ignored issues
show
The condition is_string($name) is always true.
Loading history...
207
            throw new InvalidArgumentException('invalid header name');
208
        }
209
210 2
        $headers = $this->getHeaders();
211 2
        if ($key = current(preg_grep("~^{$name}$~i", array_keys($headers)))) {
212 2
            $name = $key;
213
        }
214 2
        unset($headers[$name]);
215
216 2
        $instance = clone($this);
217 2
        $this->setObjectProperty(
218 2
            $instance->getInnerObject()->headers,
219 2
            $this->getInnerObject() instanceof AuraResponse ? 'headers' : 'data',
220 2
            $headers
221
        );
222
223 2
        return $instance;
224
    }
225
226
    /**
227
     * Gets the body of the message.
228
     *
229
     * @return StreamInterface
230
     */
231 2
    public function getBody(): StreamInterface
232
    {
233 2
        return $this->body;
234
    }
235
236
    /**
237
     * Return an instance with the specified message body.
238
     *
239
     * @param StreamInterface $body
240
     * @return static
241
     * @throws InvalidArgumentException
242
     */
243 2
    public function withBody(StreamInterface $body): self
244
    {
245 2
        if (!$body instanceof Stream) {
246
            $body = new Stream($body->detach());
247
        }
248
249 2
        $instance = clone($this);
250 2
        $instance->body = $body;
251
252 2
        $object = $instance->getInnerObject();
253 2
        if ($object instanceof AuraRequest) {
254 1
            $this->setObjectProperty($object->content, 'raw', $body);
255 1
        } elseif ($object instanceof AuraResponse) {
0 ignored issues
show
$object is always a sub-type of Aura\Web\Response.
Loading history...
256 1
            $object->content->set($body);
257
        }
258
259 2
        return $instance;
260
    }
261
262
    /**
263
     * Normalize the header value.
264
     *
265
     * @param null|string|array $value
266
     * @return array
267
     */
268 17
    private function normalizeHeaderValue($value): array
269
    {
270 17
        if (null === $value) {
271
            return [];
272
        }
273
274 17
        if (is_string($value)) {
275 15
            $value = explode(',', $value);
276
        }
277
278 17
        if (!is_array($value) || empty($value)) {
0 ignored issues
show
The condition is_array($value) is always true.
Loading history...
279
            throw new InvalidArgumentException('invalid header value');
280
        }
281
282 17
        return $value;
283
    }
284
285 42
    public function __clone()
286
    {
287 42
        $this->object = (new HelperFactory())
288 42
            ->createHelper($this->getInnerObject())
289 42
            ->cloneObject();
290 42
    }
291
292 65
    protected function setObjectProperty($object, $property, $value)
293
    {
294 65
        (new HelperFactory())
295 65
            ->createHelper($object)
296 65
            ->setObjectProperty($property, $value);
297 65
    }
298
}
299