Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
70 / 70
100.00% covered (success)
100.00%
18 / 18
CRAP
100.00% covered (success)
100.00%
1 / 1
MessageTrait
100.00% covered (success)
100.00%
70 / 70
100.00% covered (success)
100.00%
18 / 18
30
100.00% covered (success)
100.00%
1 / 1
 getProtocolVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withProtocolVersion
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getHeaders
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasHeader
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHeader
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getHeaderLine
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withHeader
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 withAddedHeader
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 withoutHeader
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getBody
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 withBody
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setHeaders
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 trimHeaderValues
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 setProtocolVersion
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setHeader
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 addHeader
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 deleteHeader
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 setBody
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Tal;
4
5use GuzzleHttp\Psr7\Utils;
6use Psr\Http\Message\MessageInterface;
7use function GuzzleHttp\Psr7\stream_for;
8use Psr\Http\Message\StreamInterface;
9
10/**
11 * Trait implementing functionality common to requests and responses.
12 */
13trait MessageTrait
14{
15    /** @var array Map of all registered headers, as original name => array of values */
16    protected $headers = [];
17
18    /** @var array Map of lowercase header name => original name at registration */
19    protected $headerNames  = [];
20
21    /** @var string */
22    protected $protocol = '1.1';
23
24    /** @var StreamInterface */
25    protected $stream;
26
27    public function getProtocolVersion(): string
28    {
29        return $this->protocol;
30    }
31
32    public function withProtocolVersion(string $version): MessageInterface
33    {
34        if ($this->protocol === $version) {
35            return $this;
36        }
37
38        $new = clone $this;
39        return $new->setProtocolVersion($version);
40    }
41
42    public function getHeaders(): array
43    {
44        return $this->headers;
45    }
46
47    public function hasHeader(string $header): bool
48    {
49        return isset($this->headerNames[strtolower($header)]);
50    }
51
52    public function getHeader(string $header): array
53    {
54        $header = strtolower($header);
55
56        if (!isset($this->headerNames[$header])) {
57            return [];
58        }
59
60        $header = $this->headerNames[$header];
61
62        return $this->headers[$header];
63    }
64
65    public function getHeaderLine(string $header): string
66    {
67        return implode(', ', $this->getHeader($header));
68    }
69
70    public function withHeader(string $header, $value): MessageInterface
71    {
72        $new = clone $this;
73        return $new->setHeader($header, $value);
74    }
75
76    public function withAddedHeader(string $header, $value): MessageInterface
77    {
78        $new = clone $this;
79        return $new->addHeader($header, $value);
80    }
81
82    public function withoutHeader(string $header): MessageInterface
83    {
84        $normalized = strtolower($header);
85
86        if (!isset($this->headerNames[$normalized])) {
87            return $this;
88        }
89
90        $new = clone $this;
91        return $new->deleteHeader($header);
92    }
93
94    public function getBody(): StreamInterface
95    {
96        if (!$this->stream) {
97            $this->stream = Utils::streamFor('');
98        }
99
100        return $this->stream;
101    }
102
103    public function withBody(StreamInterface $body): MessageInterface
104    {
105        if ($body === $this->stream) {
106            return $this;
107        }
108
109        $new = clone $this;
110        return $new->setBody($body);
111    }
112
113    protected function setHeaders(array $headers)
114    {
115        $this->headerNames = $this->headers = [];
116        foreach ($headers as $header => $value) {
117            if (!is_array($value)) {
118                $value = [$value];
119            }
120
121            $value = $this->trimHeaderValues($value);
122            $normalized = strtolower($header);
123            if (isset($this->headerNames[$normalized])) {
124                $header = $this->headerNames[$normalized];
125                $this->headers[$header] = array_merge($this->headers[$header], $value);
126            } else {
127                $this->headerNames[$normalized] = $header;
128                $this->headers[$header] = $value;
129            }
130        }
131    }
132
133    /**
134     * Trims whitespace from the header values.
135     *
136     * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
137     *
138     * header-field = field-name ":" OWS field-value OWS
139     * OWS          = *( SP / HTAB )
140     *
141     * @param string[] $values Header values
142     *
143     * @return string[] Trimmed header values
144     *
145     * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
146     */
147    protected function trimHeaderValues(array $values)
148    {
149        return array_map(function ($value) {
150            return trim($value, " \t");
151        }, $values);
152    }
153
154    /**
155     * Sets the specified HTTP protocol version.
156     *
157     * The version string MUST contain only the HTTP version number (e.g.,
158     * "1.1", "1.0").
159     *
160     * @param string $version HTTP protocol version
161     * @return static
162     */
163    protected function setProtocolVersion($version)
164    {
165        $this->protocol = $version;
166        return $this;
167    }
168
169    /**
170     * Sets the provided value replacing the specified header.
171     *
172     * While header names are case-insensitive, the casing of the header will
173     * be preserved by this function, and returned from getHeaders().
174     *
175     * @param string $header Case-insensitive header field name.
176     * @param string|string[] $value Header value(s).
177     * @return static
178     * @throws \InvalidArgumentException for invalid header names or values.
179     */
180    protected function setHeader($header, $value)
181    {
182        if (!is_array($value)) {
183            $value = [$value];
184        }
185
186        $value = $this->trimHeaderValues($value);
187        $normalized = strtolower($header);
188
189        if (isset($this->headerNames[$normalized])) {
190            unset($this->headers[$this->headerNames[$normalized]]);
191        }
192        $this->headerNames[$normalized] = $header;
193        $this->headers[$header] = $value;
194
195        return $this;
196    }
197
198    /**
199     * Adds the specified header appended with the given value.
200     *
201     * Existing values for the specified header will be maintained. The new
202     * value(s) will be appended to the existing list. If the header did not
203     * exist previously, it will be added.
204     *
205     * @param string $header Case-insensitive header field name to add.
206     * @param string|string[] $value Header value(s).
207     * @return static
208     * @throws \InvalidArgumentException for invalid header names or values.
209     */
210    protected function addHeader($header, $value)
211    {
212        if (!is_array($value)) {
213            $value = [$value];
214        }
215
216        $value = $this->trimHeaderValues($value);
217        $normalized = strtolower($header);
218
219        if (isset($this->headerNames[$normalized])) {
220            $header = $this->headerNames[$normalized];
221            $this->headers[$header] = array_merge($this->headers[$header], $value);
222        } else {
223            $this->headerNames[$normalized] = $header;
224            $this->headers[$header] = $value;
225        }
226
227        return $this;
228    }
229
230    /**
231     * Deletes the specified header.
232     *
233     * Header resolution MUST be done without case-sensitivity.
234     *
235     * @param string $header Case-insensitive header field name to remove.
236     * @return static
237     */
238    protected function deleteHeader($header)
239    {
240        $normalized = strtolower($header);
241
242        $header = $this->headerNames[$normalized];
243
244        unset($this->headers[$header], $this->headerNames[$normalized]);
245
246        return $this;
247    }
248
249    /**
250     * Sets the specified message body.
251     *
252     * The body MUST be a StreamInterface object.
253     *
254     * @param StreamInterface $body Body.
255     * @return static
256     * @throws \InvalidArgumentException When the body is not valid.
257     */
258    protected function setBody(StreamInterface $body)
259    {
260        $this->stream = $body;
261        return $this;
262    }
263}