Passed
Push — master ( 37a4df...e71162 )
by Anatoly
07:15 queued 11s
created

Message::withHeaderObject()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 5
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 9
ccs 6
cts 6
cp 1
crap 1
rs 10
1
<?php declare(strict_types=1);
2
3
/**
4
 * It's free open-source software released under the MIT License.
5
 *
6
 * @author Anatoly Fenric <[email protected]>
7
 * @copyright Copyright (c) 2018, Anatoly Fenric
8
 * @license https://github.com/sunrise-php/http-message/blob/master/LICENSE
9
 * @link https://github.com/sunrise-php/http-message
10
 */
11
12
namespace Sunrise\Http\Message;
13
14
/**
15
 * Import classes
16
 */
17
use Psr\Http\Message\MessageInterface;
18
use Psr\Http\Message\StreamInterface;
19
use Sunrise\Http\Header\HeaderCollectionInterface;
20
use Sunrise\Http\Header\HeaderInterface;
21
22
/**
23
 * Hypertext Transfer Protocol Message
24
 *
25
 * @link https://tools.ietf.org/html/rfc7230
26
 * @link https://www.php-fig.org/psr/psr-7/
27
 */
28
class Message implements MessageInterface
29
{
30
31
    /**
32
     * Protocol version for the message
33
     *
34
     * @var string
35
     */
36
    protected $protocolVersion = '1.1';
37
38
    /**
39
     * Headers of the message
40
     *
41
     * @var array
42
     */
43
    protected $headers = [];
44
45
    /**
46
     * Body of the message
47
     *
48
     * @var null|StreamInterface
49
     */
50
    protected $body;
51
52
    /**
53
     * {@inheritDoc}
54
     */
55 1
    public function getProtocolVersion() : string
56
    {
57 1
        return $this->protocolVersion;
58
    }
59
60
    /**
61
     * {@inheritDoc}
62
     */
63 22
    public function withProtocolVersion($protocolVersion) : MessageInterface
64
    {
65 22
        $this->validateProtocolVersion($protocolVersion);
66
67 1
        $clone = clone $this;
68 1
        $clone->protocolVersion = $protocolVersion;
69
70 1
        return $clone;
71
    }
72
73
    /**
74
     * {@inheritDoc}
75
     */
76 15
    public function getHeaders() : array
77
    {
78 15
        return $this->headers;
79
    }
80
81
    /**
82
     * {@inheritDoc}
83
     */
84 4
    public function hasHeader($name) : bool
85
    {
86 4
        $name = $this->normalizeHeaderName($name);
87
88 4
        return ! empty($this->headers[$name]);
89
    }
90
91
    /**
92
     * {@inheritDoc}
93
     */
94 3
    public function getHeader($name) : array
95
    {
96 3
        $name = $this->normalizeHeaderName($name);
97 3
        if (empty($this->headers[$name])) {
98 1
            return [];
99
        }
100
101 3
        return $this->headers[$name];
102
    }
103
104
    /**
105
     * {@inheritDoc}
106
     */
107 10
    public function getHeaderLine($name) : string
108
    {
109 10
        $name = $this->normalizeHeaderName($name);
110 10
        if (empty($this->headers[$name])) {
111 1
            return '';
112
        }
113
114 10
        return \implode(', ', $this->headers[$name]);
115
    }
116
117
    /**
118
     * {@inheritDoc}
119
     */
120 160
    public function withHeader($name, $value, bool $append = false) : MessageInterface
121
    {
122 160
        $this->validateHeaderName($name);
123 128
        $this->validateHeaderValue($value);
124
125 32
        $name = $this->normalizeHeaderName($name);
126 32
        $value = $this->normalizeHeaderValue($value);
127
128 32
        if (isset($this->headers[$name]) && $append) {
129 7
            $value = \array_merge($this->headers[$name], $value);
130
        }
131
132 32
        $clone = clone $this;
133 32
        $clone->headers[$name] = $value;
134
135 32
        return $clone;
136
    }
137
138
    /**
139
     * {@inheritDoc}
140
     */
141 68
    public function withAddedHeader($name, $value) : MessageInterface
142
    {
143 68
        return $this->withHeader($name, $value, true);
144
    }
145
146
    /**
147
     * Returns a new instance with the given headers
148
     *
149
     * [!] This method is not associated with PSR-7.
150
     *
151
     * @param iterable $headers
152
     * @param bool $append
153
     *
154
     * @return MessageInterface
155
     *
156
     * @since 1.3.0
157
     */
158 1
    public function withMultipleHeaders(iterable $headers, bool $append = false) : MessageInterface
159
    {
160 1
        $result = clone $this;
161
162 1
        foreach ($headers as $name => $value) {
163 1
            $result = $result->withHeader($name, $value, $append);
164
        }
165
166 1
        return $result;
167
    }
168
169
    /**
170
     * Returns a new instance with the given header
171
     *
172
     * [!] This method is not associated with PSR-7.
173
     *
174
     * @param HeaderInterface $header
175
     * @param bool $append
176
     *
177
     * @return MessageInterface
178
     *
179
     * @since 1.4.0
180
     */
181 1
    public function withHeaderObject(HeaderInterface $header, bool $append = false) : MessageInterface
182
    {
183 1
        $name = $header->getFieldName();
184 1
        $value = $header->getFieldValue();
185
186 1
        $result = clone $this;
187 1
        $result = $result->withHeader($name, $value, $append);
188
189 1
        return $result;
190
    }
191
192
    /**
193
     * Returns a new instance with the given headers
194
     *
195
     * [!] This method is not associated with PSR-7.
196
     *
197
     * @param HeaderCollectionInterface $headers
198
     * @param bool $append
199
     *
200
     * @return MessageInterface
201
     *
202
     * @since 1.4.0
203
     */
204 1
    public function withHeaderCollection(HeaderCollectionInterface $headers, bool $append = false) : MessageInterface
205
    {
206 1
        $result = clone $this;
207
208 1
        foreach ($headers as $header) {
209 1
            $name = $header->getFieldName();
210 1
            $value = $header->getFieldValue();
211
212 1
            $result = $result->withHeader($name, $value, $append);
213
        }
214
215 1
        return $result;
216
    }
217
218
    /**
219
     * {@inheritDoc}
220
     */
221 2
    public function withoutHeader($name) : MessageInterface
222
    {
223 2
        $name = $this->normalizeHeaderName($name);
224
225 2
        $clone = clone $this;
226
227 2
        unset($clone->headers[$name]);
228
229 2
        return $clone;
230
    }
231
232
    /**
233
     * {@inheritDoc}
234
     */
235 5
    public function getBody() : ?StreamInterface
236
    {
237 5
        return $this->body;
238
    }
239
240
    /**
241
     * {@inheritDoc}
242
     */
243 6
    public function withBody(StreamInterface $body) : MessageInterface
244
    {
245 6
        $clone = clone $this;
246 6
        $clone->body = $body;
247
248 6
        return $clone;
249
    }
250
251
    /**
252
     * Validates the given protocol version
253
     *
254
     * @param mixed $protocolVersion
255
     *
256
     * @return void
257
     *
258
     * @throws \InvalidArgumentException
259
     *
260
     * @link https://tools.ietf.org/html/rfc7230#section-2.6
261
     * @link https://tools.ietf.org/html/rfc7540
262
     */
263 22
    protected function validateProtocolVersion($protocolVersion) : void
264
    {
265 22
        if (! \is_string($protocolVersion)) {
266 9
            throw new \InvalidArgumentException('HTTP protocol version must be a string');
267
        }
268
269 13
        if (! \preg_match('/^\d(?:\.\d)?$/', $protocolVersion)) {
270 12
            throw new \InvalidArgumentException(
271 12
                \sprintf('The given protocol version "%s" is not valid', $protocolVersion)
272
            );
273
        }
274 1
    }
275
276
    /**
277
     * Validates the given header name
278
     *
279
     * @param mixed $headerName
280
     *
281
     * @return void
282
     *
283
     * @throws \InvalidArgumentException
284
     *
285
     * @link https://tools.ietf.org/html/rfc7230#section-3.2
286
     */
287 160
    protected function validateHeaderName($headerName) : void
288
    {
289 160
        if (! \is_string($headerName)) {
290 18
            throw new \InvalidArgumentException('Header name must be a string');
291
        }
292
293 142
        if (! \preg_match(HeaderInterface::RFC7230_TOKEN, $headerName)) {
294 14
            throw new \InvalidArgumentException(
295 14
                \sprintf('The given header name "%s" is not valid', $headerName)
296
            );
297
        }
298 128
    }
299
300
    /**
301
     * Validates the given header value
302
     *
303
     * @param mixed $headerValue
304
     *
305
     * @return void
306
     *
307
     * @throws \InvalidArgumentException
308
     *
309
     * @link https://tools.ietf.org/html/rfc7230#section-3.2
310
     */
311 128
    protected function validateHeaderValue($headerValue) : void
312
    {
313 128
        if (\is_string($headerValue)) {
314 33
            $headerValue = [$headerValue];
315
        }
316
317 128
        if (! \is_array($headerValue) || [] === $headerValue) {
318 18
            throw new \InvalidArgumentException(
319 18
                'Header value must be a string or not an empty array'
320
            );
321
        }
322
323 110
        foreach ($headerValue as $oneOf) {
324 110
            if (! \is_string($oneOf)) {
325 60
                throw new \InvalidArgumentException(
326 60
                    'Header value must be a string or an array containing only strings'
327
                );
328
            }
329
330 92
            if (! \preg_match(HeaderInterface::RFC7230_FIELD_VALUE, $oneOf)) {
331 18
                throw new \InvalidArgumentException(
332 18
                    \sprintf('The given header value "%s" is not valid', $oneOf)
333
                );
334
            }
335
        }
336 32
    }
337
338
    /**
339
     * Normalizes the given header name
340
     *
341
     * @param string $headerName
342
     *
343
     * @return string
344
     *
345
     * @link https://tools.ietf.org/html/rfc7230#section-3.2
346
     */
347 32
    protected function normalizeHeaderName($headerName) : string
348
    {
349
        // Each header field consists of a case-insensitive field name...
350 32
        $headerName = \strtolower($headerName);
351
352 32
        return $headerName;
353
    }
354
355
    /**
356
     * Normalizes the given header value
357
     *
358
     * @param string|array $headerValue
359
     *
360
     * @return array
361
     */
362 32
    protected function normalizeHeaderValue($headerValue) : array
363
    {
364 32
        $headerValue = (array) $headerValue;
365 32
        $headerValue = \array_values($headerValue);
366
367 32
        return $headerValue;
368
    }
369
}
370