Completed
Push — master ( ac741e...b546d2 )
by Tobias
02:00
created

MessageTrait::withHeader()   C

Complexity

Conditions 8
Paths 13

Size

Total Lines 30
Code Lines 18

Duplication

Lines 5
Ratio 16.67 %

Code Coverage

Tests 17
CRAP Score 8.0109

Importance

Changes 0
Metric Value
dl 5
loc 30
ccs 17
cts 18
cp 0.9444
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 18
nc 13
nop 2
crap 8.0109
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Nyholm\Psr7;
6
7
use Nyholm\Psr7\Factory\StreamFactory;
8
use Psr\Http\Message\StreamInterface;
9
10
/**
11
 * Trait implementing functionality common to requests and responses.
12
 *
13
 * @author Michael Dowling and contributors to guzzlehttp/psr7
14
 * @author Tobias Nyholm <[email protected]>
15
 */
16
trait MessageTrait
17
{
18
    /** @var array Map of all registered headers, as original name => array of values */
19
    private $headers = [];
20
21
    /** @var array Map of lowercase header name => original name at registration */
22
    private $headerNames = [];
23
24
    /** @var string */
25
    private $protocol = '1.1';
26
27
    /** @var StreamInterface */
28
    private $stream;
29
30 5
    public function getProtocolVersion(): string
31
    {
32 5
        return $this->protocol;
33
    }
34
35 4
    public function withProtocolVersion($version): self
36
    {
37 4
        if ($this->protocol === $version) {
38 1
            return $this;
39
        }
40
41 3
        $new = clone $this;
42 3
        $new->protocol = $version;
43
44 3
        return $new;
45
    }
46
47 16
    public function getHeaders(): array
48
    {
49 16
        return $this->headers;
50
    }
51
52 82
    public function hasHeader($header): bool
53
    {
54 82
        return isset($this->headerNames[strtolower($header)]);
55
    }
56
57 25
    public function getHeader($header): array
58
    {
59 25
        $header = strtolower($header);
60
61 25
        if (!isset($this->headerNames[$header])) {
62 5
            return [];
63
        }
64
65 24
        $header = $this->headerNames[$header];
66
67 24
        return $this->headers[$header];
68
    }
69
70 23
    public function getHeaderLine($header): string
71
    {
72 23
        return implode(', ', $this->getHeader($header));
73
    }
74
75 20
    public function withHeader($header, $value): self
76
    {
77 20 View Code Duplication
        if (!is_array($value)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
78 17
            $value = [$value];
79
        } elseif (empty($value)) {
80 2
            throw new \InvalidArgumentException('Header values must be non-empty strings');
81
        }
82
83 18
        if (!is_string($header) || empty($header)) {
84 8
            throw new \InvalidArgumentException('Header name must be non-empty strings');
85
        }
86
87 10
        foreach ($value as $v) {
88 10
            if (!is_string($v)) {
89 10
                throw new \InvalidArgumentException('Header values must be non-empty strings');
90
            }
91
        }
92
93 6
        $value = $this->trimHeaderValues($value);
94 6
        $normalized = strtolower($header);
95
96 6
        $new = clone $this;
97 6
        if (isset($new->headerNames[$normalized])) {
98 1
            unset($new->headers[$new->headerNames[$normalized]]);
99
        }
100 6
        $new->headerNames[$normalized] = $header;
101 6
        $new->headers[$header] = $value;
102
103 6
        return $new;
104
    }
105
106 34
    public function withAddedHeader($header, $value): self
107
    {
108 34 View Code Duplication
        if (!is_array($value)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
109 29
            $value = [$value];
110 7
        } elseif (!empty($value)) {
111 5
            $value = array_values($value);
112
        } else {
113 2
            throw new \InvalidArgumentException('Header values must be non-empty strings');
114
        }
115
116 32
        if (!is_string($header) || empty($header)) {
117 8
            throw new \InvalidArgumentException('Header name must be non-empty strings');
118
        }
119
120 24
        foreach ($value as $v) {
121 24
            if (!is_string($v)) {
122 24
                throw new \InvalidArgumentException('Header values must be non-empty strings');
123
            }
124
        }
125
126 20
        $value = $this->trimHeaderValues($value);
127 20
        $normalized = strtolower($header);
128
129 20
        $new = clone $this;
130 20
        if (isset($new->headerNames[$normalized])) {
131 14
            $header = $this->headerNames[$normalized];
132 14
            $new->headers[$header] = array_merge($this->headers[$header], $value);
133
        } else {
134 18
            $new->headerNames[$normalized] = $header;
135 18
            $new->headers[$header] = $value;
136
        }
137
138 20
        return $new;
139
    }
140
141 5
    public function withoutHeader($header): self
142
    {
143 5
        $normalized = strtolower($header);
144
145 5
        if (!isset($this->headerNames[$normalized])) {
146 2
            return $this;
147
        }
148
149 3
        $header = $this->headerNames[$normalized];
150
151 3
        $new = clone $this;
152 3
        unset($new->headers[$header], $new->headerNames[$normalized]);
153
154 3
        return $new;
155
    }
156
157 9
    public function getBody(): StreamInterface
158
    {
159 9
        if (!$this->stream) {
160 3
            $this->stream = (new StreamFactory())->createStream('');
161
        }
162
163 9
        return $this->stream;
164
    }
165
166 4
    public function withBody(StreamInterface $body): self
167
    {
168 4
        if ($body === $this->stream) {
169 1
            return $this;
170
        }
171
172 3
        $new = clone $this;
173 3
        $new->stream = $body;
174
175 3
        return $new;
176
    }
177
178 143
    private function setHeaders(array $headers): void
179
    {
180 143
        $this->headerNames = $this->headers = [];
181 143
        foreach ($headers as $header => $value) {
182 11
            if (!is_array($value)) {
183 10
                $value = [$value];
184
            }
185
186 11
            $value = $this->trimHeaderValues($value);
187 11
            $normalized = strtolower($header);
188 11
            if (isset($this->headerNames[$normalized])) {
189
                $header = $this->headerNames[$normalized];
190
                $this->headers[$header] = array_merge($this->headers[$header], $value);
191
            } else {
192 11
                $this->headerNames[$normalized] = $header;
193 11
                $this->headers[$header] = $value;
194
            }
195
        }
196 143
    }
197
198
    /**
199
     * Trims whitespace from the header values.
200
     *
201
     * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
202
     *
203
     * header-field = field-name ":" OWS field-value OWS
204
     * OWS          = *( SP / HTAB )
205
     *
206
     * @param string[] $values Header values
207
     *
208
     * @return string[] Trimmed header values
209
     *
210
     * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
211
     */
212
    private function trimHeaderValues(array $values): array
213
    {
214 29
        return array_map(function (string $value) {
215 29
            return trim($value, " \t");
216 29
        }, $values);
217
    }
218
}
219