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