Completed
Push — master ( 46045d...6c2bdc )
by Alex
02:01
created

UrlParser   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 426
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 116
dl 0
loc 426
rs 6.96
c 2
b 0
f 1
wmc 53

17 Methods

Rating   Name   Duplication   Size   Complexity  
A methodDoesNotExists() 0 3 2
A canBeCalled() 0 4 3
A getExactRouteHandlerOrUniversal() 0 18 3
A isFunction() 0 3 2
A executeHandler() 0 18 4
A getMiddlewareResult() 0 25 6
A registerMiddleware() 0 9 2
A _getRouteMatcherRegExPattern() 0 22 6
A getDynamicRouteProcessor() 0 31 6
A _getParameterNames() 0 26 4
A getStaticRouteProcessor() 0 13 3
A hasParam() 0 3 1
A findDynamicRouteProcessor() 0 9 2
A warmCache() 0 9 3
A reverse() 0 9 2
A findStaticRouteProcessor() 0 9 2
A getParam() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like UrlParser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use UrlParser, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Mezon\Router;
3
4
use Mezon\Router\Types\BaseType;
5
6
trait UrlParser
7
{
8
9
    /**
10
     * Parsed parameters of the calling router
11
     *
12
     * @var array
13
     */
14
    protected $parameters = [];
15
16
    /**
17
     * Called route
18
     *
19
     * @var string
20
     */
21
    protected $calledRoute = '';
22
23
    /**
24
     * Cache for regular expressions
25
     *
26
     * @var array
27
     */
28
    private $cachedRegExps = [];
29
30
    /**
31
     * Cached parameters for route
32
     *
33
     * @var array
34
     */
35
    private $cachedParameters = [];
36
37
    /**
38
     * Middleware for routes processing
39
     *
40
     * @var array
41
     */
42
    private $middleware = [];
43
44
    /**
45
     * Method compiles route pattern string in regex string.
46
     * For example [i:id]/some-str in ([\[0-9\]])/some-str
47
     *
48
     * @param string $routerPattern
49
     *            router pattern
50
     * @return string regexp pattern
51
     */
52
    private function _getRouteMatcherRegExPattern(string $routerPattern, bool $addBraces = true): string
53
    {
54
        $key = $routerPattern . ($addBraces ? '1' : '0');
55
56
        // try read from cache
57
        if (isset($this->cachedRegExps[$key])) {
58
            return $this->cachedRegExps[$key];
59
        }
60
61
        // parsing routes
62
        $compiledRouterPattern = $routerPattern;
63
        foreach ($this->types as $typeClass) {
64
            $compiledRouterPattern = preg_replace(
65
                '/' . $typeClass::searchRegExp() . '/',
66
                ($addBraces ? '(' : '') . $typeClass::parserRegExp() . ($addBraces ? ')' : ''),
67
                $compiledRouterPattern);
68
        }
69
70
        // final setup + save in cache
71
        $this->cachedRegExps[$key] = $compiledRouterPattern;
72
73
        return $compiledRouterPattern;
74
    }
75
76
    /**
77
     * Method returns all parameter names in the route
78
     *
79
     * @param string $routerPattern
80
     *            route
81
     * @return array names
82
     */
83
    private function _getParameterNames(string $routerPattern): array
84
    {
85
        if (isset($this->cachedParameters[$routerPattern])) {
86
            return $this->cachedParameters[$routerPattern];
87
        }
88
89
        $regExPattern = [];
90
91
        foreach (array_keys($this->types) as $typeName) {
92
            $regExPattern[] = $typeName;
93
        }
94
95
        $regExPattern = '\[(' . implode('|', $regExPattern) . '):(' . BaseType::PARAMETER_NAME_REGEXP . ')\]';
96
97
        $names = [];
98
        preg_match_all('/' . str_replace('/', '\\/', $regExPattern) . '/', $routerPattern, $names);
99
100
        $return = [];
101
102
        foreach ($names[2] as $name) {
103
            $return[] = $name;
104
        }
105
106
        $this->cachedParameters[$routerPattern] = $return;
107
108
        return $return;
109
    }
110
111
    /**
112
     * Method warms cache
113
     */
114
    public function warmCache(): void
115
    {
116
        foreach (self::getListOfSupportedRequestMethods() as $requestMethod) {
117
            $routesForMethod = $this->paramRoutes[$requestMethod];
118
119
            foreach (array_keys($routesForMethod) as $routerPattern) {
120
                $this->_getRouteMatcherRegExPattern($routerPattern);
121
122
                $this->_getParameterNames($routerPattern);
123
            }
124
        }
125
    }
126
127
    /**
128
     * Method searches dynamic route processor
129
     *
130
     * @param string $route
131
     *            Route
132
     * @param string $requestMethod
133
     *            Request method
134
     * @return array|callable|bool route's handler or false in case the handler was not found
135
     */
136
    protected function getDynamicRouteProcessor(string $route, string $requestMethod = '')
137
    {
138
        $bunches = $this->paramRoutes[$requestMethod == '' ? $_SERVER['REQUEST_METHOD'] : $requestMethod];
139
140
        foreach ($bunches as $bunch) {
141
            $matches = [];
142
143
            if (preg_match($bunch['regexp'], $route, $matches)) {
144
                $routeData = $bunch['bunch'][count($matches)];
145
                $regExPattern = $this->_getRouteMatcherRegExPattern($routeData['pattern']);
146
147
                // try match
148
                $values = [];
149
                if (preg_match('/^' . str_replace('/', '\\/', $regExPattern) . '$/', $route, $values)) {
150
                    // fetch parameter names
151
                    $names = $this->_getParameterNames($routeData['pattern']);
152
153
                    $this->parameters = [];
154
                    foreach ($names as $i => $name) {
155
                        $this->parameters[$name] = $values[$i + 1];
156
                    }
157
158
                    $this->calledRoute = $routeData['pattern'];
159
160
                    return $routeData['callback'];
161
                }
162
            }
163
        }
164
165
        // match was not found
166
        return false;
167
    }
168
169
    /**
170
     * Method searches dynamic route processor
171
     *
172
     * @param string $route
173
     *            Route
174
     * @return string|bool Result of the router'scall or false if any error occured
175
     */
176
    public function findDynamicRouteProcessor(string $route)
177
    {
178
        $processor = $this->getDynamicRouteProcessor($route);
179
180
        if ($processor === false) {
181
            return false;
182
        }
183
184
        return $this->executeHandler($processor, $route);
185
    }
186
187
    /**
188
     * Checking that method exists
189
     *
190
     * @param mixed $processor
191
     *            callback object
192
     * @param ?string $functionName
193
     *            callback method
194
     * @return bool true if method does not exists
195
     */
196
    private function methodDoesNotExists($processor, ?string $functionName): bool
197
    {
198
        return isset($processor[0]) && method_exists($processor[0], $functionName) === false;
199
    }
200
201
    /**
202
     * Checking that handler can be called
203
     *
204
     * @param object|array|callable $processor
205
     *            callback object
206
     * @param ?string $functionName
207
     *            callback method
208
     * @return bool
209
     */
210
    private function canBeCalled($processor, ?string $functionName): bool
211
    {
212
        return is_callable($processor) &&
213
            (method_exists($processor[0], $functionName) || isset($processor[0]->$functionName));
214
    }
215
216
    /**
217
     * Checking that processor can be called as function
218
     *
219
     * @param mixed $processor
220
     *            route processor
221
     * @return bool true if the $processor can be called as function
222
     */
223
    private function isFunction($processor): bool
224
    {
225
        return is_callable($processor) && is_array($processor) === false;
226
    }
227
228
    /**
229
     * Method returns either universal hanler if it fits or normal handler
230
     *
231
     * @param array $processors
232
     *            list of routes and handlers
233
     * @param string $route
234
     *            calling route
235
     * @return mixed processor
236
     */
237
    protected function getExactRouteHandlerOrUniversal(&$processors, string $route)
238
    {
239
        $this->calledRoute = $route;
240
241
        if ($this->universalRouteWasAdded) {
242
            $allRoutes = array_keys($processors);
243
244
            if (array_search('*', $allRoutes) <= array_search($route, $allRoutes)) {
245
                $processor = $processors['*'];
246
                $this->calledRoute = '*';
247
            } else {
248
                $processor = $processors[$route];
249
            }
250
        } else {
251
            $processor = $processors[$route];
252
        }
253
254
        return $processor;
255
    }
256
257
    /**
258
     * Method registeres middleware for the router
259
     *
260
     * @param callable $middleware
261
     *            middleware
262
     */
263
    public function registerMiddleware(string $router, callable $middleware): void
264
    {
265
        $routerTrimmed = trim($router, '/');
266
267
        if (!isset($this->middleware[$routerTrimmed])) {
268
            $this->middleware[$routerTrimmed] = [];
269
        }
270
271
        $this->middleware[$routerTrimmed][] = $middleware;
272
    }
273
274
    /**
275
     * Method returns middleware processing result
276
     *
277
     * @param string $route
278
     *            processed route
279
     * @return array middleware result
280
     */
281
    private function getMiddlewareResult(string $route): array
282
    {
283
        $middleWares = $this->middleware[$this->calledRoute] ?? null;
284
285
        $result = [$route, $this->parameters];
286
287
        if (!isset($middleWares)) {
288
            return $result;
289
        }
290
291
        foreach ($middleWares as $middleWare) {
292
            $result = call_user_func($middleWare, $route, $this->parameters);
293
294
            if (is_array($result)) {
295
                if (array_key_exists(0, $result)) {
296
                    $route = $result[0];
297
                }
298
299
                if (array_key_exists(1, $result)) {
300
                    $this->parameters = $result[1];
301
                }
302
            }
303
        }
304
305
        return [$route, $this->parameters];
306
    }
307
308
    /**
309
     * Method executes route handler
310
     *
311
     * @param mixed $processor
312
     * @param string $route
313
     * @return mixed route handler execution result
314
     */
315
    protected function executeHandler($processor, string $route)
316
    {
317
        if ($this->isFunction($processor)) {
318
            return call_user_func_array($processor, $this->getMiddlewareResult($route));
319
        }
320
321
        $functionName = $processor[1] ?? null;
322
323
        if ($this->canBeCalled($processor, $functionName)) {
324
            // passing route path and parameters
325
            return call_user_func_array($processor, $this->getMiddlewareResult($route));
326
        } else {
327
            $callableDescription = Utils::getCallableDescription($processor);
328
329
            if ($this->methodDoesNotExists($processor, $functionName)) {
330
                throw (new \Exception("'$callableDescription' does not exists"));
331
            } else {
332
                throw (new \Exception("'$callableDescription' must be callable entity"));
333
            }
334
        }
335
    }
336
337
    /**
338
     * Method returns route handler
339
     *
340
     * @param string $route
341
     *            Route
342
     * @return array|callable|bool route handler
343
     */
344
    protected function getStaticRouteProcessor(string $route)
345
    {
346
        $processors = $this->staticRoutes[$_SERVER['REQUEST_METHOD']];
347
348
        if (isset($processors[$route])) {
349
            $processor = $this->getExactRouteHandlerOrUniversal($processors, $route);
350
        } elseif (isset($processors['*'])) {
351
            $processor = $processors['*'];
352
        } else {
353
            return false;
354
        }
355
356
        return $processor;
357
    }
358
359
    /**
360
     * Method searches route processor
361
     *
362
     * @param string $route
363
     *            Route
364
     * @return mixed Result of the router processor
365
     */
366
    public function findStaticRouteProcessor(string $route)
367
    {
368
        $processor = $this->getStaticRouteProcessor($route);
369
370
        if ($processor === false) {
371
            return false;
372
        }
373
374
        return $this->executeHandler($processor, $route);
375
    }
376
377
    /**
378
     * Method returns route parameter
379
     *
380
     * @param string $name
381
     *            Route parameter
382
     * @return string Route parameter
383
     */
384
    public function getParam(string $name): string
385
    {
386
        if (isset($this->parameters[$name]) === false) {
387
            throw (new \Exception('Parameter ' . $name . ' was not found in route', - 1));
388
        }
389
390
        return $this->parameters[$name];
391
    }
392
393
    /**
394
     * Does parameter exists
395
     *
396
     * @param string $name
397
     *            Param name
398
     * @return bool True if the parameter exists
399
     */
400
    public function hasParam(string $name): bool
401
    {
402
        return isset($this->parameters[$name]);
403
    }
404
405
    /**
406
     * Getting route by name
407
     *
408
     * @param string $routeName
409
     *            route's name
410
     * @return string route
411
     */
412
    public abstract function getRouteByName(string $routeName): string;
413
414
    /**
415
     * Compiling route into URL
416
     *
417
     * @param string $routeName
418
     *            route name
419
     * @param array $parameters
420
     *            parameters to use in URL
421
     * @return string compiled route
422
     */
423
    public function reverse(string $routeName, array $parameters = []): string
424
    {
425
        $route = $this->getRouteByName($routeName);
426
427
        foreach ($parameters as $name => $value) {
428
            $route = preg_replace('/\[([A-Za-z_\-])\:' . $name . ']/', $value, $route);
429
        }
430
431
        return $route;
432
    }
433
}
434