Passed
Branch master (e894a3)
by Melech
15:39 queued 01:19
created

Header   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 325
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 59
c 1
b 0
f 0
dl 0
loc 325
rs 9.76
wmc 33
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\Header;
15
16
use Valkyrja\Http\Message\Header\Contract\Header as Contract;
17
use Valkyrja\Http\Message\Header\Exception\UnsupportedOffsetSetException;
18
use Valkyrja\Http\Message\Header\Exception\UnsupportedOffsetUnsetException;
19
use Valkyrja\Http\Message\Header\Security\HeaderSecurity;
20
use Valkyrja\Http\Message\Header\Value\Contract\Value;
21
use Valkyrja\Http\Message\Header\Value\Value as HeaderValue;
22
23
use function array_filter;
24
use function array_map;
25
use function array_merge;
26
use function count;
27
use function explode;
28
use function implode;
29
use function is_string;
30
use function str_contains;
31
use function strtolower;
32
33
/**
34
 * Class Header.
35
 *
36
 * @author Melech Mizrachi
37
 */
38
class Header implements Contract
39
{
40
    /**
41
     * Deliminator between name and values.
42
     *
43
     * @var non-empty-string
44
     */
45
    protected const string DELIMINATOR = ':';
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected T_STRING, expecting '=' on line 45 at column 27
Loading history...
46
47
    /**
48
     * Deliminator for values.
49
     *
50
     * @var non-empty-string
51
     */
52
    protected const string VALUE_DELIMINATOR = ',';
53
54
    /**
55
     * The header name.
56
     *
57
     * @var string
58
     */
59
    protected string $name;
60
61
    /**
62
     * The normalized name, useful for comparison.
63
     *
64
     * @var string
65
     */
66
    protected string $normalizedName;
67
68
    /**
69
     * The values.
70
     *
71
     * @var Value[]
72
     */
73
    protected array $values = [];
74
75
    /**
76
     * The position during iteration.
77
     *
78
     * @var int
79
     */
80
    protected int $position = 0;
81
82
    /**
83
     * @param string       $name
84
     * @param Value|string ...$values
85
     */
86
    public function __construct(string $name, Value|string ...$values)
87
    {
88
        $this->rewind();
89
        $this->updateName($name);
90
        $this->updateValues(...$values);
91
    }
92
93
    /**
94
     * @inheritDoc
95
     */
96
    public static function fromValue(string $value): static
97
    {
98
        $header         = $value;
99
        $valuesAsString = '';
100
        $values         = [];
101
102
        if (str_contains($header, static::DELIMINATOR)) {
103
            [$header, $valuesAsString] = explode(static::DELIMINATOR, $value);
104
            $values = [$valuesAsString];
105
        }
106
107
        if (str_contains($valuesAsString, static::VALUE_DELIMINATOR)) {
108
            $values = explode(static::VALUE_DELIMINATOR, $valuesAsString);
109
        }
110
111
        return new static($header, ...$values);
112
    }
113
114
    /**
115
     * @inheritDoc
116
     */
117
    public function getName(): string
118
    {
119
        return $this->name;
120
    }
121
122
    /**
123
     * @inheritDoc
124
     */
125
    public function getNormalizedName(): string
126
    {
127
        return $this->normalizedName;
128
    }
129
130
    /**
131
     * @inheritDoc
132
     */
133
    public function withName(string $name): static
134
    {
135
        $new = clone $this;
136
137
        $new->updateName($name);
138
139
        return $new;
140
    }
141
142
    /**
143
     * @inheritDoc
144
     */
145
    public function getValues(): array
146
    {
147
        return $this->values;
148
    }
149
150
    /**
151
     * @inheritDoc
152
     */
153
    public function withValues(Value|string ...$values): static
154
    {
155
        $new = clone $this;
156
157
        $new->updateValues(...$values);
158
159
        return $new;
160
    }
161
162
    /**
163
     * @inheritDoc
164
     */
165
    public function withAddedValues(Value|string ...$values): static
166
    {
167
        $new = $this->withValues(...$values);
168
169
        $new->values = array_merge($this->values, $new->values);
170
171
        return $new;
172
    }
173
174
    /**
175
     * @inheritDoc
176
     */
177
    public function getValuesAsString(): string
178
    {
179
        return $this->valuesToString();
180
    }
181
182
    /**
183
     * @inheritDoc
184
     */
185
    public function asValue(): string
186
    {
187
        return $this->__toString();
188
    }
189
190
    /**
191
     * @inheritDoc
192
     */
193
    public function asFlatValue(): string
194
    {
195
        return $this->__toString();
196
    }
197
198
    /**
199
     * @inheritDoc
200
     */
201
    public function jsonSerialize(): string
202
    {
203
        return $this->asValue();
204
    }
205
206
    /**
207
     * @inheritDoc
208
     */
209
    public function __toString(): string
210
    {
211
        return $this->nameToString() . $this->valuesToString();
212
    }
213
214
    /**
215
     * @inheritDoc
216
     */
217
    public function offsetExists(mixed $offset): bool
218
    {
219
        return isset($this->values[$offset]);
220
    }
221
222
    /**
223
     * @inheritDoc
224
     */
225
    public function offsetGet(mixed $offset): Value
226
    {
227
        return $this->values[$offset];
228
    }
229
230
    /**
231
     * @inheritDoc
232
     */
233
    public function offsetSet(mixed $offset, mixed $value): void
234
    {
235
        throw new UnsupportedOffsetSetException('Use withValues or withAddedValues');
236
    }
237
238
    /**
239
     * @inheritDoc
240
     */
241
    public function offsetUnset(mixed $offset): void
242
    {
243
        throw new UnsupportedOffsetUnsetException('Use withValues or withAddedValues');
244
    }
245
246
    /**
247
     * @inheritDoc
248
     */
249
    public function count(): int
250
    {
251
        return count($this->values);
252
    }
253
254
    /**
255
     * @inheritDoc
256
     */
257
    public function current(): Value
258
    {
259
        return $this->values[$this->position];
260
    }
261
262
    /**
263
     * @inheritDoc
264
     */
265
    public function next(): void
266
    {
267
        $this->position++;
268
    }
269
270
    /**
271
     * @inheritDoc
272
     */
273
    public function key(): int
274
    {
275
        return $this->position;
276
    }
277
278
    /**
279
     * @inheritDoc
280
     */
281
    public function valid(): bool
282
    {
283
        return isset($this->values[$this->position]);
284
    }
285
286
    /**
287
     * @inheritDoc
288
     */
289
    public function rewind(): void
290
    {
291
        $this->position = 0;
292
    }
293
294
    /**
295
     * Map string values to Value objects.
296
     *
297
     * @param Value|string ...$values
298
     *
299
     * @return Value[]
300
     */
301
    protected function mapToValue(Value|string ...$values): array
302
    {
303
        return array_map(
304
            static fn (Value|string $value): Value => is_string($value) ? HeaderValue::fromValue($value) : $value,
305
            $values
306
        );
307
    }
308
309
    /**
310
     * @param string $name
311
     *
312
     * @return void
313
     */
314
    protected function updateName(string $name): void
315
    {
316
        HeaderSecurity::assertValidName($name);
317
318
        $this->name = $name;
319
320
        $this->normalizedName = strtolower($name);
321
    }
322
323
    protected function updateValues(Value|string ...$values): void
324
    {
325
        $this->assertHeaderValues(...$values);
326
        $this->values = $this->mapToValue(...$values);
327
    }
328
329
    /**
330
     * @return string
331
     */
332
    protected function nameToString(): string
333
    {
334
        return $this->name . ':';
335
    }
336
337
    /**
338
     * @see https://greenbytes.de/tech/webdav/rfc2616.html#message.headers
339
     *
340
     * @return string
341
     */
342
    protected function valuesToString(): string
343
    {
344
        $filteredValues = array_filter($this->values, static fn (Value $value): bool => $value->__toString() !== '');
345
346
        return implode(',', $filteredValues);
347
    }
348
349
    /**
350
     * Filter header values.
351
     *
352
     * @param Value|string ...$values Header values
353
     *
354
     * @return array<array-key, Value|string>
355
     */
356
    protected function assertHeaderValues(Value|string ...$values): array
357
    {
358
        foreach ($values as $value) {
359
            HeaderSecurity::assertValid((string) $value);
360
        }
361
362
        return $values;
363
    }
364
}
365