Completed
Push — v3 ( 80ddeb...1a8e03 )
by Mihail
08:08
created

HeaderTrait   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 207
Duplicated Lines 0 %

Importance

Changes 6
Bugs 1 Features 0
Metric Value
eloc 89
c 6
b 1
f 0
dl 0
loc 207
rs 9.76
wmc 33

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getHeader() 0 14 5
A normalizeHeader() 0 9 2
A getCanonicalizedHeaders() 0 14 3
A getFlattenedHeaders() 0 7 2
A withoutHeader() 0 11 2
A setHeaders() 0 9 3
A withHeader() 0 9 1
A normalizeHeaderValue() 0 18 4
A getHeaders() 0 3 1
A normalizeHeaderName() 0 7 2
A withAddedHeader() 0 15 2
A withHeaders() 0 7 2
A replaceHeaders() 0 8 2
A hasHeader() 0 3 1
A getHeaderLine() 0 3 1
1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of the Koded package.
5
 *
6
 * (c) Mihail Binev <[email protected]>
7
 *
8
 * Please view the LICENSE distributed with this source code
9
 * for the full copyright and license information.
10
 *
11
 */
12
13
namespace Koded\Http;
14
15
use Koded\Http\Interfaces\HttpStatus;
16
17
trait HeaderTrait
18
{
19
    /**
20
     * @var array Message headers.
21
     */
22
    protected array $headers = [];
23
24
    /**
25
     * @var array Used for case-insensitivity header name checks
26
     */
27
    protected array $headersMap = [];
28
29
    public function getHeaders(): array
30
    {
31
        return $this->headers;
32
    }
33
34
    public function getHeader($name): array
35
    {
36
        if (false === isset($this->headersMap[$name = \strtolower($name)])) {
37
            return [];
38
        }
39
        $value = $this->headers[$this->headersMap[$name]];
40
        if (\is_string($value)) {
41
            return empty($value) ? [] : [$value];
42
        }
43
        $header = [];
44
        foreach ($value as $_ => $v) {
45
            $header[] = \join(',', (array)$v);
46
        }
47
        return $header;
48
    }
49
50
    public function getHeaderLine($name): string
51
    {
52
        return \join(',', $this->getHeader($name));
53
    }
54
55
    public function withHeader($name, $value): static
56
    {
57
        $instance = clone $this;
58
        $name     = $instance->normalizeHeaderName($name);
59
60
        $instance->headersMap[\strtolower($name)] = $name;
61
62
        $instance->headers[$name] = $this->normalizeHeaderValue($name, $value);
63
        return $instance;
64
    }
65
66
    public function withHeaders(array $headers): static
67
    {
68
        $instance = clone $this;
69
        foreach ($headers as $name => $value) {
70
            $instance->normalizeHeader($name, $value, false);
71
        }
72
        return $instance;
73
    }
74
75
    public function withoutHeader($name): static
76
    {
77
        $instance = clone $this;
78
        $name     = \strtolower($name);
79
        if (isset($instance->headersMap[$name])) {
80
            unset(
81
                $instance->headers[$this->headersMap[$name]],
82
                $instance->headersMap[$name]
83
            );
84
        }
85
        return $instance;
86
    }
87
88
    public function withAddedHeader($name, $value): static
89
    {
90
        $instance = clone $this;
91
        $name     = $instance->normalizeHeaderName($name);
92
        $value    = $instance->normalizeHeaderValue($name, $value);
93
        if (isset($instance->headersMap[$header = \strtolower($name)])) {
94
            $header                     = $instance->headersMap[$header];
95
            $instance->headers[$header] = \array_unique(
96
                \array_merge_recursive($instance->headers[$header], $value)
97
            );
98
        } else {
99
            $instance->headersMap[$header] = $name;
100
            $instance->headers[$name]      = $value;
101
        }
102
        return $instance;
103
    }
104
105
    public function hasHeader($name): bool
106
    {
107
        return \array_key_exists(\strtolower($name), $this->headersMap);
108
    }
109
110
    public function replaceHeaders(array $headers): static
111
    {
112
        $instance          = clone $this;
113
        $instance->headers = $instance->headersMap = [];
114
        foreach ($headers as $name => $value) {
115
            $instance->normalizeHeader($name, $value, false);
116
        }
117
        return $instance;
118
    }
119
120
    /**
121
     * Transforms the nested headers as a flatten array.
122
     * This method is not part of the PSR-7.
123
     *
124
     * @return array
125
     */
126
    public function getFlattenedHeaders(): array
127
    {
128
        $flattenHeaders = [];
129
        foreach ($this->headers as $name => $value) {
130
            $flattenHeaders[] = $name . ':' . \join(',', (array)$value);
131
        }
132
        return $flattenHeaders;
133
    }
134
135
    public function getCanonicalizedHeaders(array $names = []): string
136
    {
137
        if (empty($names)) {
138
            $names = \array_keys($this->headers);
139
        }
140
        if (!$headers = \array_reduce($names, function($list, $name) {
141
            $name   = \str_replace('_', '-', $name);
142
            $list[] = \strtolower($name) . ':' . \join(',', $this->getHeader($name));
143
            return $list;
144
        })) {
145
            return '';
146
        }
147
        \sort($headers);
148
        return \join("\n", $headers);
149
    }
150
151
    /**
152
     * @param string          $name
153
     * @param string|string[] $value
154
     * @param bool            $skipKey
155
     *
156
     * @return void
157
     */
158
    protected function normalizeHeader(string $name, array|string $value, bool $skipKey): void
159
    {
160
        $name = \str_replace(["\r", "\n", "\t"], '', \trim($name));
161
        if (false === $skipKey) {
162
            $name = \ucwords(\str_replace('_', '-', \strtolower($name)), '-');
163
        }
164
        $this->headersMap[\strtolower($name)] = $name;
165
166
        $this->headers[$name] = $this->normalizeHeaderValue($name, $value);
167
    }
168
169
    /**
170
     * @param array $headers Associative headers array
171
     *
172
     * @return static
173
     */
174
    protected function setHeaders(array $headers): static
175
    {
176
        try {
177
            foreach ($headers as $name => $value) {
178
                $this->normalizeHeader($name, $value, false);
179
            }
180
            return $this;
181
        } catch (\TypeError $e) {
182
            throw new \InvalidArgumentException($e->getMessage(), HttpStatus::BAD_REQUEST, $e);
183
        }
184
    }
185
186
    /**
187
     * @param string $name
188
     *
189
     * @return string Normalized header name
190
     */
191
    protected function normalizeHeaderName(string $name): string
192
    {
193
        $name = \str_replace(["\r", "\n", "\t"], '', \trim($name));
194
        if ('' !== $name) {
195
            return $name;
196
        }
197
        throw new \InvalidArgumentException('Empty header name', HttpStatus::BAD_REQUEST);
198
    }
199
200
    /**
201
     * @param string          $name
202
     * @param string|string[] $value
203
     *
204
     * @return array
205
     */
206
    protected function normalizeHeaderValue(string $name, string|array $value): array
207
    {
208
        if (\is_string($value)) {
0 ignored issues
show
introduced by
The condition is_string($value) is always false.
Loading history...
209
            $value = (array)$value;
210
        }
211
        try {
212
            if (empty($value = \array_map(function($v): string {
213
                return \trim(\preg_replace('/\s+/', ' ', $v));
214
            }, $value))) {
215
                throw new \InvalidArgumentException(
216
                    \sprintf('The value for header "%s" cannot be empty', $name),
217
                    HttpStatus::BAD_REQUEST);
218
            }
219
            return $value;
220
        } catch (\TypeError $e) {
221
            throw new \InvalidArgumentException(
222
                \sprintf('Invalid value for header "%s", expects a string or array of strings', $name),
223
                HttpStatus::BAD_REQUEST, $e);
224
        }
225
    }
226
}
227