Passed
Push — develop ( 2bc06f...07d547 )
by nguereza
02:52
created

Router::setBasePath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 5
rs 10
1
<?php
2
3
/**
4
 * Platine Router
5
 *
6
 * Platine Router is the a lightweight and simple router using middleware
7
 *  to match and dispatch the request.
8
 *
9
 * This content is released under the MIT License (MIT)
10
 *
11
 * Copyright (c) 2020 Platine Router
12
 * Copyright (c) 2020 Evgeniy Zyubin
13
 *
14
 * Permission is hereby granted, free of charge, to any person obtaining a copy
15
 * of this software and associated documentation files (the "Software"), to deal
16
 * in the Software without restriction, including without limitation the rights
17
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
 * copies of the Software, and to permit persons to whom the Software is
19
 * furnished to do so, subject to the following conditions:
20
 *
21
 * The above copyright notice and this permission notice shall be included in all
22
 * copies or substantial portions of the Software.
23
 *
24
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
 * SOFTWARE.
31
 */
32
33
/**
34
 *  @file Router.php
35
 *
36
 *  The Router class is used to route the request to the handler
37
 *  for response generation
38
 *
39
 *  @package    Platine\Route
40
 *  @author Platine Developers Team
41
 *  @copyright  Copyright (c) 2020
42
 *  @license    http://opensource.org/licenses/MIT  MIT License
43
 *  @link   https://www.platine-php.com
44
 *  @version 1.0.0
45
 *  @filesource
46
 */
