Test Failed
Pull Request — master (#13)
by Divine Niiquaye
14:05
created

Route::get()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 16
c 1
b 0
f 0
dl 0
loc 23
rs 9.7333
ccs 0
cts 0
cp 0
cc 4
nc 4
nop 1
crap 20
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Flight Routing.
7
 *
8
 * PHP version 7.1 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Flight\Routing;
19
20
/**
21
 * Value object representing a single route.
22
 *
23
 * Internally, only those three properties are required. However, underlying
24
 * router implementations may allow or require additional information, such as
25
 * information defining how to generate a URL from the given route, qualifiers
26
 * for how segments of a route match, or even default values to use.
27
 * 
28
 * __call() forwards method-calls to Route, but returns mixed contents.
29
 * listing Route's methods below, so that IDEs know they are valid
30
 *
31
 * @method string getPath() Gets the route path.
32
 * @method null|string getName() Gets the route name.
33
 * @method string[] getMethods() Gets the route methods.
34
 * @method string[] getSchemes() Gets the route domain schemes.
35
 * @method string[] getDomain() Gets the route host.
36
 * @method mixed getController() Gets the route handler.
37
 * @method array getMiddlewares() Gets the route middlewares.
38
 * @method array getPatterns() Gets the route pattern placeholder assert.
39
 * @method array getDefaults() Gets the route default settings.
40
 * @method array getArguments() Gets the arguments passed to route handler as parameters.
41
 * @method array getAll() Gets all the routes properties.
42
 *
43
 * @author Divine Niiquaye Ibok <[email protected]>
44
 */
45
class Route
46
{
47
    use Traits\CastingTrait;
48
49
    /**
50
     * A Pattern to Locates appropriate route by name, support dynamic route allocation using following pattern:
51
     * Pattern route:   `pattern/*<controller@action>`
52
     * Default route: `*<controller@action>`
53
     * Only action:   `pattern/*<action>`.
54
     *
55
     * @var string
56
     */
57
    public const RCA_PATTERN = '/^(?P<route>.*?)?(?P<handler>\*\<(?:(?<c>[a-zA-Z0-9\\\\]+?)\@)?(?<a>[a-zA-Z0-9_\-]+)?\>)?$/u';
58
59
    /**
60
     * A Pattern to match protocol, host and port from a url
61
     *
62
     * Examples of urls that can be matched:
63
     * http://en.example.domain
64
     * //example.domain
65
     * //example.com
66
     * https://example.com:34
67
     * //example.com
68
     * example.com
69
     * localhost:8000
70
     * {foo}.domain.com
71
     *
72
     * @var string
73
     */
74
    public const URL_PATTERN = '/^(?:(?P<scheme>https?)\:)?(?P<domain>(?:\/\/)?(?P<host>[^\/\*]+)?(\:\d+)?)\/*?$/u';
75
76
    /**
77
     * Create a new Route constructor.
78
     *
79 151
     * @param string          $pattern The route pattern
80
     * @param string|string[] $methods The route HTTP methods. Multiple methods can be supplied,
81 151
     *                                 delimited by a pipe character '|', eg. 'GET|POST'
82 151
     * @param mixed           $handler The PHP class, object or callable that returns the response when matched
83 151
     */
84 151
    public function __construct(string $pattern, string $methods = 'GET|HEAD', $handler = null)
85 150
    {
86
        $this->controller = $handler;
87
        $this->path       = $this->castRoute($pattern);
88
89
        if (!empty($methods)) {
90
            $this->method(...\explode('|', $methods));
91
        }
92 1
    }
93
94 1
    /**
95
     * @internal This is handled different by router
96 1
     *
97 1
     * @param array $properties
98 1
     */
99 1
    public static function __set_state(array $properties)
100 1
    {
101 1
        $recovered = new self($properties['path'], '', $properties['controller']);
102 1
103
        unset($properties['path'], $properties['controller']);
104 1
105
        foreach ($properties as $name => $property) {
106
            $recovered->{$name} = $property;
107
        }
108
109
        return $recovered;
110
    }
111
112
    public function __call($method, $arguments)
113
    {
114
        $routeMethod = \strtolower(\preg_replace('~^get([A-Z]{1}[a-z]+)$~', '\1', $method, 1));
115
116
        if ('all' === $routeMethod || 'arugments' === $routeMethod) {
117
            return \call_user_func([$this, 'get'], $routeMethod);
118
        }
119
120
        if (!\property_exists(__CLASS__, $routeMethod)) {
121
            throw new \BadMethodCallException(
122
                \sprintf(
123
                    'Property "%s->%s" does not exist. should be one of [%s],' .
124
                    ' or arguments, prefixed with a \'get\' name; eg: getName().',
125
                    Route::class,
126
                    $method,
127
                    join(', ', array_keys($this->get('all')))
128
                )
129
            );
130
        }
131
132
        return \call_user_func([$this, 'get'], $routeMethod);
133
    }
134
135
    /**
136
     * Sets the route path prefix.
137
     *
138
     * @param string $path
139
     *
140
     * @return Route $this The current Route instance
141
     */
142
    public function prefix(string $path): self
143
    {
144
        $this->path = $this->castPrefix($this->path, $path);
145
146
        return $this;
147
    }
148
149
    /**
150
     * Sets the route path pattern.
151
     *
152
     * @param string $pattern
153
     *
154
     * @return Route $this The current Route instance
155
     */
156
    public function path(string $pattern): self
157
    {
158
        $this->path = $this->castRoute($pattern);
159
160
        return $this;
161
    }
162
163
    /**
164
     * Sets the route name.
165
     *
166
     * @param string $routeName
167
     *
168
     * @return Route $this The current Route instance
169
     */
170
    public function bind(string $routeName): self
171
    {
172
        $this->name = $routeName;
173
174
        return $this;
175
    }
176
177
    /**
178
     * Sets the route code that should be executed when matched.
179
     *
180
     * @param mixed $to PHP class, object or callable that returns the response when matched
181
     *
182
     * @return Route $this The current Route instance
183
     */
184
    public function run($to): self
185
    {
186
        $this->controller = $to;
187
188
        return $this;
189
    }
190
191
    /**
192
     * Sets the requirement for a route variable.
193
     *
194
     * @param string          $variable The variable name
195
     * @param string|string[] $regexp   The regexp to apply
196
     *
197
     * @return Route $this The current route instance
198
     */
199
    public function assert(string $variable, $regexp): self
200
    {
201
        $this->patterns[$variable] = $regexp;
202
203
        return $this;
204
    }
205
206
    /**
207
     * Sets the default value for a route variable.
208
     *
209
     * @param string $variable The variable name
210
     * @param mixed  $default  The default value
211
     *
212
     * @return Route $this The current Route instance
213
     */
214
    public function default(string $variable, $default): self
215
    {
216
        $this->defaults[$variable] = $default;
217
218
        return $this;
219
    }
220
221
    /**
222
     * Sets the parameter value for a route handler.
223
     *
224
     * @param int|string $variable The parameter name
225
     * @param mixed      $value    The parameter value
226
     *
227
     * @return Route $this The current Route instance
228
     */
229
    public function argument($variable, $value): self
230
    {
231
        if (!\is_int($variable)) {
232
            if (\is_numeric($value)) {
233
                $value = (int) $value;
234
            } elseif (\is_string($value)) {
235
                $value = \rawurldecode($value);
236
            }
237
238
            $this->defaults['_arguments'][$variable] = $value;
239
        }
240
241
        return $this;
242
    }
243
244
    /**
245
     * Sets the requirement for the HTTP method.
246
     *
247
     * @param string $methods the HTTP method(s) name
248
     *
249
     * @return Route $this The current Route instance
250
     */
251
    public function method(string ...$methods): self
252
    {
253
        foreach ($methods as $method) {
254
            $this->methods[\strtoupper($method)] = true;
255
        }
256
257
        return $this;
258
    }
259
260
    /**
261
     * Sets the requirement of host on this Route.
262
     *
263
     * @param string $hosts The host for which this route should be enabled
264
     *
265
     * @return Route $this The current Route instance
266
     */
267
    public function domain(string ...$hosts): self
268
    {
269
        foreach ($hosts as $host) {
270
            \preg_match(Route::URL_PATTERN, $host, $matches);
271
272
            if (isset($matches['scheme']) && !empty($scheme = $matches['scheme'])) {
273
                $this->schemes[$scheme] = true;
274
            }
275
276
            $this->domain[$matches['host'] ?? $host] = true;
277
        }
278
279
        return $this;
280
    }
281
282
    /**
283
     * Sets the requirement of domain scheme on this Route.
284
     *
285
     * @param string ...$schemes
286
     *
287
     * @return Route $this The current Route instance
288
     */
289
    public function scheme(string ...$schemes): self
290
    {
291
        foreach ($schemes as $scheme) {
292
            $this->schemes[$scheme] = true;
293
        }
294
295
        return $this;
296
    }
297
298
    /**
299
     * Sets the middleware(s) to handle before triggering the route handler
300
     *
301
     * @param mixed ...$middlewares
302
     *
303
     * @return Route $this The current Route instance
304
     */
305
    public function middleware(...$middlewares): self
306
    {
307
        /** @var int|string $index */
308
        foreach ($middlewares as $index => $middleware) {
309
            if (!\is_callable($middleware) && (\is_int($index) && \is_array($middleware))) {
310
                $this->middleware(...$middleware);
311
312
                continue;
313
            }
314
315
            $this->middlewares[] = $middleware;
316
        }
317
318
        return $this;
319
    }
320
321
    /**
322
     * Get any of (name, path, domain, defaults, schemes, domain, controller, patterns, middlewares).
323
     * And also accepts "all" and "arguments".
324
     *
325
     * @param string $name
326
     */
327
    public function get(string $name)
328
    {
329
        if (\property_exists(__CLASS__, $name)) {
330
            return $this->{$name};
331
        }
332
333
        if ('all' === $name) {
334
            return [
335
                'controller'  => $this->controller,
336
                'methods'     => $this->methods,
337
                'schemes'     => $this->schemes,
338
                'domain'      => $this->domain,
339
                'name'        => $this->name,
340
                'path'        => $this->path,
341
                'patterns'    => $this->patterns,
342
                'middlewares' => $this->middlewares,
343
                'defaults'    => $this->defaults,
344
            ];
345
        } elseif ('arguments' === $name) {
346
            return $this->defaults['_arguments'] ?? [];
347
        }
348
349
        return null;
350
    }
351
352
    public function generateRouteName(string $prefix): string
353
    {
354
        $methods = \implode('_', \array_keys($this->methods)) . '_';
355
356
        $routeName = $methods . $prefix . $this->path;
357
        $routeName = \str_replace(['/', ':', '|', '-'], '_', $routeName);
358
        $routeName = \preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName);
359
360
        // Collapse consecutive underscores down into a single underscore.
361
        $routeName = (string) \preg_replace('/_+/', '_', $routeName);
362
363
        return $routeName;
364
    }
365
}
366