Passed
Pull Request — master (#146)
by Sergei
02:20
created

Route::action()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

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