Passed
Pull Request — master (#194)
by Dmitriy
12:10
created

Route::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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