Passed
Push — master ( e0a9c0...8285b1 )
by Alexander
02:11
created

Route::post()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 2
b 0
f 0
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
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 70
    private function __construct(array $methods, string $pattern, ?MiddlewareDispatcher $dispatcher = null)
44
    {
45 70
        $this->methods = $methods;
46 70
        $this->pattern = $pattern;
47 70
        $this->dispatcher = $dispatcher;
48 70
    }
49
50
    /**
51
     * @psalm-assert MiddlewareDispatcher $this->dispatcher
52
     */
53 13
    public function injectDispatcher(MiddlewareDispatcher $dispatcher): void
54
    {
55 13
        $this->dispatcher = $dispatcher;
56 13
    }
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 59
    public static function get(string $pattern, ?MiddlewareDispatcher $dispatcher = null): self
75
    {
76 59
        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 70
    public static function methods(
153
        array $methods,
154
        string $pattern,
155
        ?MiddlewareDispatcher $dispatcher = null
156
    ): self {
157 70
        return new self($methods, $pattern, $dispatcher);
158
    }
159
160
    /**
161
     * @return self
162
     */
163 23
    public function name(string $name): self
164
    {
165 23
        $route = clone $this;
166 23
        $route->name = $name;
167 23
        return $route;
168
    }
169
170
    /**
171
     * @return self
172
     */
173 20
    public function pattern(string $pattern): self
174
    {
175 20
        $new = clone $this;
176 20
        $new->pattern = $pattern;
177 20
        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 17
    public function middleware(...$middlewareDefinition): self
225
    {
226 17
        if ($this->actionAdded) {
227 1
            throw new RuntimeException('middleware() can not be used after action().');
228
        }
229 16
        $route = clone $this;
230 16
        array_push(
231 16
            $route->middlewareDefinitions,
232 16
            ...array_values($middlewareDefinition)
233
        );
234 16
        return $route;
235
    }
236
237
    /**
238
     * Prepends a handler middleware definition that should be invoked for a matched route.
239
     * Last added handler will be executed first.
240
     *
241
     * @param array|callable|string ...$middlewareDefinition
242
     *
243
     * @return self
244
     */
245 16
    public function prependMiddleware(...$middlewareDefinition): self
246
    {
247 16
        if (!$this->actionAdded) {
248 1
            throw new RuntimeException('prependMiddleware() can not be used before action().');
249
        }
250 15
        $route = clone $this;
251 15
        array_unshift(
252 15
            $route->middlewareDefinitions,
253 15
            ...array_values($middlewareDefinition)
254
        );
255 15
        return $route;
256
    }
257
258
    /**
259
     * Appends action handler. It is a primary middleware definition that should be invoked last for a matched route.
260
     *
261
     * @param array|callable|string $middlewareDefinition
262
     *
263
     * @return self
264
     */
265 19
    public function action($middlewareDefinition): self
266
    {
267 19
        $route = clone $this;
268 19
        $route->middlewareDefinitions[] = $middlewareDefinition;
269 19
        $route->actionAdded = true;
270 19
        return $route;
271
    }
272
273
    /**
274
     * Excludes middleware from being invoked when action is handled.
275
     * It is useful to avoid invoking one of the parent group middleware for
276
     * a certain route.
277
     *
278
     * @param mixed ...$middlewareDefinition
279
     *
280
     * @return self
281
     */
282 2
    public function disableMiddleware(...$middlewareDefinition): self
283
    {
284 2
        $route = clone $this;
285 2
        array_push(
286 2
            $route->disabledMiddlewareDefinitions,
287 2
            ...array_values($middlewareDefinition)
288
        );
289 2
        return $route;
290
    }
291
292
    /**
293
     * @param string $key
294
     *
295
     * @return mixed
296
     *
297
     * @internal
298
     *
299
     * @psalm-template T as string
300
     * @psalm-param T $key
301
     * @psalm-return (
302
     *   T is ('name'|'pattern') ? string :
303
     *     (T is 'host' ? string|null :
304
     *       (T is 'methods' ? array<array-key,string> :
305
     *         (T is 'defaults' ? array :
306
     *           (T is ('override'|'hasMiddlewares'|'hasDispatcher') ? bool :
307
     *             (T is 'dispatcherWithMiddlewares' ? MiddlewareDispatcher : mixed)
308
     *           )
309
     *         )
310
     *       )
311
     *     )
312
     * )
313
     */
314 52
    public function getData(string $key)
315
    {
316 52
        switch ($key) {
317 52
            case 'name':
318 26
                return $this->name ??
319 26
                    (implode(', ', $this->methods) . ' ' . (string) $this->host . $this->pattern);
320 49
            case 'pattern':
321 21
                return $this->pattern;
322 47
            case 'host':
323 8
                return $this->host;
324 45
            case 'methods':
325 28
                return $this->methods;
326 36
            case 'defaults':
327 1
                return $this->defaults;
328 35
            case 'override':
329 4
                return $this->override;
330 34
            case 'dispatcherWithMiddlewares':
331 19
                return $this->getDispatcherWithMiddlewares();
332 28
            case 'hasMiddlewares':
333 21
                return $this->middlewareDefinitions !== [];
334 12
            case 'hasDispatcher':
335 11
                return $this->dispatcher !== null;
336
            default:
337 1
                throw new InvalidArgumentException('Unknown data key: ' . $key);
338
        }
339
    }
340
341 3
    public function __toString(): string
342
    {
343 3
        $result = '';
344
345 3
        if ($this->name !== null) {
346 2
            $result .= '[' . $this->name . '] ';
347
        }
348
349 3
        if ($this->methods !== []) {
350 3
            $result .= implode(',', $this->methods) . ' ';
351
        }
352 3
        if ($this->host !== null && strrpos($this->pattern, $this->host) === false) {
353 1
            $result .= $this->host;
354
        }
355 3
        $result .= $this->pattern;
356
357 3
        return $result;
358
    }
359
360 1
    public function __debugInfo()
361
    {
362
        return [
363 1
            'name' => $this->name,
364 1
            'methods' => $this->methods,
365 1
            'pattern' => $this->pattern,
366 1
            'host' => $this->host,
367 1
            'defaults' => $this->defaults,
368 1
            'override' => $this->override,
369 1
            'actionAdded' => $this->actionAdded,
370 1
            'middlewareDefinitions' => $this->middlewareDefinitions,
371 1
            'disabledMiddlewareDefinitions' => $this->disabledMiddlewareDefinitions,
372 1
            'middlewareDispatcher' => $this->dispatcher,
373
        ];
374
    }
375
376 19
    private function getDispatcherWithMiddlewares(): MiddlewareDispatcher
377
    {
378 19
        if ($this->dispatcher === null) {
379 1
            throw new RuntimeException(sprintf('There is no dispatcher in the route %s.', $this->getData('name')));
380
        }
381
382 18
        if ($this->dispatcher->hasMiddlewares()) {
383 1
            return $this->dispatcher;
384
        }
385
386
        /** @var mixed $definition */
387 17
        foreach ($this->middlewareDefinitions as $index => $definition) {
388 16
            if (in_array($definition, $this->disabledMiddlewareDefinitions, true)) {
389 1
                unset($this->middlewareDefinitions[$index]);
390
            }
391
        }
392
393 17
        return $this->dispatcher = $this->dispatcher->withMiddlewares($this->middlewareDefinitions);
394
    }
395
}
396