Completed
Push — master ( 3e15a8...ec1159 )
by Mihail
09:16
created

HeaderTrait::normalizeHeaderName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 15
rs 10
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 InvalidArgumentException;
16
use Throwable;
17
18
19
trait HeaderTrait
20
{
21
    /**
22
     * @var array Message headers.
23
     */
24
    protected $headers = [];
25
26
    /**
27
     * @var array Used for case-insensitivity header name checks
28
     */
29
    protected $headersMap = [];
30
31
    public function getHeaders(): array
32
    {
33
        return $this->headers;
34
    }
35
36
    public function getHeader($name): array
37
    {
38
        if (false === isset($this->headersMap[$name = strtolower($name)])) {
39
            return [];
40
        }
41
42
        if ($value = $this->headers[$this->headersMap[$name]]) {
43
            return (array)$value;
44
        }
45
46
        return [];
47
    }
48
49
    public function getHeaderLine($name): string
50
    {
51
        return join(',', $this->getHeader($name));
52
    }
53
54
    public function withHeader($name, $value): self
55
    {
56
        $instance = clone $this;
57
        $name     = $instance->normalizeHeaderName($name);
58
59
        $instance->headersMap[strtolower($name)] = $name;
60
61
        $instance->headers[$name] = $this->normalizeHeaderValue($name, $value);
62
63
        return $instance;
64
    }
65
66
    public function withHeaders(array $headers): self
67
    {
68
        $instance = clone $this;
69
70
        foreach ($headers as $name => $value) {
71
            $instance->normalizeHeader($name, $value, false);
72
        }
73
74
        return $instance;
75
    }
76
77
    public function withoutHeader($name): self
78
    {
79
        $instance = clone $this;
80
        $name     = strtolower($name);
81
        unset($instance->headers[$this->headersMap[$name]], $instance->headersMap[$name]);
82
83
        return $instance;
84
    }
85
86
    public function withAddedHeader($name, $value): self
87
    {
88
        $instance = clone $this;
89
        $name     = $instance->normalizeHeaderName($name);
90
        $value    = $instance->normalizeHeaderValue($name, $value);
91
92
        if (isset($instance->headersMap[$header = strtolower($name)])) {
93
            $header                     = $instance->headersMap[$header];
94
            $instance->headers[$header] = array_unique(array_merge((array)$instance->headers[$header], $value));
95
        } else {
96
            $instance->headersMap[$header] = $name;
97
            $instance->headers[$name]      = $value;
98
        }
99
100
        return $instance;
101
    }
102
103
    public function hasHeader($name): bool
104
    {
105
        return array_key_exists(strtolower($name), $this->headersMap);
106
    }
107
108
    public function replaceHeaders(array $headers)
109
    {
110
        $instance          = clone $this;
111
        $instance->headers = $instance->headersMap = [];
112
113
        foreach ($headers as $name => $value) {
114
            $instance->normalizeHeader($name, $value, false);
115
        }
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
133
        return $flattenHeaders;
134
    }
135
136
    public function getCanonicalizedHeaders(array $names = []): string
137
    {
138
        if (empty($names)) {
139
            $names = array_keys($this->headers);
140
        }
141
142
        if (!$headers = array_reduce($names, function($list, $name) {
143
            $name   = str_replace('_', '-', $name);
144
            $list[] = strtolower($name) . ':' . join(',', $this->getHeader($name));
145
146
            return $list;
147
        })) {
148
            return '';
149
        }
150
151
        sort($headers);
152
153
        return join("\n", $headers);
154
    }
155
156
    /**
157
     * @param string $name
158
     * @param mixed  $value
159
     * @param bool   $skipKey
160
     *
161
     * @return void
162
     */
163
    protected function normalizeHeader(string $name, $value, bool $skipKey): void
164
    {
165
        $name = str_replace(["\r", "\n", "\t"], '', trim($name));
166
167
        if (false === $skipKey) {
168
            $name = ucwords(str_replace('_', '-', strtolower($name)), '-');
169
        }
170
171
        $this->headersMap[strtolower($name)] = $name;
172
173
        $this->headers[$name] = $this->normalizeHeaderValue($name, $value);
174
    }
175
176
    /**
177
     * @param array $headers Associative headers array
178
     *
179
     * @return static
180
     */
181
    protected function setHeaders(array $headers)
182
    {
183
        foreach (array_filter($headers, 'is_string', ARRAY_FILTER_USE_KEY) as $name => $value) {
184
            $this->normalizeHeader($name, $value, false);
185
        }
186
187
        return $this;
188
    }
189
190
    /**
191
     * @param string $name
192
     *
193
     * @return string Normalized header name
194
     */
195
    protected function normalizeHeaderName($name): string
196
    {
197
        try {
198
            $name = str_replace(["\r", "\n", "\t"], '', trim($name));
199
        } catch (Throwable $e) {
200
            throw new InvalidArgumentException(
201
                sprintf('Header name must be a string, %s given', gettype($name)), StatusCode::BAD_REQUEST
202
            );
203
        }
204
205
        if ('' === $name) {
206
            throw new InvalidArgumentException('Empty header name', StatusCode::BAD_REQUEST);
207
        }
208
209
        return $name;
210
    }
211
212
    /**
213
     * @param string          $name
214
     * @param string|string[] $value
215
     *
216
     * @return array
217
     */
218
    protected function normalizeHeaderValue(string $name, $value): array
219
    {
220
        $type = gettype($value);
221
        switch ($type) {
222
            case 'array':
223
            case 'integer':
224
            case 'double':
225
            case 'string':
226
                $value = (array)$value;
227
                break;
228
            default:
229
                throw new InvalidArgumentException(
230
                    sprintf('Invalid header value, expects string or array, "%s" given', $type), StatusCode::BAD_REQUEST
231
                );
232
        }
233
234
        if (empty($value = array_map(function($v) {
235
            return trim(preg_replace('/\s+/', ' ', $v));
236
        }, $value))) {
237
            throw new InvalidArgumentException(
238
                sprintf('The value for header "%s" cannot be empty', $name), StatusCode::BAD_REQUEST
239
            );
240
        }
241
242
        return $value;
243
    }
244
}
245