Passed
Pull Request — master (#194)
by Dmitriy
02:30
created

Route::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 2
b 0
f 0
nc 1
nop 8
dl 0
loc 13
ccs 4
cts 4
cp 1
crap 1
rs 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Router;
6
7
use Attribute;
8
use InvalidArgumentException;
9
use RuntimeException;
10
use Stringable;
11
use Yiisoft\Http\Method;
12
13
use function in_array;
14
15
/**
16
 * Route defines a mapping from URL to callback / name and vice versa.
17
 */
18
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
19
final class Route implements Stringable
20
{
21
    private bool $actionAdded = false;
22
    private array $builtMiddlewares = [];
23
24
    /**
25
     * @param string[] $methods HTTP method names
26
     * @param string[] $hosts
27
     * @param array[]|callable[]|string[] $middlewares
28
     * @param array $disabledMiddlewares Excludes middleware from being invoked when action is handled.
29
     * It is useful to avoid invoking one of the parent group middleware for
30
     * a certain route.
31
     * @param bool $override Marks route as override. When added it will replace existing route with the same name.
32
     * @param array<string,string> $defaults  Parameter default values indexed by parameter names.
33
     * @psalm-param array<string,null|Stringable|scalar> $defaults
34
     */
35 73
    public function __construct(
36
        private array $methods,
37
        private string $pattern,
38
        private ?string $name = null,
39
        private array $middlewares = [],
40
        private array $disabledMiddlewares = [],
41
        private array $hosts = [],
42
        private bool $override = false,
43
        private array $defaults = [],
44
    ) {
45 73
        $this->methods = $this->processMethods($this->methods);
46 73
        $this->hosts = $this->processHosts($this->hosts);
47 73
        $this->defaults = $this->processDefaults($this->defaults);
48
    }
49
50 60
    public static function get(string $pattern): self
51
    {
52 60
        return self::methods([Method::GET], $pattern);
53
    }
54
55 9
    public static function post(string $pattern): self
56
    {
57 9
        return self::methods([Method::POST], $pattern);
58
    }
59
60 4
    public static function put(string $pattern): self
61
    {
62 4
        return self::methods([Method::PUT], $pattern);
63
    }
64
65 1
    public static function delete(string $pattern): self
66
    {
67 1
        return self::methods([Method::DELETE], $pattern);
68
    }
69
70 1
    public static function patch(string $pattern): self
71
    {
72 1
        return self::methods([Method::PATCH], $pattern);
73
    }
74
75 1
    public static function head(string $pattern): self
76
    {
77 1
        return self::methods([Method::HEAD], $pattern);
78
    }
79
80 7
    public static function options(string $pattern): self
81
    {
82 7
        return self::methods([Method::OPTIONS], $pattern);
83
    }
84
85
    /**
86
     * @param string[] $methods
87
     */
88 72
    public static function methods(
89
        array $methods,
90
        string $pattern,
91
    ): self {
92 72
        return new self($methods, $pattern);
93
    }
94
95 23
    public function name(string $name): self
96
    {
97 23
        $route = clone $this;
98 23
        $route->name = $name;
99 23
        return $route;
100
    }
101
102 21
    public function pattern(string $pattern): self
103
    {
104 21
        $new = clone $this;
105 21
        $new->pattern = $pattern;
106 21
        return $new;
107
    }
108
109 8
    public function host(string $host): self
110
    {
111 8
        return $this->hosts($host);
112
    }
113
114 12
    public function hosts(string ...$hosts): self
115
    {
116 12
        $route = clone $this;
117
118 12
        $route->hosts = $this->processHosts($hosts);
119
120 12
        return $route;
121
    }
122
123
    /**
124
     * Marks route as override. When added it will replace existing route with the same name.
125
     */
126 4
    public function override(): self
127
    {
128 4
        $route = clone $this;
129 4
        $route->override = true;
130 4
        return $route;
131
    }
132
133
    /**
134
     * Parameter default values indexed by parameter names.
135
     *
136
     * @psalm-param array<string,null|Stringable|scalar> $defaults
137
     */
138 3
    public function defaults(array $defaults): self
139
    {
140 3
        $route = clone $this;
141 3
        $route->defaults = $this->processDefaults($defaults);
142 3
        return $route;
143
    }
144
145
    /**
146
     * Appends a handler middleware definition that should be invoked for a matched route.
147
     * First added handler will be executed first.
148
     */
149 18
    public function middleware(array|callable|string ...$middlewareDefinition): self
150
    {
151 18
        if ($this->actionAdded) {
152 1
            throw new RuntimeException('middleware() can not be used after action().');
153
        }
154 17
        $route = clone $this;
155 17
        array_push(
156 17
            $route->middlewares,
157 17
            ...array_values($middlewareDefinition)
158 17
        );
159 17
        $route->builtMiddlewares = [];
160 17
        return $route;
161
    }
162
163
    /**
164
     * Prepends a handler middleware definition that should be invoked for a matched route.
165
     * Last added handler will be executed first.
166
     */
167 18
    public function prependMiddleware(array|callable|string ...$middlewareDefinition): self
168
    {
169 18
        if (!$this->actionAdded) {
170 1
            throw new RuntimeException('prependMiddleware() can not be used before action().');
171
        }
172 17
        $route = clone $this;
173 17
        array_unshift(
174 17
            $route->middlewares,
175 17
            ...array_values($middlewareDefinition)
176 17
        );
177 17
        $route->builtMiddlewares = [];
178 17
        return $route;
179
    }
180
181
    /**
182
     * Appends action handler. It is a primary middleware definition that should be invoked last for a matched route.
183
     */
184 20
    public function action(array|callable|string $middlewareDefinition): self
185
    {
186 20
        $route = clone $this;
187 20
        $route->middlewares[] = $middlewareDefinition;
188 20
        $route->builtMiddlewares = [];
189 20
        $route->actionAdded = true;
190 20
        return $route;
191
    }
192
193
    /**
194
     * Excludes middleware from being invoked when action is handled.
195
     * It is useful to avoid invoking one of the parent group middleware for
196
     * a certain route.
197
     */
198 3
    public function disableMiddleware(mixed ...$middlewareDefinition): self
199
    {
200 3
        $route = clone $this;
201 3
        array_push(
202 3
            $route->disabledMiddlewares,
203 3
            ...array_values($middlewareDefinition)
204 3
        );
205 3
        $route->builtMiddlewares = [];
206 3
        return $route;
207
    }
208
209
    /**
210
     * @psalm-template T as string
211
     *
212
     * @psalm-param T $key
213
     *
214
     * @psalm-return (
215
     *   T is ('name'|'pattern') ? string :
216
     *       (T is 'host' ? string|null :
217
     *           (T is 'hosts' ? array<array-key, string> :
218
     *               (T is 'methods' ? array<array-key,string> :
219
     *                   (T is 'defaults' ? array<string,string> :
220
     *                       (T is ('override'|'hasMiddlewares') ? bool : mixed)
221
     *                   )
222
     *               )
223
     *           )
224
     *       )
225
     *    )
226
     */
227 46
    public function getData(string $key): mixed
228
    {
229 46
        return match ($key) {
230 46
            'name' => $this->name ??
231 27
                (implode(', ', $this->methods) . ' ' . implode('|', $this->hosts) . $this->pattern),
232 46
            'pattern' => $this->pattern,
233 46
            'host' => $this->hosts[0] ?? null,
234 46
            'hosts' => $this->hosts,
235 46
            'methods' => $this->methods,
236 46
            'defaults' => $this->defaults,
237 46
            'override' => $this->override,
238 46
            'hasMiddlewares' => $this->middlewares !== [],
239 46
            default => throw new InvalidArgumentException('Unknown data key: ' . $key),
240 46
        };
241
    }
242
243 4
    public function __toString(): string
244
    {
245 4
        $result = $this->name === null
246 1
            ? ''
247 3
            : '[' . $this->name . '] ';
248
249 4
        if ($this->methods !== []) {
250 4
            $result .= implode(',', $this->methods) . ' ';
251
        }
252
253 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...
254 2
            $quoted = array_map(static fn ($host) => preg_quote($host, '/'), $this->hosts);
255
256 2
            if (!preg_match('/' . implode('|', $quoted) . '/', $this->pattern)) {
257 2
                $result .= implode('|', $this->hosts);
258
            }
259
        }
260
261 4
        $result .= $this->pattern;
262
263 4
        return $result;
264
    }
265
266 1
    public function __debugInfo()
267
    {
268 1
        return [
269 1
            'name' => $this->name,
270 1
            'methods' => $this->methods,
271 1
            'pattern' => $this->pattern,
272 1
            'hosts' => $this->hosts,
273 1
            'defaults' => $this->defaults,
274 1
            'override' => $this->override,
275 1
            'actionAdded' => $this->actionAdded,
276 1
            'middlewareDefinitions' => $this->middlewares,
277 1
            'builtMiddlewares' => $this->builtMiddlewares,
278 1
            'disabledMiddlewareDefinitions' => $this->disabledMiddlewares,
279 1
        ];
280
    }
281
282 16
    public function getMiddlewares(): array
283
    {
284 16
        if ($this->builtMiddlewares !== []) {
285
            return $this->builtMiddlewares;
286
        }
287 16
        $result = $this->middlewares;
288
        /** @var mixed $definition */
289 16
        foreach ($result as $index => $definition) {
290 15
            if (in_array($definition, $this->disabledMiddlewares, true)) {
291 1
                unset($result[$index]);
292
            }
293
        }
294 16
        $this->builtMiddlewares = $result;
295
296 16
        return $result;
297
    }
298
299 73
    private function processDefaults(array $defaults): array
300
    {
301 73
        return array_map('\strval', $defaults);
302
    }
303
304 73
    private function processHosts(array $hosts): array
305
    {
306 73
        $result = [];
307 73
        foreach ($hosts as $host) {
308 13
            $host = rtrim($host, '/');
309
310 13
            if ($host !== '' && !in_array($host, $result, true)) {
311 12
                $result[] = $host;
312
            }
313
        }
314 73
        return $result;
315
    }
316
317 73
    private function processMethods(array $methods): array
318
    {
319 73
        $result = [];
320 73
        foreach ($methods as $method) {
321 73
            $method = strtoupper($method);
322 73
            if (!in_array($method, Method::ALL, true)) {
323
                throw new \InvalidArgumentException(
324
                    sprintf(
325
                        'Method "%s" is not supported. Possible methods: "%s".',
326
                        $method,
327
                        implode('", "', Method::ALL),
328
                    )
329
                );
330
            }
331 73
            $result[] = $method;
332
        }
333 73
        return $result;
334
    }
335
}
336