47
48
declare(strict_types=1);
49
50
namespace Platine\Route;
51
52
use Platine\Http\ServerRequestInterface;
53
use Platine\Http\UriInterface;
54
use Platine\Route\Exception\RouteNotFoundException;
55
56
class Router
57
{
58
    /**
59
     * The current route group prefix
60
     * @var string
61
     */
62
    protected string $groupPrefix = '';
63
64
    /**
65
     * The instance of RouteCollectionInterface
66
     * @var  RouteCollectionInterface
67
     */
68
    protected RouteCollectionInterface $routes;
69
70
    /**
71
     * The base path to use
72
     * @var string
73
     */
74
    protected string $basePath = '/';
75
76
    /**
77
     * Create new Router instance
78
     * @param RouteCollectionInterface|null $routes
79
     */
80
    public function __construct(?RouteCollectionInterface $routes = null)
81
    {
82
        $this->routes = $routes ? $routes : new RouteCollection();
83
    }
84
85
    /**
86
     * Set base path
87
     * @param string $basePath
88
     * @return $this
89
     */
90
    public function setBasePath(string $basePath): self
91
    {
92
        $this->basePath = $basePath;
93
94
        return $this;
95
    }
96
97
98
    /**
99
     * Return the instance of RouteCollectionInterface with all routes set.
100
     *
101
     * @return RouteCollectionInterface
102
     */
103
    public function routes(): RouteCollectionInterface
104
    {
105
        return $this->routes;
106
    }
107
108
    /**
109
     * Create a route group with a common prefix.
110
     *
111
     * The callback can take a Router instance as a parameter.
112
     * All routes created in the passed callback will have the given group prefix prepended.
113
     *
114
     * @param  string   $prefix   common path prefix for the route group.
115
     * @param  callable $callback callback that will add routes with a common path prefix.
116
     * @return void
117
     */
118
    public function group(string $prefix, callable $callback): void
119
    {
120
        $currentGroupPrefix = $this->groupPrefix;
121
        $this->groupPrefix = $currentGroupPrefix . $prefix;
122
        $callback($this);
123
        $this->groupPrefix = $currentGroupPrefix;
124
    }
125
126
    /**
127
     * Add new route and return it
128
     * @param string $pattern path pattern with parameters.
129
     * @param mixed $handler action, controller, callable, closure, etc.
130
     * @param string[]  $methods allowed request methods of the route.
131
     * @param string $name  the  route name.
132
     * @param array<string, mixed> $attributes the route attributes.
133
     *
134
     * @return Route
135
     */
136
    public function add(
137
        string $pattern,
138
        $handler,
139
        array $methods,
140
        string $name = '',
141
        array $attributes = []
142
    ): Route {
143
        $pattern = $this->groupPrefix . $pattern;
144
        $route = new Route($pattern, $handler, $name, $methods, $attributes);
145
        $this->routes->add($route);
146
147
        return $route;
148
    }
149
150
151
    /**
152
     * Add a generic route for any request methods and returns it.
153
     *
154
     * @param  string $pattern path pattern with parameters.
155
     * @param  mixed $handler action, controller, callable, closure, etc.
156
     * @param  string $name    the  route name.
157
     * @param array<string, mixed> $attributes the route attributes.
158
     * @return Route the new route added
159
     */
160
    public function any(string $pattern, $handler, string $name = '', array $attributes = []): Route
161
    {
162
        return $this->add($pattern, $handler, [], $name, $attributes);
163
    }
164
165
    /**
166
     * Add a GET route and returns it.
167
     *
168
     * @see  Router::add
169
     * @param mixed $handler action, controller, callable, closure, etc.
170
     * @param array<string, mixed> $attributes the route attributes.
171
     */
172
    public function get(string $pattern, $handler, string $name = '', array $attributes = []): Route
173
    {
174
        return $this->add($pattern, $handler, ['GET'], $name, $attributes);
175
    }
176
177
    /**
178
     * Add a POST route and returns it.
179
     *
180
     * @see  Router::add
181
     * @param mixed $handler action, controller, callable, closure, etc.
182
     * @param array<string, mixed> $attributes the route attributes.
183
     */
184
    public function post(string $pattern, $handler, string $name = '', array $attributes = []): Route
185
    {
186
        return $this->add($pattern, $handler, ['POST'], $name, $attributes);
187
    }
188
189
    /**
190
     * Add a PUT route and returns it.
191
     *
192
     * @see  Router::add
193
     * @param mixed $handler action, controller, callable, closure, etc.
194
     * @param array<string, mixed> $attributes the route attributes.
195
     */
196
    public function put(string $pattern, $handler, string $name = '', array $attributes = []): Route
197
    {
198
        return $this->add($pattern, $handler, ['PUT'], $name, $attributes);
199
    }
200
201
    /**
202
     * Add a PATCH route and returns it.
203
     *
204
     * @see  Router::add
205
     * @param mixed $handler action, controller, callable, closure, etc.
206
     * @param array<string, mixed> $attributes the route attributes.
207
     */
208
    public function patch(string $pattern, $handler, string $name = '', array $attributes = []): Route
209
    {
210
        return $this->add($pattern, $handler, ['PATCH'], $name, $attributes);
211
    }
212
213
    /**
214
     * Add a DELETE route and returns it.
215
     *
216
     * @see  Router::add
217
     * @param mixed $handler action, controller, callable, closure, etc.
218
     * @param array<string, mixed> $attributes the route attributes.
219
     */
220
    public function delete(string $pattern, $handler, string $name = '', array $attributes = []): Route
221
    {
222
        return $this->add($pattern, $handler, ['DELETE'], $name, $attributes);
223
    }
224
225
    /**
226
     * Add a HEAD route and returns it.
227
     *
228
     * @see  Router::add
229
     * @param mixed $handler action, controller, callable, closure, etc.
230
     * @param array<string, mixed> $attributes the route attributes.
231
     */
232
    public function head(string $pattern, $handler, string $name = '', array $attributes = []): Route
233
    {
234
        return $this->add($pattern, $handler, ['HEAD'], $name, $attributes);
235
    }
236
237
    /**
238
     * Add a OPTIONS route and returns it.
239
     *
240
     * @see  Router::add
241
     * @param mixed $handler action, controller, callable, closure, etc.
242
     * @param array<string, mixed> $attributes the route attributes.
243
     */
244
    public function options(string $pattern, $handler, string $name = '', array $attributes = []): Route
245
    {
246
        return $this->add($pattern, $handler, ['OPTIONS'], $name, $attributes);
247
    }
248
249
    /**
250
     * Add a resource route.
251
     *
252
     * @param  string $pattern path pattern with parameters.
253
     * @param  class-string $handler action class.
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
254
     * @param  string $name the  route name and permission prefix.
255
     * @param  bool $permission whether to use permission.
256
     * @param array<string, mixed> $attributes the route attributes.
257
     * @return $this
258
     */
259
    public function resource(
260
        string $pattern,
261
        string $handler,
262
        string $name = '',
263
        bool $permission = true,
264
        array $attributes = []
265
    ): self {
266
267
        $maps = [
268
            [
269
                'path' => '',
270
                'action' => '%s@index',
271
                'route_name' => '%s_list',
272
                'method' => 'get',
273
                'csrf' => false,
274
                'many' => false,
275
                'permission' => $permission ? '%s_list' : null,
276
            ],
277
            [
278
                'path' => '/detail/{id}',
279
                'action' => '%s@detail',
280
                'route_name' => '%s_detail',
281
                'method' => 'get',
282
                'csrf' => false,
283
                'many' => false,
284
                'permission' => $permission ? '%s_detail' : null,
285
            ],
286
            [
287
                'path' => '/create',
288
                'action' => '%s@create',
289
                'route_name' => '%s_create',
290
                'method' => 'add',
291
                'csrf' => false,
292
                'many' => true,
293
                'permission' => $permission ? '%s_create' : null,
294
            ],
295
            [
296
                'path' => '/update/{id}',
297
                'action' => '%s@update',
298
                'route_name' => '%s_update',
299
                'method' => 'add',
300
                'csrf' => false,
301
                'many' => true,
302
                'permission' => $permission ? '%s_update' : null,
303
            ],
304
            [
305
                'path' => '/delete/{id}',
306
                'action' => '%s@delete',
307
                'route_name' => '%s_delete',
308
                'method' => 'get',
309
                'csrf' => true,
310
                'many' => false,
311
                'permission' => $permission ? '%s_delete' : null,
312
            ],
313
        ];
314
315
        if (empty($name)) {
316
            $name = trim($pattern, '/');
317
        }
318
319
        $this->group($pattern, function (Router $router) use ($handler, $maps, $name, $attributes) {
320
            foreach ($maps as $map) {
321
                if ($map['many']) {
322
                    /** @var Route $route */
323
                    $route = $router->{$map['method']}(
324
                        $map['path'],
325
                        sprintf($map['action'], $handler),
326
                        ['GET', 'POST'],
327
                        sprintf($map['route_name'], $name),
328
                        $attributes
329
                    );
330
                } else {
331
                    /** @var Route $route */
332
                    $route = $router->{$map['method']}(
333
                        $map['path'],
334
                        sprintf($map['action'], $handler),
335
                        sprintf($map['route_name'], $name),
336
                        $attributes
337
                    );
338
                }
339
340
                if ($map['permission']) {
341
                    $route->setAttribute('permission', sprintf($map['permission'], $name));
342
                }
343
344
                if ($map['csrf']) {
345
                    $route->setAttribute('csrf', true);
346
                }
347
            }
348
        });
349
350
        return $this;
351
    }
352
353
354
    /**
355
     * Matches the request against known routes.
356
     * @param  ServerRequestInterface $request
357
     * @param  bool $checkAllowedMethods whether to check if the
358
     * request method matches the allowed route methods.
359
     * @return Route|null matched route or null if the
360
     * request does not match any route.
361
     */
362
    public function match(
363
        ServerRequestInterface $request,
364
        bool $checkAllowedMethods = true
365
    ): ?Route {
366
        $notAllowedMethodRoute = null;
367
        foreach ($this->routes->all() as $route) {
368
            if (!$route->match($request, $this->basePath)) {
369
                continue;
370
            }
371
372
            if ($route->isAllowedMethod($request->getMethod())) {
373
                return $route;
374
            }
375
376
            if ($notAllowedMethodRoute === null) {
377
                $notAllowedMethodRoute = $route;
378
            }
379
        }
380
381
        return $checkAllowedMethods ? null : $notAllowedMethodRoute;
382
    }
383
384
    /**
385
     * Return the Uri for this route
386
     * @param  string  $name the route name
387
     * @param  array<string, mixed>  $parameters the route parameters
388
     * @return UriInterface
389
     *
390
     * @throws RouteNotFoundException if the route does not exist.
391
     */
392
    public function getUri(string $name, array $parameters = []): UriInterface
393
    {
394
        if ($this->routes->has($name)) {
395
            return $this->routes->get($name)->getUri($parameters, $this->basePath);
396
        }
397
398
        throw new RouteNotFoundException(sprintf('Route [%s] not found', $name));
399
    }
400
401
    /**
402
     * Generates the URL path from the named route and parameters.
403
     * @param  string $name
404
     * @param  array<string, mixed>  $parameters
405
     * @return string
406
     *
407
     * @throws RouteNotFoundException if the route does not exist.
408
     */
409
    public function path(string $name, array $parameters = []): string
410
    {
411
        return $this->getUri($name, $parameters)->getPath();
412
    }
413
}
414