Passed
Pull Request — master (#210)
by Sergei
02:34
created

Route::getData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 13
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 15
ccs 14
cts 14
cp 1
crap 1
rs 9.8333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Router;
6
7
use InvalidArgumentException;
8
use RuntimeException;
9
use Stringable;
10
use Yiisoft\Http\Method;
11
12
use function in_array;
13
14
/**
15
 * Route defines a mapping from URL to callback / name and vice versa.
16
 */
17
final class Route implements Stringable
18
{
19
    private ?string $name = null;
20
21
    /**
22
     * @var string[]
23
     */
24
    private array $hosts = [];
25
    private bool $override = false;
26
    private bool $actionAdded = false;
27
28
    /**
29
     * @var array[]|callable[]|string[]
30
     */
31
    private array $middlewareDefinitions = [];
32
33
    private array $disabledMiddlewareDefinitions = [];
34
35
    /**
36
     * @var array<string,string>
37
     */
38
    private array $defaults = [];
39
40
    /**
41
     * @param string[] $methods
42
     */
43 76
    private function __construct(
44
        private array $methods,
45
        private string $pattern,
46
    ) {
47 76
    }
48
49 64
    public static function get(string $pattern): self
50
    {
51 64
        return self::methods([Method::GET], $pattern);
52
    }
53
54 9
    public static function post(string $pattern): self
55
    {
56 9
        return self::methods([Method::POST], $pattern);
57
    }
58
59 4
    public static function put(string $pattern): self
60
    {
61 4
        return self::methods([Method::PUT], $pattern);
62
    }
63
64 1
    public static function delete(string $pattern): self
65
    {
66 1
        return self::methods([Method::DELETE], $pattern);
67
    }
68
69 1
    public static function patch(string $pattern): self
70
    {
71 1
        return self::methods([Method::PATCH], $pattern);
72
    }
73
74 1
    public static function head(string $pattern): self
75
    {
76 1
        return self::methods([Method::HEAD], $pattern);
77
    }
78
79 9
    public static function options(string $pattern): self
80
    {
81 9
        return self::methods([Method::OPTIONS], $pattern);
82
    }
83
84
    /**
85
     * @param string[] $methods
86
     */
87 76
    public static function methods(array $methods, string $pattern): self
88
    {
89 76
        return new self($methods, $pattern);
90
    }
91
92 23
    public function name(string $name): self
93
    {
94 23
        $route = clone $this;
95 23
        $route->name = $name;
96 23
        return $route;
97
    }
98
99 23
    public function pattern(string $pattern): self
100
    {
101 23
        $new = clone $this;
102 23
        $new->pattern = $pattern;
103 23
        return $new;
104
    }
105
106 9
    public function host(string $host): self
107
    {
108 9
        return $this->hosts($host);
109
    }
110
111 13
    public function hosts(string ...$hosts): self
112
    {
113 13
        $route = clone $this;
114 13
        $route->hosts = [];
115
116 13
        foreach ($hosts as $host) {
117 13
            $host = rtrim($host, '/');
118
119 13
            if ($host !== '' && !in_array($host, $route->hosts, true)) {
120 12
                $route->hosts[] = $host;
121
            }
122
        }
123
124 13
        return $route;
125
    }
126
127
    /**
128
     * Marks route as override. When added it will replace existing route with the same name.
129
     */
130 4
    public function override(): self
131
    {
132 4
        $route = clone $this;
133 4
        $route->override = true;
134 4
        return $route;
135
    }
136
137
    /**
138
     * Parameter default values indexed by parameter names.
139
     *
140
     * @psalm-param array<string,null|Stringable|scalar> $defaults
141
     */
142 3
    public function defaults(array $defaults): self
143
    {
144 3
        $route = clone $this;
145 3
        $route->defaults = array_map('\strval', $defaults);
146 3
        return $route;
147
    }
148
149
    /**
150
     * Appends a handler middleware definition that should be invoked for a matched route.
151
     * First added handler will be executed first.
152
     */
153 22
    public function middleware(array|callable|string ...$middlewareDefinition): self
154
    {
155 22
        if ($this->actionAdded) {
156 1
            throw new RuntimeException('middleware() can not be used after action().');
157
        }
158 21
        $route = clone $this;
159 21
        array_push(
160 21
            $route->middlewareDefinitions,
161 21
            ...array_values($middlewareDefinition)
162 21
        );
163 21
        return $route;
164
    }
165
166
    /**
167
     * Prepends a handler middleware definition that should be invoked for a matched route.
168
     * Last added handler will be executed first.
169
     */
170 23
    public function prependMiddleware(array|callable|string ...$middlewareDefinition): self
171
    {
172 23
        if (!$this->actionAdded) {
173 1
            throw new RuntimeException('prependMiddleware() can not be used before action().');
174
        }
175 22
        $route = clone $this;
176 22
        array_unshift(
177 22
            $route->middlewareDefinitions,
178 22
            ...array_values($middlewareDefinition)
179 22
        );
180 22
        return $route;
181
    }
182
183
    /**
184
     * Appends action handler. It is a primary middleware definition that should be invoked last for a matched route.
185
     */
186 25
    public function action(array|callable|string $middlewareDefinition): self
187
    {
188 25
        $route = clone $this;
189 25
        $route->middlewareDefinitions[] = $middlewareDefinition;
190 25
        $route->actionAdded = true;
191 25
        return $route;
192
    }
193
194
    /**
195
     * Excludes middleware from being invoked when action is handled.
196
     * It is useful to avoid invoking one of the parent group middleware for
197
     * a certain route.
198
     */
199 3
    public function disableMiddleware(mixed ...$middlewareDefinition): self
200
    {
201 3
        $route = clone $this;
202 3
        array_push(
203 3
            $route->disabledMiddlewareDefinitions,
204 3
            ...array_values($middlewareDefinition)
205 3
        );
206 3
        return $route;
207
    }
208
209
    /**
210
     * @psalm-template T as string
211
     * @psalm-param T $key
212
     * @psalm-return (
213
     *   T is ('name'|'pattern') ? string :
214
     *       (T is 'host' ? string|null :
215
     *           (T is 'hosts' ? array<array-key, string> :
216
     *               (T is 'methods' ? array<array-key,string> :
217
     *                   (T is 'defaults' ? array<string,string> :
218
     *                       (T is ('override'|'hasMiddlewares') ? bool :
219
     *                           (T is 'middlewareDefinitions' ? array<array-key,array|callable|string> : mixed)
220
     *                       )
221
     *                   )
222
     *               )
223
     *           )
224
     *       )
225
     *    )
226
     */
227 56
    public function getData(string $key): mixed
228
    {
229 56
        $middlewareDefinitions = $this->getMiddlewareDefinitions();
230 56
        return match ($key) {
231 56
            'name' => $this->name ??
232 31
                (implode(', ', $this->methods) . ' ' . implode('|', $this->hosts) . $this->pattern),
233 56
            'pattern' => $this->pattern,
234 56
            'host' => $this->hosts[0] ?? null,
235 56
            'hosts' => $this->hosts,
236 56
            'methods' => $this->methods,
237 56
            'defaults' => $this->defaults,
238 56
            'override' => $this->override,
239 56
            'middlewareDefinitions' => $middlewareDefinitions,
240 56
            'hasMiddlewares' => !empty($middlewareDefinitions),
241 56
            default => throw new InvalidArgumentException('Unknown data key: ' . $key),
242 56
        };
243
    }
244
245 4
    public function __toString(): string
246
    {
247 4
        $result = $this->name === null
248 1
            ? ''
249 3
            : '[' . $this->name . '] ';
250
251 4
        if ($this->methods !== []) {
252 4
            $result .= implode(',', $this->methods) . ' ';
253
        }
254
255 4
        if ($this->hosts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->hosts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
256 2
            $quoted = array_map(static fn ($host) => preg_quote($host, '/'), $this->hosts);
257
258 2
            if (!preg_match('/' . implode('|', $quoted) . '/', $this->pattern)) {
259 2
                $result .= implode('|', $this->hosts);
260
            }
261
        }
262
263 4
        $result .= $this->pattern;
264
265 4
        return $result;
266
    }
267
268 1
    public function __debugInfo()
269
    {
270 1
        return [
271 1
            'name' => $this->name,
272 1
            'methods' => $this->methods,
273 1
            'pattern' => $this->pattern,
274 1
            'hosts' => $this->hosts,
275 1
            'defaults' => $this->defaults,
276 1
            'override' => $this->override,
277 1
            'actionAdded' => $this->actionAdded,
278 1
            'middlewareDefinitions' => $this->middlewareDefinitions,
279 1
            'disabledMiddlewareDefinitions' => $this->disabledMiddlewareDefinitions,
280 1
        ];
281
    }
282
283 56
    private function getMiddlewareDefinitions(): array
284
    {
285 56
        foreach ($this->middlewareDefinitions as $index => $definition) {
286 26
            if (in_array($definition, $this->disabledMiddlewareDefinitions, true)) {
287 1
                unset($this->middlewareDefinitions[$index]);
288
            }
289
        }
290
291 56
        return $this->middlewareDefinitions;
292
    }
293
}
294