Passed
Branch master (37a4df)
by Anatoly
02:49
created

Message::withMultipleHeaders()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 2
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\HeaderInterface;
20
21
/**
22
 * Hypertext Transfer Protocol Message
23
 *
24
 * @link https://tools.ietf.org/html/rfc7230
25
 * @link https://www.php-fig.org/psr/psr-7/
26
 */
27
class Message implements MessageInterface
28
{
29
30
	/**
31
	 * Protocol version for the message
32
	 *
33
	 * @var string
34
	 */
35
	protected $protocolVersion = '1.1';
36
37
	/**
38
	 * Headers of the message
39
	 *
40
	 * @var array
41
	 */
42
	protected $headers = [];
43
44
	/**
45
	 * Body of the message
46
	 *
47
	 * @var null|StreamInterface
48
	 */
49
	protected $body;
50
51
	/**
52
	 * {@inheritDoc}
53
	 */
54 1
	public function getProtocolVersion() : string
55
	{
56 1
		return $this->protocolVersion;
57
	}
58
59
	/**
60
	 * {@inheritDoc}
61
	 */
62 22
	public function withProtocolVersion($protocolVersion) : MessageInterface
63
	{
64 22
		$this->validateProtocolVersion($protocolVersion);
65
66 1
		$clone = clone $this;
67
68 1
		$clone->protocolVersion = $protocolVersion;
69
70 1
		return $clone;
71
	}
72
73
	/**
74
	 * {@inheritDoc}
75
	 */
76 13
	public function getHeaders() : array
77
	{
78 13
		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
98 3
		if (empty($this->headers[$name])) {
99 1
			return [];
100
		}
101
102 3
		return $this->headers[$name];
103
	}
104
105
	/**
106
	 * {@inheritDoc}
107
	 */
108 10
	public function getHeaderLine($name) : string
109
	{
110 10
		$name = $this->normalizeHeaderName($name);
111
112 10
		if (empty($this->headers[$name])) {
113 1
			return '';
114
		}
115
116 10
		return \implode(', ', $this->headers[$name]);
117
	}
118
119
	/**
120
	 * {@inheritDoc}
121
	 */
122 158
	public function withHeader($name, $value, bool $append = false) : MessageInterface
123
	{
124 158
		$this->validateHeaderName($name);
125 126
		$this->validateHeaderValue($value);
126
127 30
		$name = $this->normalizeHeaderName($name);
128 30
		$value = $this->normalizeHeaderValue($value);
129
130 30
		if (isset($this->headers[$name]) && $append) {
131 5
			$value = \array_merge($this->headers[$name], $value);
132
		}
133
134 30
		$clone = clone $this;
135
136 30
		$clone->headers[$name] = $value;
137
138 30
		return $clone;
139
	}
140
141
	/**
142
	 * {@inheritDoc}
143
	 */
144 68
	public function withAddedHeader($name, $value) : MessageInterface
145
	{
146 68
		return $this->withHeader($name, $value, true);
147
	}
148
149
	/**
150
	 * Returns a new instance with the given headers
151
	 *
152
	 * @param iterable $headers
153
	 * @param bool $append
154
	 *
155
	 * @return MessageInterface
156
	 *
157
	 * @since 1.3.0
158
	 */
159 1
	public function withMultipleHeaders(iterable $headers, bool $append = false) : MessageInterface
160
	{
161 1
		$result = clone $this;
162
163 1
		foreach ($headers as $name => $value) {
164 1
			$result = $result->withHeader($name, $value, $append);
165
		}
166
167 1
		return $result;
168
	}
169
170
	/**
171
	 * {@inheritDoc}
172
	 */
173 2
	public function withoutHeader($name) : MessageInterface
174
	{
175 2
		$name = $this->normalizeHeaderName($name);
176
177 2
		$clone = clone $this;
178
179 2
		unset($clone->headers[$name]);
180
181 2
		return $clone;
182
	}
183
184
	/**
185
	 * {@inheritDoc}
186
	 */
187 5
	public function getBody() : ?StreamInterface
188
	{
189 5
		return $this->body;
190
	}
191
192
	/**
193
	 * {@inheritDoc}
194
	 */
195 6
	public function withBody(StreamInterface $body) : MessageInterface
196
	{
197 6
		$clone = clone $this;
198
199 6
		$clone->body = $body;
200
201 6
		return $clone;
202
	}
203
204
	/**
205
	 * Validates the given protocol version
206
	 *
207
	 * @param mixed $protocolVersion
208
	 *
209
	 * @return void
210
	 *
211
	 * @throws \InvalidArgumentException
212
	 *
213
	 * @link https://tools.ietf.org/html/rfc7230#section-2.6
214
	 * @link https://tools.ietf.org/html/rfc7540
215
	 */
216 22
	protected function validateProtocolVersion($protocolVersion) : void
217
	{
218 22
		if (! \is_string($protocolVersion)) {
219 9
			throw new \InvalidArgumentException('HTTP protocol version must be a string');
220
		}
221
222 13
		if (! \preg_match('/^\d(?:\.\d)?$/', $protocolVersion)) {
223 12
			throw new \InvalidArgumentException(
224 12
				\sprintf('The given protocol version "%s" is not valid', $protocolVersion)
225
			);
226
		}
227 1
	}
228
229
	/**
230
	 * Validates the given header name
231
	 *
232
	 * @param mixed $headerName
233
	 *
234
	 * @return void
235
	 *
236
	 * @throws \InvalidArgumentException
237
	 *
238
	 * @link https://tools.ietf.org/html/rfc7230#section-3.2
239
	 */
240 158
	protected function validateHeaderName($headerName) : void
241
	{
242 158
		if (! \is_string($headerName)) {
243 18
			throw new \InvalidArgumentException('Header name must be a string');
244
		}
245
246 140
		if (! \preg_match(HeaderInterface::RFC7230_TOKEN, $headerName)) {
247 14
			throw new \InvalidArgumentException(
248 14
				\sprintf('The given header name "%s" is not valid', $headerName)
249
			);
250
		}
251 126
	}
252
253
	/**
254
	 * Validates the given header value
255
	 *
256
	 * @param mixed $headerValue
257
	 *
258
	 * @return void
259
	 *
260
	 * @throws \InvalidArgumentException
261
	 *
262
	 * @link https://tools.ietf.org/html/rfc7230#section-3.2
263
	 */
264 126
	protected function validateHeaderValue($headerValue) : void
265
	{
266 126
		if (\is_string($headerValue)) {
267 31
			$headerValue = [$headerValue];
268
		}
269
270 126
		if (! \is_array($headerValue) || [] === $headerValue) {
271 18
			throw new \InvalidArgumentException('Header value must be a string or not an empty array');
272
		}
273
274 108
		foreach ($headerValue as $oneOf) {
275 108
			if (! \is_string($oneOf)) {
276 60
				throw new \InvalidArgumentException('Header value must be a string or an array containing only strings');
277
			}
278
279 90
			if (! \preg_match(HeaderInterface::RFC7230_FIELD_VALUE, $oneOf)) {
280 18
				throw new \InvalidArgumentException(
281 90
					\sprintf('The given header value "%s" is not valid', $oneOf)
282
				);
283
			}
284
		}
285 30
	}
286
287
	/**
288
	 * Normalizes the given header name
289
	 *
290
	 * @param string $headerName
291
	 *
292
	 * @return string
293
	 *
294
	 * @link https://tools.ietf.org/html/rfc7230#section-3.2
295
	 */
296 30
	protected function normalizeHeaderName($headerName) : string
297
	{
298
		// Each header field consists of a case-insensitive field name...
299 30
		$headerName = \strtolower($headerName);
300
301 30
		return $headerName;
302
	}
303
304
	/**
305
	 * Normalizes the given header value
306
	 *
307
	 * @param string|array $headerValue
308
	 *
309
	 * @return array
310
	 */
311 30
	protected function normalizeHeaderValue($headerValue) : array
312
	{
313 30
		$headerValue = (array) $headerValue;
314 30
		$headerValue = \array_values($headerValue);
315
316 30
		return $headerValue;
317
	}
318
}
319