Completed
Push — master ( 00cc1c...3873f0 )
by Alex
01:35
created

UrlParser.php (1 issue)

Labels
Severity
1
<?php
2
namespace Mezon\Router;
3
4
trait UrlParser
5
{
6
7
    /**
8
     * Parsed parameters of the calling router
9
     *
10
     * @var array
11
     */
12
    protected $parameters = [];
13
14
    /**
15
     * Cache for regular expressions
16
     *
17
     * @var array
18
     */
19
    private $cachedRegExps = [];
20
21
    /**
22
     * Cached parameters for route
23
     *
24
     * @var array
25
     */
26
    private $cachedParameters = [];
27
28
    /**
29
     * Method compiles route pattern string in regex string.
30
     * For example [i:id]/some-str in ([\[0-9\]])/some-str
31
     *
32
     * @param string $routerPattern
33
     *            router pattern
34
     * @return string regexp pattern
35
     */
36
    private function _getRouteMatcherRegExPattern(string $routerPattern): string
37
    {
38
        // try read from cache
39
        if (isset($this->cachedRegExps[$routerPattern])) {
40
            return $this->cachedRegExps[$routerPattern];
41
        }
42
43
        // parsing routes
44
        $compiledRouterPattern = $routerPattern;
45
        foreach ($this->types as $typeClass) {
46
            $compiledRouterPattern = preg_replace(
47
                '/' . $typeClass::searchRegExp() . '/',
48
                $typeClass::parserRegExp(),
49
                $compiledRouterPattern);
50
        }
51
52
        // final setup + save in cache
53
        $this->cachedRegExps[$routerPattern] = $compiledRouterPattern;
54
55
        return $compiledRouterPattern;
56
    }
57
58
    /**
59
     * Method returns all parameter names in the route
60
     *
61
     * @param string $routerPattern
62
     *            route
63
     * @return array names
64
     */
65
    private function _getParameterNames(string $routerPattern): array
66
    {
67
        if (isset($this->cachedParameters[$routerPattern])) {
68
            return $this->cachedParameters[$routerPattern];
69
        }
70
71
        $regExPattern = [];
72
73
        foreach (array_keys($this->types) as $typeName) {
74
            $regExPattern[] = $typeName;
75
        }
76
77
        $regExPattern = '\[(' . implode('|', $regExPattern) . '):([a-zA-Z0-9_\-]+)\]';
78
79
        $names = [];
80
        preg_match_all('/' . str_replace('/', '\\/', $regExPattern) . '/', $routerPattern, $names);
81
82
        $return = [];
83
84
        foreach ($names[2] as $name) {
85
            $return[] = $name;
86
        }
87
88
        $this->cachedParameters[$routerPattern] = $return;
89
90
        return $return;
91
    }
92
93
    /**
94
     * Method warms cache
95
     */
96
    public function warmCache(): void
97
    {
98
        foreach (self::getListOfSupportedRequestMethods() as $requestMethod) {
99
            $routesForMethod = $this->getRoutesForMethod($requestMethod);
100
101
            foreach (array_keys($routesForMethod) as $routerPattern) {
102
                // may be it is static route?
103
                if (strpos($routerPattern, '[') === false) {
104
                    // it is static route, so skip it
105
                    continue;
106
                }
107
108
                $this->_getRouteMatcherRegExPattern($routerPattern);
109
110
                $this->_getParameterNames($routerPattern);
111
            }
112
        }
113
    }
114
115
    /**
116
     * Method searches dynamic route processor
117
     *
118
     * @param array $processors
119
     *            Callable router's processor
120
     * @param string $route
121
     *            Route
122
     * @return array|callable|bool route's handler or false in case the handler was not found
123
     */
124
    protected function getDynamicRouteProcessor(array &$processors, string $route)
125
    {
126
        $values = [];
127
128
        foreach ($processors as $pattern => $processor) {
129
            // may be it is static route?
130
            if (strpos($pattern, '[') === false) {
131
                // it is static route, so skip it
132
                continue;
133
            }
134
135
            $regExPattern = $this->_getRouteMatcherRegExPattern($pattern);
136
137
            // try match
138
            if (preg_match('/^' . str_replace('/', '\\/', $regExPattern) . '$/', $route, $values)) {
139
                // fetch parameter names
140
                $names = $this->_getParameterNames($pattern);
141
142
                $this->parameters = [];
143
                foreach ($names as $i => $name) {
144
                    $this->parameters[$name] = $values[$i + 1];
145
                }
146
147
                return $processor;
148
            }
149
        }
150
151
        // match was not found
152
        return false;
153
    }
154
155
    /**
156
     * Method searches dynamic route processor
157
     *
158
     * @param array $processors
159
     *            Callable router's processor
160
     * @param string $route
161
     *            Route
162
     * @return string|bool Result of the router'scall or false if any error occured
163
     */
164
    public function findDynamicRouteProcessor(array &$processors, string $route)
165
    {
166
        $processor = $this->getDynamicRouteProcessor($processors, $route);
167
168
        if ($processor !== false) {
169
            return call_user_func($processor, $route, $this->parameters);
0 ignored issues
show
It seems like $processor can also be of type true; however, parameter $function of call_user_func() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

169
            return call_user_func(/** @scrutinizer ignore-type */ $processor, $route, $this->parameters);
Loading history...
170
        }
171
172
        return false;
173
    }
174
175
    /**
176
     * Checking that method exists
177
     *
178
     * @param mixed $processor
179
     *            callback object
180
     * @param ?string $functionName
181
     *            callback method
182
     * @return bool true if method does not exists
183
     */
184
    private function methodDoesNotExists($processor, ?string $functionName): bool
185
    {
186
        return isset($processor[0]) && method_exists($processor[0], $functionName) === false;
187
    }
188
189
    /**
190
     * Checking that handler can be called
191
     *
192
     * @param object|array|callable $processor
193
     *            callback object
194
     * @param ?string $functionName
195
     *            callback method
196
     * @return bool
197
     */
198
    private function canBeCalled($processor, ?string $functionName): bool
199
    {
200
        return is_callable($processor) &&
201
            (method_exists($processor[0], $functionName) || isset($processor[0]->$functionName));
202
    }
203
204
    /**
205
     * Checking that processor can be called as function
206
     *
207
     * @param mixed $processor
208
     *            route processor
209
     * @return bool true if the $processor can be called as function
210
     */
211
    private function isFunction($processor): bool
212
    {
213
        return is_callable($processor) && is_array($processor) === false;
214
    }
215
216
    /**
217
     * Method returns either universal hanler if it fits or normal handler
218
     *
219
     * @param array $processors
220
     *            list of routes and handlers
221
     * @param string $route
222
     *            calling route
223
     * @return mixed processor
224
     */
225
    protected function getExactRouteHandlerOrUniversal(&$processors, string $route)
226
    {
227
        if ($this->universalRouteWasAdded) {
228
            $allRoutes = array_keys($processors);
229
230
            if (array_search('*', $allRoutes) <= array_search($route, $allRoutes)) {
231
                $processor = $processors['*'];
232
            } else {
233
                $processor = $processors[$route];
234
            }
235
        } else {
236
            $processor = $processors[$route];
237
        }
238
239
        return $processor;
240
    }
241
242
    /**
243
     * Method executes route handler
244
     *
245
     * @param mixed $processor
246
     * @param string $route
247
     * @return mixed route handler execution result
248
     */
249
    protected function executeHandler($processor, string $route)
250
    {
251
        if ($this->isFunction($processor)) {
252
            return $processor($route, []);
253
        }
254
255
        $functionName = $processor[1] ?? null;
256
257
        if ($this->canBeCalled($processor, $functionName)) {
258
            // passing route path and parameters
259
            return call_user_func($processor, $route, []);
260
        } else {
261
            $callableDescription = \Mezon\Router\Utils::getCallableDescription($processor);
262
263
            if ($this->methodDoesNotExists($processor, $functionName)) {
264
                throw (new \Exception("'$callableDescription' does not exists"));
265
            } else {
266
                throw (new \Exception("'$callableDescription' must be callable entity"));
267
            }
268
        }
269
    }
270
271
    /**
272
     * Method returns route handler
273
     *
274
     * @param mixed $processors
275
     *            Callable router's processor
276
     * @param string $route
277
     *            Route
278
     * @return array|callable|bool route handler
279
     */
280
    protected function getStaticRouteProcessor(&$processors, string $route)
281
    {
282
        if (isset($processors[$route])) {
283
            $processor = $this->getExactRouteHandlerOrUniversal($processors, $route);
284
        } elseif (isset($processors['*'])) {
285
            $processor = $processors['*'];
286
        } else {
287
            return false;
288
        }
289
290
        return $processor;
291
    }
292
293
    /**
294
     * Method searches route processor
295
     *
296
     * @param mixed $processors
297
     *            Callable router's processor
298
     * @param string $route
299
     *            Route
300
     * @return mixed Result of the router processor
301
     */
302
    public function findStaticRouteProcessor(&$processors, string $route)
303
    {
304
        $processor = $this->getStaticRouteProcessor($processors, $route);
305
306
        if ($processor === false) {
307
            return false;
308
        }
309
310
        return $this->executeHandler($processor, $route);
311
    }
312
313
    /**
314
     * Method returns route parameter
315
     *
316
     * @param string $name
317
     *            Route parameter
318
     * @return string Route parameter
319
     */
320
    public function getParam(string $name): string
321
    {
322
        if (isset($this->parameters[$name]) === false) {
323
            throw (new \Exception('Parameter ' . $name . ' was not found in route', - 1));
324
        }
325
326
        return $this->parameters[$name];
327
    }
328
329
    /**
330
     * Does parameter exists
331
     *
332
     * @param string $name
333
     *            Param name
334
     * @return bool True if the parameter exists
335
     */
336
    public function hasParam(string $name): bool
337
    {
338
        return isset($this->parameters[$name]);
339
    }
340
341
    /**
342
     * Getting route by name
343
     *
344
     * @param string $routeName
345
     *            route's name
346
     * @return string route
347
     */
348
    public abstract function getRouteByName(string $routeName): string;
349
350
    /**
351
     * Compiling route into URL
352
     *
353
     * @param string $routeName
354
     *            route name
355
     * @param array $parameters
356
     *            parameters to use in URL
357
     * @return string compiled route
358
     */
359
    public function reverse(string $routeName, array $parameters = []): string
360
    {
361
        $route = $this->getRouteByName($routeName);
362
363
        foreach ($parameters as $name => $value) {
364
            $route = preg_replace('/\[([A-Za-z_\-])\:' . $name . ']/', $value, $route);
365
        }
366
367
        return $route;
368
    }
369
}
370