Completed
Push — master ( 8b19ed...9f4169 )
by Alex
01:44
created

Router.php (1 issue)

1
<?php
2
namespace Mezon\Router;
3
4
// TODO add custom types
5
// TODO PSR-7 compliant
6
// TODO add non-static routes optimizations like here https://medium.com/@nicolas.grekas/making-symfonys-router-77-7x-faster-1-2-958e3754f0e1
7
// TODO add 404 test benchmark
8
9
/**
10
 * Class Router
11
 *
12
 * @package Mezon
13
 * @subpackage Router
14
 * @author Dodonov A.A.
15
 * @version v.1.0 (2019/08/15)
16
 * @copyright Copyright (c) 2019, aeon.org
17
 */
18
19
/**
20
 * Router class
21
 */
22
class Router
23
{
24
25
    use \Mezon\Router\RoutesSet, \Mezon\Router\UrlParser, \Mezon\Router\DefaultTypes;
26
27
    /**
28
     * Method wich handles invalid route error
29
     *
30
     * @var callable
31
     */
32
    private $invalidRouteErrorHandler;
33
34
    /**
35
     * Method returns request method
36
     *
37
     * @return string Request method
38
     */
39
    protected function getRequestMethod(): string
40
    {
41
        return $_SERVER['REQUEST_METHOD'] ?? 'GET';
42
    }
43
44
    /**
45
     * Constructor
46
     */
47
    public function __construct()
48
    {
49
        $_SERVER['REQUEST_METHOD'] = $this->getRequestMethod();
50
51
        $this->invalidRouteErrorHandler = [
52
            $this,
53
            'noProcessorFoundErrorHandler'
54
        ];
55
56
        $this->initDefaultTypes();
57
    }
58
59
    /**
60
     * Method fetches actions from the objects and creates GetRoutes for them
61
     *
62
     * @param object $object
63
     *            Object to be processed
64
     */
65
    public function fetchActions(object $object): void
66
    {
67
        $methods = get_class_methods($object);
68
69
        foreach ($methods as $method) {
70
            if (strpos($method, 'action') === 0) {
71
                $route = \Mezon\Router\Utils::convertMethodNameToRoute($method);
72
                $this->addGetRoute($route, $object, $method);
73
                $this->addPostRoute($route, $object, $method);
74
            }
75
        }
76
    }
77
78
    /**
79
     * Method adds route and it's handler
80
     *
81
     * $callback function may have two parameters - $route and $parameters. Where $route is a called route,
82
     * and $parameters is associative array (parameter name => parameter value) with URL parameters
83
     *
84
     * @param string $route
85
     *            Route
86
     * @param mixed $callback
87
     *            Collback wich will be processing route call.
88
     * @param string|array $requestMethod
89
     *            Request type
90
     * @param string $routeName name of the route
91
     */
92
    public function addRoute(string $route, $callback, $requestMethod = 'GET', string $routeName = ''): void
93
    {
94
        $route = trim($route, '/');
95
96
        if ($route == '*') {
97
            $this->universalRouteWasAdded = true;
98
        }
99
100
        if (is_array($requestMethod)) {
101
            foreach ($requestMethod as $r) {
102
                $this->addRoute($route, $callback, $r, $routeName);
103
            }
104
        } else {
105
            $this->validateRequestMethod($requestMethod);
106
107
            // this 'if' is for backward compatibility
108
            // remove it on 02-04-2021
109
            if (is_array($callback) && isset($callback[1]) && is_array($callback[1])) {
110
                $callback = $callback[1];
111
            }
112
            $routes[$route] = $callback;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$routes was never initialized. Although not strictly required by PHP, it is generally a good practice to add $routes = array(); before regardless.
Loading history...
113
            $this->setRouteCallback($route, $callback, $requestMethod);
114
            // register route name
115
            $this->registerRouteName($routeName, $route);
116
        }
117
    }
118
119
    /**
120
     * Method processes no processor found error
121
     *
122
     * @param string $route
123
     *            Route
124
     */
125
    public function noProcessorFoundErrorHandler(string $route)
126
    {
127
        throw (new \Exception(
128
            'The processor was not found for the route ' . $route . ' in ' . $this->getAllRoutesTrace()));
129
    }
130
131
    /**
132
     * Method sets InvalidRouteErrorHandler function
133
     *
134
     * @param callable $function
135
     *            Error handler
136
     */
137
    public function setNoProcessorFoundErrorHandler(callable $function)
138
    {
139
        $oldErrorHandler = $this->invalidRouteErrorHandler;
140
141
        $this->invalidRouteErrorHandler = $function;
142
143
        return $oldErrorHandler;
144
    }
145
146
    /**
147
     * Processing specified router
148
     *
149
     * @param mixed $route
150
     *            Route
151
     */
152
    public function callRoute($route)
153
    {
154
        $route = \Mezon\Router\Utils::prepareRoute($route);
155
        $requestMethod = $this->getRequestMethod();
156
        $this->validateRequestMethod($requestMethod);
157
        $routesForMethod = $this->getRoutesForMethod($requestMethod);
158
159
        if (($result = $this->findStaticRouteProcessor($routesForMethod, $route)) !== false) {
160
            return $result;
161
        }
162
163
        if (($result = $this->findDynamicRouteProcessor($routesForMethod, $route)) !== false) {
164
            return $result;
165
        }
166
167
        call_user_func($this->invalidRouteErrorHandler, $route);
168
    }
169
170
    /**
171
     * Method returns call back by it's router
172
     *
173
     * @param array|string $route
174
     *            route
175
     * @return array|callable|bool route callback
176
     */
177
    public function getCallback($route)
178
    {
179
        $route = \Mezon\Router\Utils::prepareRoute($route);
180
        $requestMethod = $this->getRequestMethod();
181
        $routesForMethod = $this->getRoutesForMethod($requestMethod);
182
183
        if (($result = $this->getStaticRouteProcessor($routesForMethod, $route)) !== false) {
184
            return $result;
185
        }
186
187
        if (($result = $this->getDynamicRouteProcessor($routesForMethod, $route)) !== false) {
188
            return $result;
189
        }
190
191
        call_user_func($this->invalidRouteErrorHandler, $route); // @codeCoverageIgnoreStart
192
    } // @codeCoverageIgnoreEnd
193
}
194