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