Passed
Pull Request — master (#68)
by Dmitriy
14:50
created

Route::validateMiddleware()   A

Complexity

Conditions 6
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
dl 0
loc 13
ccs 5
cts 5
cp 1
crap 6
rs 9.2222
c 1
b 0
f 0
eloc 6
nc 3
nop 1
1
<?php
2
3
namespace Yiisoft\Router;
4
5
use InvalidArgumentException;
6
use Psr\Http\Server\MiddlewareInterface;
7
use Yiisoft\Http\Method;
8
9
/**
10
 * Route defines a mapping from URL to callback / name and vice versa
11
 */
12
final class Route
13
{
14
    private ?string $name = null;
15
    /** @var string[] */
16
    private array $methods;
17
    private string $pattern;
18
    private ?string $host = null;
19
    private ?DispatcherInterface $dispatcher = null;
20
21
    /**
22
     * @var callable[]|string[]|array[]
23
     */
24
    private array $middlewares = [];
25
    private array $defaults = [];
26
27
    private function __construct(?DispatcherInterface $dispatcher = null)
28
    {
29
        $this->dispatcher = $dispatcher;
30
    }
31
32
    public function injectDispatcher(DispatcherInterface $dispatcher): void
33
    {
34
        $this->dispatcher = $dispatcher;
35
    }
36
37
    public function getDispatcherWithMiddlewares(): DispatcherInterface
38 44
    {
39
        return $this->dispatcher->withMiddlewares($this->middlewares);
0 ignored issues
show
Bug introduced by
The method withMiddlewares() 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

39
        return $this->dispatcher->/** @scrutinizer ignore-call */ withMiddlewares($this->middlewares);

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...
40 44
    }
41 44
42
    public function hasDispatcher(): bool
43 7
    {
44
        return $this->dispatcher !== null;
45 7
    }
46 7
47 7
    /**
48
     * @param string $pattern
49
     * @param callable|string|array|null $middleware primary route handler {@see addMiddleware()}
50 10
     * @param DispatcherInterface|null $dispatcher
51
     * @return self
52 10
     */
53
    public static function get(string $pattern, $middleware = null, ?DispatcherInterface $dispatcher = null): self
54
    {
55
        return self::methods([Method::GET], $pattern, $middleware, $dispatcher);
56
    }
57
58
    /**
59
     * @param string $pattern
60
     * @param callable|string|array|null $middleware primary route handler {@see addMiddleware()}
61 34
     * @param DispatcherInterface|null $dispatcher
62
     * @return self
63 34
     */
64
    public static function post(string $pattern, $middleware = null, ?DispatcherInterface $dispatcher = null): self
65
    {
66
        return self::methods([Method::POST], $pattern, $middleware, $dispatcher);
67
    }
68
69
    /**
70
     * @param string $pattern
71
     * @param callable|string|array|null $middleware primary route handler {@see addMiddleware()}
72 4
     * @param DispatcherInterface|null $dispatcher
73
     * @return self
74 4
     */
75
    public static function put(string $pattern, $middleware = null, ?DispatcherInterface $dispatcher = null): self
76
    {
77
        return self::methods([Method::PUT], $pattern, $middleware, $dispatcher);
78
    }
79
80
    /**
81
     * @param string $pattern
82
     * @param callable|string|array|null $middleware primary route handler {@see addMiddleware()}
83 1
     * @param DispatcherInterface|null $dispatcher
84
     * @return self
85 1
     */
86
    public static function delete(string $pattern, $middleware = null, ?DispatcherInterface $dispatcher = null): self
87
    {
88
        return self::methods([Method::DELETE], $pattern, $middleware, $dispatcher);
89
    }
90
91
    /**
92
     * @param string $pattern
93
     * @param callable|string|array|null $middleware primary route handler {@see addMiddleware()}
94 1
     * @param DispatcherInterface|null $dispatcher
95
     * @return self
96 1
     */
97
    public static function patch(string $pattern, $middleware = null, ?DispatcherInterface $dispatcher = null): self
98
    {
99
        return self::methods([Method::PATCH], $pattern, $middleware, $dispatcher);
100
    }
101
102
    /**
103
     * @param string $pattern
104
     * @param callable|string|array|null $middleware primary route handler {@see addMiddleware()}
105 1
     * @param DispatcherInterface|null $dispatcher
106
     * @return self
107 1
     */
108
    public static function head(string $pattern, $middleware = null, ?DispatcherInterface $dispatcher = null): self
109
    {
110
        return self::methods([Method::HEAD], $pattern, $middleware, $dispatcher);
111
    }
112
113
    /**
114
     * @param string $pattern
115
     * @param callable|string|array|null $middleware primary route handler {@see addMiddleware()}
116 1
     * @param DispatcherInterface|null $dispatcher
117
     * @return self
118 1
     */
119
    public static function options(string $pattern, $middleware = null, ?DispatcherInterface $dispatcher = null): self
120
    {
121
        return self::methods([Method::OPTIONS], $pattern, $middleware, $dispatcher);
122
    }
123
124
    /**
125
     * @param array $methods
126
     * @param string $pattern
127 1
     * @param callable|string|array|null $middleware primary route handler {@see addMiddleware()}
128
     * @param DispatcherInterface|null $dispatcher
129 1
     * @return self
130
     */
131
    public static function methods(
132
        array $methods,
133
        string $pattern,
134
        $middleware = null,
135
        ?DispatcherInterface $dispatcher = null
136
    ): self {
137
        $route = new self($dispatcher);
138
        $route->methods = $methods;
139 44
        $route->pattern = $pattern;
140
        if ($middleware !== null) {
141
            $route->validateMiddleware($middleware);
142
            $route->middlewares[] = $middleware;
143
        }
144
        return $route;
145 44
    }
146 44
147 44
    /**
148 44
     * @param string $pattern
149 10
     * @param callable|string|array|null $middleware primary route handler {@see addMiddleware()}
150 4
     * @param DispatcherInterface|null $dispatcher
151
     * @return self
152 38
     */
153
    public static function anyMethod(string $pattern, $middleware = null, ?DispatcherInterface $dispatcher = null): self
154
    {
155
        return self::methods(Method::ANY, $pattern, $middleware, $dispatcher);
156
    }
157
158
    public function name(string $name): self
159
    {
160
        $route = clone $this;
161 1
        $route->name = $name;
162
        return $route;
163 1
    }
164
165
    public function pattern(string $pattern): self
166 8
    {
167
        $new = clone $this;
168 8
        $new->pattern = $pattern;
169 8
        return $new;
170 8
    }
171
172
    public function host(string $host): self
173 4
    {
174
        $route = clone $this;
175 4
        $route->host = rtrim($host, '/');
176 4
        return $route;
177 4
    }
178
179
    /**
180 2
     * Parameter default values indexed by parameter names
181
     *
182 2
     * @param array $defaults
183 2
     * @return self
184 2
     */
185
    public function defaults(array $defaults): self
186
    {
187
        $route = clone $this;
188
        $route->defaults = $defaults;
189
        return $route;
190
    }
191
192
    /**
193 1
     * @param callable|string|array $middleware
194
     */
195 1
    private function validateMiddleware($middleware): void
196 1
    {
197 1
        if (
198
            is_string($middleware) && is_subclass_of($middleware, MiddlewareInterface::class)
199
        ) {
200
            return;
201
        }
202
203 21
        if ($this->isCallable($middleware) && (!is_array($middleware) || !is_object($middleware[0]))) {
204
            return;
205
        }
206 21
207
        throw new InvalidArgumentException('Parameter should be either PSR middleware class name or a callable.');
208 1
    }
209
210
211 20
    /**
212 13
     * Prepends a handler that should be invoked for a matching route.
213
     * Last added handler will be invoked first.
214
     *
215 7
     * Parameter can be a PSR middleware class name, handler action
216
     * (an array of [handlerClass, handlerMethod]) or a callable.
217
     *
218
     * For handler action and callable typed parameters are automatically injected using dependency
219
     * injection container passed to the route. Current request and handler could be obtained by
220
     * type-hinting for ServerRequestInterface and RequestHandlerInterface.
221
     *
222 12
     * @param callable|string|array $middleware
223
     * @return Route
224 12
     */
225
    public function addMiddleware($middleware): self
226
    {
227
        $this->validateMiddleware($middleware);
228
229
        $route = clone $this;
230
        $route->middlewares[] = $middleware;
231 12
        return $route;
232 3
    }
233
234
    public function __toString(): string
235 3
    {
236
        $result = '';
237
238 9
        if ($this->name !== null) {
239 9
            $result .= '[' . $this->name . '] ';
240
        }
241
242 9
        if ($this->methods !== []) {
243
            $result .= implode(',', $this->methods) . ' ';
244
        }
245
        if ($this->host !== null && strrpos($this->pattern, $this->host) === false) {
246
            $result .= $this->host;
247
        }
248 20
        $result .= $this->pattern;
249
250 20
        return $result;
251 13
    }
252
253
    public function getName(): string
254 7
    {
255
        return $this->name ?? (implode(', ', $this->methods) . ' ' . $this->host . $this->pattern);
256
    }
257
258
    public function getMethods(): array
259
    {
260
        return $this->methods;
261
    }
262
263
    public function getPattern(): string
264
    {
265
        return $this->pattern;
266
    }
267
268
    public function getHost(): ?string
269
    {
270
        return $this->host;
271 11
    }
272
273 11
    public function getDefaults(): array
274
    {
275 10
        return $this->defaults;
276 10
    }
277 10
278
    private function isCallable($definition): bool
279
    {
280 2
        if (is_callable($definition)) {
281
            return true;
282 2
        }
283
284 2
        return is_array($definition) && array_keys($definition) === [0, 1] && in_array($definition[1], get_class_methods($definition[0]) ?? [], true);
285 1
    }
286
}
287