Passed
Push — gh-pages ( 22b0fe...eb2d91 )
by
unknown
12:27 queued 10:15
created

Message   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 220
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 72
dl 0
loc 220
rs 10
c 1
b 0
f 0
wmc 27

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getHeaders() 0 2 1
A getProtocolVersion() 0 2 1
A trimHeaderValues() 0 2 1
A withHeader() 0 18 3
A __construct() 0 9 3
A withBody() 0 10 2
A getBody() 0 2 1
A hasHeader() 0 2 1
A setHeaders() 0 22 4
A withProtocolVersion() 0 10 2
A withAddedHeader() 0 21 3
A getHeaderLine() 0 2 1
A withoutHeader() 0 13 2
A getHeader() 0 7 2
1
<?php
2
/**
3
 * Class Message
4
 *
5
 * @created      11.08.2018
6
 * @author       smiley <[email protected]>
7
 * @copyright    2018 smiley
8
 * @license      MIT
9
 *
10
 * @phan-file-suppress PhanParamSignatureMismatch
11
 */
12
13
namespace chillerlan\HTTP\Psr7;
14
15
use chillerlan\HTTP\Psr17\StreamFactory;
16
use Psr\Http\Message\{MessageInterface, StreamInterface};
17
18
use function chillerlan\HTTP\Psr17\create_stream_from_input;
19
20
use function array_map, array_merge, implode, is_array, strtolower, trim;
21
22
abstract class Message implements MessageInterface{
23
24
	protected array $headers = [];
25
	/** @var string[] */
26
	protected array $headerNames = [];
27
28
	protected string $version;
29
30
	protected StreamInterface $body;
31
32
	protected StreamFactory $streamFactory;
33
34
	/**
35
	 * Message constructor.
36
	 *
37
	 * @param array|null                                             $headers
38
	 * @param null|string|resource|\Psr\Http\Message\StreamInterface $body
39
	 * @param string|null                                            $version
40
	 */
41
	public function __construct(array $headers = null, $body = null, string $version = null){
42
		$this->setHeaders(normalize_message_headers($headers ?? []));
43
44
		$this->version       = $version ?? '1.1';
45
		$this->streamFactory = new StreamFactory;
46
47
		$this->body = $body !== null && $body !== ''
48
			? create_stream_from_input($body)
49
			: $this->streamFactory->createStream();
50
	}
51
52
	/**
53
	 * @inheritDoc
54
	 */
55
	public function getProtocolVersion():string{
56
		return $this->version;
57
	}
58
59
	/**
60
	 * @inheritDoc
61
	 */
62
	public function withProtocolVersion($version):MessageInterface{
63
64
		if($this->version === $version){
65
			return $this;
66
		}
67
68
		$clone          = clone $this;
69
		$clone->version = $version;
70
71
		return $clone;
72
	}
73
74
	/**
75
	 * @inheritDoc
76
	 */
77
	public function getHeaders():array{
78
		return $this->headers;
79
	}
80
81
	/**
82
	 * @inheritDoc
83
	 */
84
	public function hasHeader($name):bool{
85
		return isset($this->headerNames[strtolower($name)]);
86
	}
87
88
	/**
89
	 * @inheritDoc
90
	 */
91
	public function getHeader($name):array{
92
93
		if(!$this->hasHeader($name)){
94
			return [];
95
		}
96
97
		return $this->headers[$this->headerNames[strtolower($name)]];
98
	}
99
100
	/**
101
	 * @inheritDoc
102
	 */
103
	public function getHeaderLine($name):string{
104
		return implode(', ', $this->getHeader($name));
105
	}
106
107
	/**
108
	 * @inheritDoc
109
	 */
110
	public function withHeader($name, $value):MessageInterface{
111
112
		if(!is_array($value)){
113
			$value = [$value];
114
		}
115
116
		$value      = $this->trimHeaderValues($value);
117
		$normalized = strtolower($name);
118
		$clone      = clone $this;
119
120
		if(isset($clone->headerNames[$normalized])){
121
			unset($clone->headers[$clone->headerNames[$normalized]]);
122
		}
123
124
		$clone->headerNames[$normalized] = $name;
125
		$clone->headers[$name]           = $value;
126
127
		return $clone;
128
	}
129
130
	/**
131
	 * @inheritDoc
132
	 */
133
	public function withAddedHeader($name, $value):MessageInterface{
134
135
		if(!is_array($value)){
136
			$value = [$value];
137
		}
138
139
		$value      = $this->trimHeaderValues($value);
140
		$normalized = strtolower($name);
141
		$clone      = clone $this;
142
143
		/** @noinspection DuplicatedCode */
144
		if(isset($clone->headerNames[$normalized])){
145
			$name                  = $this->headerNames[$normalized];
146
			$clone->headers[$name] = array_merge($this->headers[$name], $value);
147
		}
148
		else{
149
			$clone->headerNames[$normalized] = $name;
150
			$clone->headers[$name]           = $value;
151
		}
152
153
		return $clone;
154
	}
155
156
	/**
157
	 * @inheritDoc
158
	 */
159
	public function withoutHeader($name):MessageInterface{
160
		$normalized = strtolower($name);
161
162
		if(!isset($this->headerNames[$normalized])){
163
			return $this;
164
		}
165
166
		$name  = $this->headerNames[$normalized];
167
		$clone = clone $this;
168
169
		unset($clone->headers[$name], $clone->headerNames[$normalized]);
170
171
		return $clone;
172
	}
173
174
	/**
175
	 * @inheritDoc
176
	 */
177
	public function getBody():StreamInterface{
178
		return $this->body;
179
	}
180
181
	/**
182
	 * @inheritDoc
183
	 */
184
	public function withBody(StreamInterface $body):MessageInterface{
185
186
		if($body === $this->body){
187
			return $this;
188
		}
189
190
		$clone       = clone $this;
191
		$clone->body = $body;
192
193
		return $clone;
194
	}
195
196
	/**
197
	 * @param array $headers
198
	 */
199
	protected function setHeaders(array $headers):void{
200
		$this->headers     = [];
201
		$this->headerNames = [];
202
203
		foreach($headers as $name => $value){
204
205
			if(!is_array($value)){
206
				$value = [$value];
207
			}
208
209
			$value      = $this->trimHeaderValues($value);
210
			$normalized = strtolower($name);
211
212
			/** @noinspection DuplicatedCode */
213
			if(isset($this->headerNames[$normalized])){
214
				$name                 = $this->headerNames[$normalized];
215
				/** @phan-suppress-next-line PhanTypeInvalidDimOffset */
216
				$this->headers[$name] = array_merge($this->headers[$name], $value);
217
			}
218
			else{
219
				$this->headerNames[$normalized] = $name;
220
				$this->headers[$name]           = $value;
221
			}
222
223
		}
224
	}
225
226
	/**
227
	 * Trims whitespace from the header values.
228
	 *
229
	 * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
230
	 *
231
	 * header-field = field-name ":" OWS field-value OWS
232
	 * OWS          = *( SP / HTAB )
233
	 *
234
	 * @param string[] $values Header values
235
	 *
236
	 * @return string[] Trimmed header values
237
	 *
238
	 * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
239
	 */
240
	protected function trimHeaderValues(array $values):array{
241
		return array_map(fn(string $value):string => trim($value, " \t"), $values);
242
	}
243
244
}
245