Passed
Push — master ( cf5f25...797f28 )
by Anatoly
04:02
created

Message::normalizeHeaderName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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