Passed
Pull Request — master (#138)
by Rustam
02:18
created

Route   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 342
Duplicated Lines 0 %

Test Coverage

Coverage 82.91%

Importance

Changes 17
Bugs 5 Features 0
Metric Value
eloc 102
dl 0
loc 342
ccs 97
cts 117
cp 0.8291
rs 9.1199
c 17
b 5
f 0
wmc 41

26 Methods

Rating   Name   Duplication   Size   Complexity  
A injectDispatcher() 0 3 1
A __construct() 0 3 1
A host() 0 5 1
A defaults() 0 5 1
A override() 0 5 1
A withDispatcher() 0 5 1
A put() 0 3 1
A patch() 0 3 1
B getData() 0 17 7
A name() 0 5 1
A getDispatcherWithMiddlewares() 0 13 4
A head() 0 3 1
A methods() 0 10 1
A action() 0 6 1
A pattern() 0 5 1
A delete() 0 3 1
A get() 0 3 1
A disableMiddleware() 0 5 1
A post() 0 3 1
A __toString() 0 17 5
A options() 0 3 1
A hasDispatcher() 0 3 1
A middleware() 0 8 2
A prependMiddleware() 0 8 2
A hasMiddlewares() 0 3 1
A __debugInfo() 0 13 1

How to fix   Complexity   

Complex Class

Complex classes like Route often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Route, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Router;
6
7
use InvalidArgumentException;
8
use RuntimeException;
9
use Yiisoft\Http\Method;
10
use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher;
11
12
/**
13
 * Route defines a mapping from URL to callback / name and vice versa.
14
 */
15
final class Route
16
{
17
    private ?string $name = null;
18
    /** @var string[] */
19
    private array $methods;
20
    private string $pattern;
21
    private ?string $host = null;
22
    private bool $override = false;
23
    private ?MiddlewareDispatcher $dispatcher;
24
    private bool $actionAdded = false;
25
    private array $middlewareDefinitions = [];
26
    private array $disabledMiddlewareDefinitions = [];
27
    private array $defaults = [];
28
29 50
    private function __construct(?MiddlewareDispatcher $dispatcher = null)
30
    {
31 50
        $this->dispatcher = $dispatcher;
32 50
    }
33
34 7
    public function injectDispatcher(MiddlewareDispatcher $dispatcher): void
35
    {
36 7
        $this->dispatcher = $dispatcher;
37 7
    }
38
39
    /**
40
     * @return self
41
     */
42 5
    public function withDispatcher(MiddlewareDispatcher $dispatcher): self
43
    {
44 5
        $route = clone $this;
45 5
        $route->dispatcher = $dispatcher;
46 5
        return $route;
47
    }
48
49
    /**
50
     * @return MiddlewareDispatcher
51
     *
52
     * @internal
53
     */
54 12
    public function getDispatcherWithMiddlewares(): MiddlewareDispatcher
55
    {
56 12
        if ($this->dispatcher->hasMiddlewares()) {
0 ignored issues
show
Bug introduced by
The method hasMiddlewares() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

56
        if ($this->dispatcher->/** @scrutinizer ignore-call */ hasMiddlewares()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
57
            return $this->dispatcher;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->dispatcher could return the type null which is incompatible with the type-hinted return Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher. Consider adding an additional type-check to rule them out.
Loading history...
58
        }
59
60 12
        foreach ($this->middlewareDefinitions as $index => $definition) {
61 12
            if (in_array($definition, $this->disabledMiddlewareDefinitions, true)) {
62
                unset($this->middlewareDefinitions[$index]);
63
            }
64
        }
65
66 12
        return $this->dispatcher = $this->dispatcher->withMiddlewares($this->middlewareDefinitions);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->dispatcher...>middlewareDefinitions) could return the type null which is incompatible with the type-hinted return Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher. Consider adding an additional type-check to rule them out.
Loading history...
67
    }
68
69 26
    public function hasDispatcher(): bool
70
    {
71 26
        return $this->dispatcher !== null;
72
    }
73
74
    /**
75
     * @param string $pattern
76
     * @param MiddlewareDispatcher|null $dispatcher
77
     *
78
     * @return self
79
     */
80 39
    public static function get(string $pattern, ?MiddlewareDispatcher $dispatcher = null): self
81
    {
82 39
        return self::methods([Method::GET], $pattern, $dispatcher);
83
    }
84
85
    /**
86
     * @param string $pattern
87
     * @param MiddlewareDispatcher|null $dispatcher
88
     *
89
     * @return self
90
     */
91 9
    public static function post(string $pattern, ?MiddlewareDispatcher $dispatcher = null): self
92
    {
93 9
        return self::methods([Method::POST], $pattern, $dispatcher);
94
    }
95
96
    /**
97
     * @param string $pattern
98
     * @param MiddlewareDispatcher|null $dispatcher
99
     *
100
     * @return self
101
     */
102 4
    public static function put(string $pattern, ?MiddlewareDispatcher $dispatcher = null): self
103
    {
104 4
        return self::methods([Method::PUT], $pattern, $dispatcher);
105
    }
106
107
    /**
108
     * @param string $pattern
109
     * @param MiddlewareDispatcher|null $dispatcher
110
     *
111
     * @return self
112
     */
113 1
    public static function delete(string $pattern, ?MiddlewareDispatcher $dispatcher = null): self
114
    {
115 1
        return self::methods([Method::DELETE], $pattern, $dispatcher);
116
    }
117
118
    /**
119
     * @param string $pattern
120
     * @param MiddlewareDispatcher|null $dispatcher
121
     *
122
     * @return self
123
     */
124 1
    public static function patch(string $pattern, ?MiddlewareDispatcher $dispatcher = null): self
125
    {
126 1
        return self::methods([Method::PATCH], $pattern, $dispatcher);
127
    }
128
129
    /**
130
     * @param string $pattern
131
     * @param MiddlewareDispatcher|null $dispatcher
132
     *
133
     * @return self
134
     */
135 1
    public static function head(string $pattern, ?MiddlewareDispatcher $dispatcher = null): self
136
    {
137 1
        return self::methods([Method::HEAD], $pattern, $dispatcher);
138
    }
139
140
    /**
141
     * @param string $pattern
142
     * @param MiddlewareDispatcher|null $dispatcher
143
     *
144
     * @return self
145
     */
146 7
    public static function options(string $pattern, ?MiddlewareDispatcher $dispatcher = null): self
147
    {
148 7
        return self::methods([Method::OPTIONS], $pattern, $dispatcher);
149
    }
150
151
    /**
152
     * @param array $methods
153
     * @param string $pattern
154
     * @param MiddlewareDispatcher|null $dispatcher
155
     *
156
     * @return self
157
     */
158 50
    public static function methods(
159
        array $methods,
160
        string $pattern,
161
        ?MiddlewareDispatcher $dispatcher = null
162
    ): self {
163 50
        $route = new self($dispatcher);
164 50
        $route->methods = $methods;
165 50
        $route->pattern = $pattern;
166
167 50
        return $route;
168
    }
169
170
    /**
171
     * @return self
172
     */
173 19
    public function name(string $name): self
174
    {
175 19
        $route = clone $this;
176 19
        $route->name = $name;
177 19
        return $route;
178
    }
179
180
    /**
181
     * @return self
182
     */
183 19
    public function pattern(string $pattern): self
184
    {
185 19
        $new = clone $this;
186 19
        $new->pattern = $pattern;
187 19
        return $new;
188
    }
189
190
    /**
191
     * @return self
192
     */
193 4
    public function host(string $host): self
194
    {
195 4
        $route = clone $this;
196 4
        $route->host = rtrim($host, '/');
197 4
        return $route;
198
    }
199
200
    /**
201
     * Marks route as override. When added it will replace existing route with the same name.
202
     *
203
     * @return self
204
     */
205 2
    public function override(): self
206
    {
207 2
        $route = clone $this;
208 2
        $route->override = true;
209 2
        return $route;
210
    }
211
212
    /**
213
     * Parameter default values indexed by parameter names.
214
     *
215
     * @param array $defaults
216
     *
217
     * @return self
218
     */
219 1
    public function defaults(array $defaults): self
220
    {
221 1
        $route = clone $this;
222 1
        $route->defaults = $defaults;
223 1
        return $route;
224
    }
225
226
    /**
227
     * Appends a handler middleware definition that should be invoked for a matched route.
228
     * First added handler will be executed first.
229
     *
230
     * @param mixed $middlewareDefinition
231
     *
232
     * @return self
233
     */
234 11
    public function middleware($middlewareDefinition): self
235
    {
236 11
        if ($this->actionAdded) {
237
            throw new RuntimeException('middleware() can not be used after action().');
238
        }
239 11
        $route = clone $this;
240 11
        $route->middlewareDefinitions[] = $middlewareDefinition;
241 11
        return $route;
242
    }
243
244
    /**
245
     * Prepends a handler middleware definition that should be invoked for a matched route.
246
     * Last added handler will be executed first.
247
     *
248
     * @param mixed $middlewareDefinition
249
     *
250
     * @return self
251
     */
252 11
    public function prependMiddleware($middlewareDefinition): self
253
    {
254 11
        if (!$this->actionAdded) {
255
            throw new RuntimeException('prependMiddleware() can not be used before action().');
256
        }
257 11
        $route = clone $this;
258 11
        array_unshift($route->middlewareDefinitions, $middlewareDefinition);
259 11
        return $route;
260
    }
261
262
    /**
263
     * Appends action handler. It is a primary middleware definition that should be invoked last for a matched route.
264
     *
265
     * @param mixed $middlewareDefinition
266
     *
267
     * @return self
268
     */
269 13
    public function action($middlewareDefinition): self
270
    {
271 13
        $route = clone $this;
272 13
        $route->middlewareDefinitions[] = $middlewareDefinition;
273 13
        $route->actionAdded = true;
274 13
        return $route;
275
    }
276
277
    /**
278
     * Excludes middleware from being invoked when action is handled.
279
     * It is useful to avoid invoking one of the parent group middleware for
280
     * a certain route.
281
     *
282
     * @param mixed $middlewareDefinition
283
     *
284
     * @return self
285
     */
286
    public function disableMiddleware($middlewareDefinition): self
287
    {
288
        $route = clone $this;
289
        $route->disabledMiddlewareDefinitions[] = $middlewareDefinition;
290
        return $route;
291
    }
292
293 3
    public function __toString(): string
294
    {
295 3
        $result = '';
296
297 3
        if ($this->name !== null) {
298 2
            $result .= '[' . $this->name . '] ';
299
        }
300
301 3
        if ($this->methods !== []) {
302 3
            $result .= implode(',', $this->methods) . ' ';
303
        }
304 3
        if ($this->host !== null && strrpos($this->pattern, $this->host) === false) {
305 1
            $result .= $this->host;
306
        }
307 3
        $result .= $this->pattern;
308
309 3
        return $result;
310
    }
311
312
    /**
313
     * @param string $key
314
     *
315
     * @return mixed
316
     *
317
     * @internal
318
     */
319 34
    public function getData(string $key)
320
    {
321 34
        switch ($key) {
322 34
            case 'name':
323 22
                return $this->name ?? (implode(', ', $this->methods) . ' ' . $this->host . $this->pattern);
324 30
            case 'pattern':
325 19
                return $this->pattern;
326 29
            case 'host':
327 7
                return $this->host;
328 28
            case 'methods':
329 26
                return $this->methods;
330 5
            case 'defaults':
331 1
                return $this->defaults;
332 4
            case 'override':
333 4
                return $this->override;
334
            default:
335
                throw new InvalidArgumentException('Unknown data key: ' . $key);
336
        }
337
    }
338
339 18
    public function hasMiddlewares(): bool
340
    {
341 18
        return $this->middlewareDefinitions !== [];
342
    }
343
344
    public function __debugInfo()
345
    {
346
        return [
347
            'name' => $this->name,
348
            'methods' => $this->methods,
349
            'pattern' => $this->pattern,
350
            'host' => $this->host,
351
            'defaults' => $this->defaults,
352
            'override' => $this->override,
353
            'actionAdded' => $this->actionAdded,
354
            'middlewareDefinitions' => $this->middlewareDefinitions,
355
            'disabledMiddlewareDefinitions' => $this->disabledMiddlewareDefinitions,
356
            'middlewareDispatcher' => $this->dispatcher,
357
        ];
358
    }
359
}
360