Passed
Push — master ( 2cda12...3e0ae5 )
by Melech
02:04 queued 37s
created

Message::injectHeader()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 34
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 6
nop 4
dl 0
loc 34
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Valkyrja\Http\Message\Trait;
15
16
use InvalidArgumentException;
17
use Valkyrja\Http\Message\Enum\ProtocolVersion;
18
use Valkyrja\Http\Message\Header\Security\HeaderSecurity;
19
use Valkyrja\Http\Message\Stream\Contract\Stream;
20
21
use function array_merge;
22
use function implode;
23
use function is_array;
24
use function strtolower;
25
26
/**
27
 * Trait MessageTrait.
28
 *
29
 * @author Melech Mizrachi
30
 */
31
trait Message
32
{
33
    /**
34
     * The headers with normalized header names.
35
     *
36
     * @var array<string, string[]>
37
     */
38
    protected array $headers = [];
39
40
    /**
41
     * Original header names.
42
     *
43
     * @var array<string, string>
44
     */
45
    protected array $headerNames = [];
46
47
    /**
48
     * The protocol version.
49
     *
50
     * @var ProtocolVersion
51
     */
52
    protected ProtocolVersion $protocolVersion = ProtocolVersion::V1_1;
53
54
    /**
55
     * The stream.
56
     *
57
     * @var Stream
58
     */
59
    protected Stream $stream;
60
61
    /**
62
     * @inheritDoc
63
     */
64
    public function getProtocolVersion(): ProtocolVersion
65
    {
66
        return $this->protocolVersion;
67
    }
68
69
    /**
70
     * @inheritDoc
71
     *
72
     * @return static
73
     */
74
    public function withProtocolVersion(ProtocolVersion $version): static
75
    {
76
        $new = clone $this;
77
78
        $new->protocolVersion = $version;
79
80
        return $new;
81
    }
82
83
    /**
84
     * @inheritDoc
85
     *
86
     * @return array<string, string[]>
87
     */
88
    public function getHeaders(): array
89
    {
90
        return $this->headers;
91
    }
92
93
    /**
94
     * @inheritDoc
95
     */
96
    public function hasHeader(string $name): bool
97
    {
98
        return isset($this->headerNames[strtolower($name)]);
99
    }
100
101
    /**
102
     * @inheritDoc
103
     *
104
     * @return string[]
105
     */
106
    public function getHeader(string $name): array
107
    {
108
        if (! $this->hasHeader($name)) {
109
            return [];
110
        }
111
112
        $name = $this->headerNames[strtolower($name)];
113
114
        return $this->headers[$name];
115
    }
116
117
    /**
118
     * @inheritDoc
119
     */
120
    public function getHeaderLine(string $name): string
121
    {
122
        $value = $this->getHeader($name);
123
124
        if (empty($value)) {
125
            return '';
126
        }
127
128
        return implode(',', $value);
129
    }
130
131
    /**
132
     * @inheritDoc
133
     *
134
     * @return static
135
     */
136
    public function withHeader(string $name, string ...$values): static
137
    {
138
        HeaderSecurity::assertValidName($name);
139
140
        $normalized = strtolower($name);
141
142
        $new = clone $this;
143
144
        if ($new->hasHeader($name)) {
145
            unset($new->headers[$new->headerNames[$normalized]]);
146
        }
147
148
        $new->headerNames[$normalized] = $name;
149
150
        $new->headers[$name] = $this->assertHeaderValues(...$values);
151
152
        return $new;
153
    }
154
155
    /**
156
     * @inheritDoc
157
     *
158
     * @param string ...$values Header value(s).
159
     *
160
     * @return static
161
     */
162
    public function withAddedHeader(string $name, string ...$values): static
163
    {
164
        HeaderSecurity::assertValidName($name);
165
166
        if (! $this->hasHeader($name)) {
167
            return $this->withHeader($name, ...$values);
168
        }
169
170
        $name = $this->headerNames[strtolower($name)];
171
172
        $new = clone $this;
173
174
        $new->headers[$name] = array_merge($this->headers[$name], $this->assertHeaderValues(...$values));
175
176
        return $new;
177
    }
178
179
    /**
180
     * @inheritDoc
181
     *
182
     * @return static
183
     */
184
    public function withoutHeader(string $name): static
185
    {
186
        if (! $this->hasHeader($name)) {
187
            return clone $this;
188
        }
189
190
        $normalized = strtolower($name);
191
        $original   = $this->headerNames[$normalized];
192
        $new        = clone $this;
193
194
        unset($new->headers[$original], $new->headerNames[$normalized]);
195
196
        return $new;
197
    }
198
199
    /**
200
     * @inheritDoc
201
     */
202
    public function getBody(): Stream
203
    {
204
        return $this->stream;
205
    }
206
207
    /**
208
     * @inheritDoc
209
     *
210
     * @return static
211
     */
212
    public function withBody(Stream $body): static
213
    {
214
        $new = clone $this;
215
216
        $new->setBody($body);
217
218
        $body->rewind();
219
220
        return $new;
221
    }
222
223
    public function __clone()
224
    {
225
        $this->stream = clone $this->stream;
226
    }
227
228
    /**
229
     * Set the body.
230
     *
231
     * @param Stream $body The body
232
     *
233
     * @return void
234
     */
235
    protected function setBody(Stream $body): void
236
    {
237
        $this->stream = $body;
238
    }
239
240
    /**
241
     * Set headers.
242
     *
243
     * @param array<string, string|string[]> $originalHeaders The original headers
244
     *
245
     * @throws InvalidArgumentException
246
     *
247
     * @return void
248
     */
249
    protected function setHeaders(array $originalHeaders): void
250
    {
251
        $headerNames = $headers = [];
252
253
        foreach ($originalHeaders as $header => $value) {
254
            $value = is_array($value) ? $value : [$value];
255
256
            HeaderSecurity::assertValidName($header);
257
258
            $headerNames[strtolower($header)] = $header;
259
260
            $headers[$header] = $this->assertHeaderValues(...$value);
261
        }
262
263
        $this->headerNames = $headerNames;
264
        $this->headers     = $headers;
265
    }
266
267
    /**
268
     * Filter header values.
269
     *
270
     * @param string ...$values Header values
271
     *
272
     * @throws InvalidArgumentException
273
     *
274
     * @return string[]
275
     */
276
    protected function assertHeaderValues(string ...$values): array
277
    {
278
        foreach ($values as $value) {
279
            HeaderSecurity::assertValid($value);
280
        }
281
282
        return $values;
283
    }
284
285
    /**
286
     * Inject a header in a headers array.
287
     *
288
     * @param string                       $header   The header to set
289
     * @param string                       $value    The value to set
290
     * @param array<string, string[]>|null $headers  [optional] The headers
291
     * @param bool                         $override [optional] Whether to override any existing value
292
     *
293
     * @return array<string, string[]>
294
     */
295
    protected function injectHeader(
296
        string $header,
297
        string $value,
298
        array|null $headers = null,
299
        bool $override = false
300
    ): array {
301
        // The headers
302
        $headers ??= [];
303
        // Normalize the content type header
304
        $normalized = strtolower($header);
305
        // The original value for the header (if it exists in the headers array)
306
        // Defaults to the value passed in
307
        $originalValue = [$value];
308
309
        // Iterate through all the headers
310
        foreach ($headers as $headerIndex => $headerValue) {
311
            // Normalize the header name and check if it matches the normalized
312
            // passed in header
313
            if (strtolower($headerIndex) === $normalized) {
314
                // Set the original value as this header value
315
                $originalValue = $headerValue;
316
317
                // Unset the header as we want to use the header string that was
318
                // passed in as the header
319
                unset($headers[$headerIndex]);
320
            }
321
        }
322
323
        // Set the header in the headers list
324
        $headers[$header] = $override
325
            ? [$value]
326
            : $originalValue;
327
328
        return $headers;
329
    }
330
}
331