Passed
Pull Request — master (#225)
by Rustam
02:46
created

Route::__toString()   A

Complexity

Conditions 5
Paths 12

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 11
nc 12
nop 0
dl 0
loc 21
ccs 12
cts 12
cp 1
crap 5
rs 9.6111
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Router;
6
7
use InvalidArgumentException;
8
use Stringable;
9
use Yiisoft\Router\Internal\MiddlewareFilter;
10
11
/**
12
 * Route defines a mapping from URL to callback / name and vice versa.
13
 */
14
final class Route implements Stringable
15
{
16
    /**
17
     * @var string[]
18
     * @psalm-var array<array-key, string>
19
     */
20
    private array $methods = [];
21
22
    /**
23
     * @var string[]
24
     */
25
    private array $hosts = [];
26
27
    /**
28
     * @var array|callable|string|null
29
     */
30
    private $action = null;
31
32
    /**
33
     * @var array[]|callable[]|string[]
34
     * @psalm-var list<array|callable|string>
35
     */
36
    private array $middlewares = [];
37
38
    /**
39
     * @psalm-var list<array|callable|string>|null
40
     */
41
    private ?array $enabledMiddlewaresCache = null;
42
43
    /**
44
     * @var array<array-key,string>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key,string> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key,string>.
Loading history...
45
     */
46
    private array $defaults = [];
47
48
    /**
49
     * @param array|callable|string|null $action Action handler. It is a primary middleware definition that
50
     * should be invoked last for a matched route.
51
     * @param array<array-key,scalar|Stringable|null> $defaults Parameter default values indexed by parameter names.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key,scalar|Stringable|null> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key,scalar|Stringable|null>.
Loading history...
52
     * @param bool $override Marks route as override. When added it will replace existing route with the same name.
53
     * @param array $disabledMiddlewares Excludes middleware from being invoked when action is handled.
54
     * It is useful to avoid invoking one of the parent group middleware for
55
     * a certain route.
56
     */
57 93
    public function __construct(
58
        array $methods,
59
        private string $pattern,
60
        private ?string $name = null,
61
        array|callable|string $action = null,
62
        array $middlewares = [],
63
        array $defaults = [],
64
        array $hosts = [],
65
        private bool $override = false,
66
        private array $disabledMiddlewares = [],
67
    ) {
68 93
        if (empty($methods)) {
69 1
            throw new InvalidArgumentException('$methods cannot be empty.');
70
        }
71 92
        $this->setMethods($methods);
72 92
        $this->action = $action;
73 92
        $this->setMiddlewares($middlewares);
74 91
        $this->setHosts($hosts);
75 90
        $this->setDefaults($defaults);
76
    }
77
78
    /**
79
     * @return string[]
80
     */
81 32
    public function getMethods(): array
82
    {
83 32
        return $this->methods;
84
    }
85
86 22
    public function getAction(): array|callable|string|null
87
    {
88 22
        return $this->action;
89
    }
90
91 27
    public function getMiddlewares(): array
92
    {
93 27
        return $this->middlewares;
94
    }
95
96
    /**
97
     * @return string[]
98
     */
99 14
    public function getHosts(): array
100
    {
101 14
        return $this->hosts;
102
    }
103
104 2
    public function getDefaults(): array
105
    {
106 2
        return $this->defaults;
107
    }
108
109 25
    public function getPattern(): string
110
    {
111 25
        return $this->pattern;
112
    }
113
114 35
    public function getName(): string
115
    {
116 35
        return $this->name ??= (implode(', ', $this->methods) . ' ' . implode('|', $this->hosts) . $this->pattern);
117
    }
118
119 6
    public function isOverride(): bool
120
    {
121 6
        return $this->override;
122
    }
123
124 1
    public function getDisabledMiddlewares(): array
125
    {
126 1
        return $this->disabledMiddlewares;
127
    }
128
129
    /**
130
     * @return array[]|callable[]|string[]
131
     * @psalm-return list<array|callable|string>
132
     */
133 27
    public function getEnabledMiddlewares(): array
134
    {
135 27
        if ($this->enabledMiddlewaresCache !== null) {
136
            return $this->enabledMiddlewaresCache;
137
        }
138
139 27
        $this->enabledMiddlewaresCache = MiddlewareFilter::filter($this->middlewares, $this->disabledMiddlewares);
140 27
        if ($this->action !== null) {
141 18
            $this->enabledMiddlewaresCache[] = $this->action;
142
        }
143
144 27
        return $this->enabledMiddlewaresCache;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->enabledMiddlewaresCache returns the type null which is incompatible with the type-hinted return array.
Loading history...
145
    }
146
147 92
    public function setMethods(array $methods): self
148
    {
149 92
        $this->assertListOfStrings($methods, 'methods');
150 92
        $this->methods = $methods;
151 92
        return $this;
152
    }
153
154 91
    public function setHosts(array $hosts): self
155
    {
156 91
        $this->assertListOfStrings($hosts, 'hosts');
157 90
        $this->hosts = [];
158 90
        foreach ($hosts as $host) {
159 16
            $host = rtrim($host, '/');
160
161 16
            if ($host !== '' && !in_array($host, $this->hosts, true)) {
162 16
                $this->hosts[] = $host;
163
            }
164
        }
165
166 90
        return $this;
167
    }
168
169 7
    public function setAction(callable|array|string|null $action): self
170
    {
171 7
        $this->action = $action;
172 7
        return $this;
173
    }
174
175 92
    public function setMiddlewares(array $middlewares): self
176
    {
177 92
        $this->assertMiddlewares($middlewares);
178 91
        $this->middlewares = $middlewares;
179 91
        $this->enabledMiddlewaresCache = null;
180 91
        return $this;
181
    }
182
183 90
    public function setDefaults(array $defaults): self
184
    {
185
        /** @var mixed $value */
186 90
        foreach ($defaults as $key => $value) {
187 3
            if (!is_scalar($value) && !($value instanceof Stringable)) {
188
                throw new \InvalidArgumentException(
189
                    'Invalid $defaults provided, list of scalar or `Stringable` instance expected.'
190
                );
191
            }
192 3
            $this->defaults[$key] = (string) $value;
193
        }
194 90
        return $this;
195
    }
196
197 23
    public function setPattern(string $pattern): self
198
    {
199 23
        $this->pattern = $pattern;
200 23
        return $this;
201
    }
202
203 17
    public function setName(?string $name): self
204
    {
205 17
        $this->name = $name;
206 17
        return $this;
207
    }
208
209 1
    public function setOverride(bool $override): self
210
    {
211 1
        $this->override = $override;
212 1
        return $this;
213
    }
214
215 1
    public function setDisabledMiddlewares(array $disabledMiddlewares): self
216
    {
217 1
        $this->disabledMiddlewares = $disabledMiddlewares;
218 1
        $this->enabledMiddlewaresCache = null;
219 1
        return $this;
220
    }
221
222 8
    public function __toString(): string
223
    {
224 8
        $result = $this->name === null
225 2
            ? ''
226 6
            : '[' . $this->name . '] ';
227
228 8
        if ($this->methods !== []) {
229 8
            $result .= implode(',', $this->methods) . ' ';
230
        }
231
232 8
        if (!empty($this->hosts)) {
233 4
            $quoted = array_map(static fn ($host) => preg_quote($host, '/'), $this->hosts);
234
235 4
            if (!preg_match('/' . implode('|', $quoted) . '/', $this->pattern)) {
236 4
                $result .= implode('|', $this->hosts);
237
            }
238
        }
239
240 8
        $result .= $this->pattern;
241
242 8
        return $result;
243
    }
244
245 1
    public function __debugInfo()
246
    {
247 1
        return [
248 1
            'name' => $this->name,
249 1
            'methods' => $this->methods,
250 1
            'pattern' => $this->pattern,
251 1
            'action' => $this->action,
252 1
            'hosts' => $this->hosts,
253 1
            'defaults' => $this->defaults,
254 1
            'override' => $this->override,
255 1
            'middlewares' => $this->middlewares,
256 1
            'disabledMiddlewares' => $this->disabledMiddlewares,
257 1
            'enabledMiddlewares' => $this->getEnabledMiddlewares(),
258 1
        ];
259
    }
260
261
    /**
262
     * @psalm-assert array<array-key,string> $items
263
     */
264 92
    private function assertListOfStrings(array $items, string $argument): void
265
    {
266 92
        foreach ($items as $item) {
267 92
            if (!is_string($item)) {
268 1
                throw new \InvalidArgumentException('Invalid $' . $argument . ' provided, list of string expected.');
269
            }
270
        }
271
    }
272
273
    /**
274
     * @psalm-assert list<array|callable|string> $middlewares
275
     */
276 92
    private function assertMiddlewares(array $middlewares): void
277
    {
278
        /** @var mixed $middleware */
279 92
        foreach ($middlewares as $middleware) {
280 34
            if (is_string($middleware)) {
281 13
                continue;
282
            }
283
284 21
            if (is_callable($middleware) || is_array($middleware)) {
285 21
                continue;
286
            }
287
288 1
            throw new \InvalidArgumentException(
289 1
                'Invalid $middlewares provided, list of string or array or callable expected.'
290 1
            );
291
        }
292
    }
293
}
294