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

UrlParser   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 412
Duplicated Lines 0 %

Importance

Changes 10
Bugs 1 Features 4
Metric Value
eloc 105
c 10
b 1
f 4
dl 0
loc 412
rs 8.8
wmc 45

17 Methods

Rating   Name   Duplication   Size   Complexity  
A executeHandler() 0 18 4
A methodDoesNotExists() 0 3 2
A getMiddlewareResult() 0 8 2
A _getRouteMatcherRegExPattern() 0 20 3
A getDynamicRouteProcessor() 0 31 5
A _getParameterNames() 0 26 4
A registerMiddleware() 0 3 1
A getStaticRouteProcessor() 0 11 3
A hasParam() 0 3 1
A findDynamicRouteProcessor() 0 9 2
A canBeCalled() 0 4 3
A warmCache() 0 15 4
A reverse() 0 9 2
A getExactRouteHandlerOrUniversal() 0 18 3
A findStaticRouteProcessor() 0 9 2
A isFunction() 0 3 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
trait UrlParser
5
{
6
7
    /**
8
     * Parsed parameters of the calling router
9
     *
10
     * @var array
11
     */
12
    protected $parameters = [];
13
14
    /**
15
     * Called route
16
     *
17
     * @var string
18
     */
19
    protected $calledRoute = '';
20
21
    /**
22
     * Cache for regular expressions
23
     *
24
     * @var array
25
     */
26
    private $cachedRegExps = [];
27
28
    /**
29
     * Cached parameters for route
30
     *
31
     * @var array
32
     */
33
    private $cachedParameters = [];
34
35
    /**
36
     * Middleware for routes processing
37
     *
38
     * @var array
39
     */
40
    private $middleware = [];
41
42
    /**
43
     * Method compiles route pattern string in regex string.
44
     * For example [i:id]/some-str in ([\[0-9\]])/some-str
45
     *
46
     * @param string $routerPattern
47
     *            router pattern
48
     * @return string regexp pattern
49
     */
50
    private function _getRouteMatcherRegExPattern(string $routerPattern): string
51
    {
52
        // try read from cache
53
        if (isset($this->cachedRegExps[$routerPattern])) {
54
            return $this->cachedRegExps[$routerPattern];
55
        }
56
57
        // parsing routes
58
        $compiledRouterPattern = $routerPattern;
59
        foreach ($this->types as $typeClass) {
60
            $compiledRouterPattern = preg_replace(
61
                '/' . $typeClass::searchRegExp() . '/',
62
                $typeClass::parserRegExp(),
63
                $compiledRouterPattern);
64
        }
65
66
        // final setup + save in cache
67
        $this->cachedRegExps[$routerPattern] = $compiledRouterPattern;
68
69
        return $compiledRouterPattern;
70
    }
71
72
    /**
73
     * Method returns all parameter names in the route
74
     *
75
     * @param string $routerPattern
76
     *            route
77
     * @return array names
78
     */
79
    private function _getParameterNames(string $routerPattern): array
80
    {
81
        if (isset($this->cachedParameters[$routerPattern])) {
82
            return $this->cachedParameters[$routerPattern];
83
        }
84
85
        $regExPattern = [];
86
87
        foreach (array_keys($this->types) as $typeName) {
88
            $regExPattern[] = $typeName;
89
        }
90
91
        $regExPattern = '\[(' . implode('|', $regExPattern) . '):([a-zA-Z0-9_\-]+)\]';
92
93
        $names = [];
94
        preg_match_all('/' . str_replace('/', '\\/', $regExPattern) . '/', $routerPattern, $names);
95
96
        $return = [];
97
98
        foreach ($names[2] as $name) {
99
            $return[] = $name;
100
        }
101
102
        $this->cachedParameters[$routerPattern] = $return;
103
104
        return $return;
105
    }
106
107
    /**
108
     * Method warms cache
109
     */
110
    public function warmCache(): void
111
    {
112
        foreach (self::getListOfSupportedRequestMethods() as $requestMethod) {
113
            $routesForMethod = $this->getRoutesForMethod($requestMethod);
0 ignored issues
show
Bug introduced by
It seems like getRoutesForMethod() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

113
            /** @scrutinizer ignore-call */ 
114
            $routesForMethod = $this->getRoutesForMethod($requestMethod);
Loading history...
114
115
            foreach (array_keys($routesForMethod) as $routerPattern) {
116
                // may be it is static route?
117
                if (strpos($routerPattern, '[') === false) {
118
                    // it is static route, so skip it
119
                    continue;
120
                }
121
122
                $this->_getRouteMatcherRegExPattern($routerPattern);
123
124
                $this->_getParameterNames($routerPattern);
125
            }
126
        }
127
    }
128
129
    /**
130
     * Method searches dynamic route processor
131
     *
132
     * @param array $processors
133
     *            Callable router's processor
134
     * @param string $route
135
     *            Route
136
     * @return array|callable|bool route's handler or false in case the handler was not found
137
     */
138
    protected function getDynamicRouteProcessor(array &$processors, string $route)
139
    {
140
        $values = [];
141
142
        foreach ($processors as $pattern => $processor) {
143
            // may be it is static route?
144
            if (strpos($pattern, '[') === false) {
145
                // it is static route, so skip it
146
                continue;
147
            }
148
149
            $regExPattern = $this->_getRouteMatcherRegExPattern($pattern);
150
151
            // try match
152
            if (preg_match('/^' . str_replace('/', '\\/', $regExPattern) . '$/', $route, $values)) {
153
                // fetch parameter names
154
                $names = $this->_getParameterNames($pattern);
155
156
                $this->parameters = [];
157
                foreach ($names as $i => $name) {
158
                    $this->parameters[$name] = $values[$i + 1];
159
                }
160
161
                $this->calledRoute = $pattern;
162
163
                return $processor;
164
            }
165
        }
166
167
        // match was not found
168
        return false;
169
    }
170
171
    /**
172
     * Method searches dynamic route processor
173
     *
174
     * @param array $processors
175
     *            Callable router's processor
176
     * @param string $route
177
     *            Route
178
     * @return string|bool Result of the router'scall or false if any error occured
179
     */
180
    public function findDynamicRouteProcessor(array &$processors, string $route)
181
    {
182
        $processor = $this->getDynamicRouteProcessor($processors, $route);
183
184
        if ($processor === false) {
185
            return false;
186
        }
187
188
        return $this->executeHandler($processor, $route);
189
    }
190
191
    /**
192
     * Checking that method exists
193
     *
194
     * @param mixed $processor
195
     *            callback object
196
     * @param ?string $functionName
197
     *            callback method
198
     * @return bool true if method does not exists
199
     */
200
    private function methodDoesNotExists($processor, ?string $functionName): bool
201
    {
202
        return isset($processor[0]) && method_exists($processor[0], $functionName) === false;
203
    }
204
205
    /**
206
     * Checking that handler can be called
207
     *
208
     * @param object|array|callable $processor
209
     *            callback object
210
     * @param ?string $functionName
211
     *            callback method
212
     * @return bool
213
     */
214
    private function canBeCalled($processor, ?string $functionName): bool
215
    {
216
        return is_callable($processor) &&
217
            (method_exists($processor[0], $functionName) || isset($processor[0]->$functionName));
218
    }
219
220
    /**
221
     * Checking that processor can be called as function
222
     *
223
     * @param mixed $processor
224
     *            route processor
225
     * @return bool true if the $processor can be called as function
226
     */
227
    private function isFunction($processor): bool
228
    {
229
        return is_callable($processor) && is_array($processor) === false;
230
    }
231
232
    /**
233
     * Method returns either universal hanler if it fits or normal handler
234
     *
235
     * @param array $processors
236
     *            list of routes and handlers
237
     * @param string $route
238
     *            calling route
239
     * @return mixed processor
240
     */
241
    protected function getExactRouteHandlerOrUniversal(&$processors, string $route)
242
    {
243
        $this->calledRoute = $route;
244
245
        if ($this->universalRouteWasAdded) {
246
            $allRoutes = array_keys($processors);
247
248
            if (array_search('*', $allRoutes) <= array_search($route, $allRoutes)) {
249
                $processor = $processors['*'];
250
                $this->calledRoute = '*';
251
            } else {
252
                $processor = $processors[$route];
253
            }
254
        } else {
255
            $processor = $processors[$route];
256
        }
257
258
        return $processor;
259
    }
260
261
    /**
262
     * Method registeres middleware for the router
263
     *
264
     * @param callable $middleware
265
     *            middleware
266
     */
267
    public function registerMiddleware(string $router, callable $middleware): void
268
    {
269
        $this->middleware[trim($router, '/')] = $middleware;
270
    }
271
272
    /**
273
     * Method returns middleware processing result
274
     *
275
     * @param string $route
276
     *            processed route
277
     * @return array middleware result
278
     */
279
    private function getMiddlewareResult(string $route): array
280
    {
281
        return isset($this->middleware[$this->calledRoute]) ? call_user_func(
282
            $this->middleware[$this->calledRoute],
283
            $route,
284
            $this->parameters) : [
285
                $route,
286
                $this->parameters
287
            ];
288
    }
289
290
    /**
291
     * Method executes route handler
292
     *
293
     * @param mixed $processor
294
     * @param string $route
295
     * @return mixed route handler execution result
296
     */
297
    protected function executeHandler($processor, string $route)
298
    {
299
        if ($this->isFunction($processor)) {
300
            return call_user_func_array($processor, $this->getMiddlewareResult($route));
301
        }
302
303
        $functionName = $processor[1] ?? null;
304
305
        if ($this->canBeCalled($processor, $functionName)) {
306
            // passing route path and parameters
307
            return call_user_func_array($processor, $this->getMiddlewareResult($route));
308
        } else {
309
            $callableDescription = \Mezon\Router\Utils::getCallableDescription($processor);
310
311
            if ($this->methodDoesNotExists($processor, $functionName)) {
312
                throw (new \Exception("'$callableDescription' does not exists"));
313
            } else {
314
                throw (new \Exception("'$callableDescription' must be callable entity"));
315
            }
316
        }
317
    }
318
319
    /**
320
     * Method returns route handler
321
     *
322
     * @param mixed $processors
323
     *            Callable router's processor
324
     * @param string $route
325
     *            Route
326
     * @return array|callable|bool route handler
327
     */
328
    protected function getStaticRouteProcessor(&$processors, string $route)
329
    {
330
        if (isset($processors[$route])) {
331
            $processor = $this->getExactRouteHandlerOrUniversal($processors, $route);
332
        } elseif (isset($processors['*'])) {
333
            $processor = $processors['*'];
334
        } else {
335
            return false;
336
        }
337
338
        return $processor;
339
    }
340
341
    /**
342
     * Method searches route processor
343
     *
344
     * @param mixed $processors
345
     *            Callable router's processor
346
     * @param string $route
347
     *            Route
348
     * @return mixed Result of the router processor
349
     */
350
    public function findStaticRouteProcessor(&$processors, string $route)
351
    {
352
        $processor = $this->getStaticRouteProcessor($processors, $route);
353
354
        if ($processor === false) {
355
            return false;
356
        }
357
358
        return $this->executeHandler($processor, $route);
359
    }
360
361
    /**
362
     * Method returns route parameter
363
     *
364
     * @param string $name
365
     *            Route parameter
366
     * @return string Route parameter
367
     */
368
    public function getParam(string $name): string
369
    {
370
        if (isset($this->parameters[$name]) === false) {
371
            throw (new \Exception('Parameter ' . $name . ' was not found in route', - 1));
372
        }
373
374
        return $this->parameters[$name];
375
    }
376
377
    /**
378
     * Does parameter exists
379
     *
380
     * @param string $name
381
     *            Param name
382
     * @return bool True if the parameter exists
383
     */
384
    public function hasParam(string $name): bool
385
    {
386
        return isset($this->parameters[$name]);
387
    }
388
389
    /**
390
     * Getting route by name
391
     *
392
     * @param string $routeName
393
     *            route's name
394
     * @return string route
395
     */
396
    public abstract function getRouteByName(string $routeName): string;
397
398
    /**
399
     * Compiling route into URL
400
     *
401
     * @param string $routeName
402
     *            route name
403
     * @param array $parameters
404
     *            parameters to use in URL
405
     * @return string compiled route
406
     */
407
    public function reverse(string $routeName, array $parameters = []): string
408
    {
409
        $route = $this->getRouteByName($routeName);
410
411
        foreach ($parameters as $name => $value) {
412
            $route = preg_replace('/\[([A-Za-z_\-])\:' . $name . ']/', $value, $route);
413
        }
414
415
        return $route;
416
    }
417
}
418