Passed
Push — master ( 4cfdc5...37c7d0 )
by Alex
07:00
created

SimpleUrlParser   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 396
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 49
eloc 104
c 1
b 0
f 1
dl 0
loc 396
rs 8.48

17 Methods

Rating   Name   Duplication   Size   Complexity  
A isFunction() 0 3 2
A findUniversalRouteProcessor() 0 9 2
A methodDoesNotExists() 0 3 3
A getUniversalRouteProcessor() 0 10 2
A getStaticRouteProcessor() 0 10 2
A reverse() 0 9 2
B getMiddlewareResult() 0 38 9
A canBeCalled() 0 5 4
A findDynamicRouteProcessor() 0 9 2
A _getRouteMatcherRegExPattern() 0 13 2
A getParam() 0 7 2
A _getParameterNames() 0 20 3
A executeHandler() 0 19 4
A hasParam() 0 3 1
A getDynamicRouteProcessor() 0 23 5
A findStaticRouteProcessor() 0 9 2
A registerMiddleware() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like SimpleUrlParser 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 SimpleUrlParser, 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 SimpleUrlParser
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
     * Middleware for routes processing
25
     *
26
     * @var array
27
     */
28
    private $middleware = [];
29
30
    /**
31
     * Method compiles route pattern string in regex string.
32
     * For example [i:id]/some-str in ([\[0-9\]])/some-str
33
     *
34
     * @param string $routerPattern
35
     *            router pattern
36
     * @return string regexp pattern
37
     */
38
    private function _getRouteMatcherRegExPattern(string $routerPattern): string
39
    {
40
        // parsing routes
41
        $compiledRouterPattern = $routerPattern;
42
43
        foreach ($this->types as $typeClass) {
44
            $compiledRouterPattern = preg_replace(
45
                '/' . $typeClass::searchRegExp() . '/',
46
                '(' . $typeClass::parserRegExp() . ')',
47
                $compiledRouterPattern);
48
        }
49
50
        return str_replace('/', '\/', $compiledRouterPattern);
51
    }
52
53
    /**
54
     * Method returns all parameter names in the route
55
     *
56
     * @param string $routerPattern
57
     *            route
58
     * @return array names
59
     */
60
    private function _getParameterNames(string $routerPattern): array
61
    {
62
        $regExPattern = [];
63
64
        foreach (array_keys($this->types) as $typeName) {
65
            $regExPattern[] = $typeName;
66
        }
67
68
        $regExPattern = '\[(' . implode('|', $regExPattern) . '):(' . BaseType::PARAMETER_NAME_REGEXP . ')\]';
69
70
        $names = [];
71
        preg_match_all('/' . str_replace('/', '\\/', $regExPattern) . '/', $routerPattern, $names);
72
73
        $return = [];
74
75
        foreach ($names[2] as $name) {
76
            $return[] = $name;
77
        }
78
79
        return $return;
80
    }
81
82
    /**
83
     * Method searches dynamic route processor
84
     *
85
     * @param string $route
86
     *            Route
87
     * @param string $requestMethod
88
     *            Request method
89
     * @return array|callable|bool route's handler or false in case the handler was not found
90
     */
91
    protected function getDynamicRouteProcessor(string $route, string $requestMethod = '')
92
    {
93
        $routes = $this->paramRoutes[$requestMethod == '' ? $_SERVER['REQUEST_METHOD'] : $requestMethod];
94
95
        foreach ($routes as $item) {
96
            $matches = [];
97
98
            if (preg_match('/^'.$this->_getRouteMatcherRegExPattern($item['pattern']).'$/', $route, $matches)) {
99
                $names = $this->_getParameterNames($item['pattern']);
100
101
                $this->parameters = [];
102
                foreach ($names as $i => $name) {
103
                    $this->parameters[$name] = $matches[(int) $i + 1];
104
                }
105
106
                $this->calledRoute = $item['pattern'];
107
108
                return $item['callback'];
109
            }
110
        }
111
112
        // match was not found
113
        return false;
114
    }
115
116
    /**
117
     * Method searches dynamic route processor
118
     *
119
     * @param string $route
120
     *            Route
121
     * @return string|bool Result of the router'scall or false if any error occured
122
     */
123
    public function findDynamicRouteProcessor(string $route)
124
    {
125
        $processor = $this->getDynamicRouteProcessor($route);
126
127
        if ($processor === false) {
128
            return false;
129
        }
130
131
        return $this->executeHandler($processor, $route);
132
    }
133
134
    /**
135
     * Checking that method exists
136
     *
137
     * @param mixed $processor
138
     *            callback object
139
     * @param ?string $functionName
140
     *            callback method
141
     * @return bool true if method does not exists
142
     */
143
    private function methodDoesNotExists($processor, ?string $functionName): bool
144
    {
145
        return $functionName === null || (isset($processor[0]) && method_exists($processor[0], $functionName) === false);
146
    }
147
148
    /**
149
     * Checking that handler can be called
150
     *
151
     * @param object|array|callable $processor
152
     *            callback object
153
     * @param ?string $functionName
154
     *            callback method
155
     * @return bool
156
     * @psalm-suppress InvalidArrayAccess
157
     */
158
    private function canBeCalled($processor, ?string $functionName): bool
159
    {
160
        return is_callable($processor) &&
161
            ($functionName !== null &&
162
            (method_exists($processor[0], $functionName) || isset($processor[0]->$functionName)));
163
    }
164
165
    /**
166
     * Checking that processor can be called as function
167
     *
168
     * @param mixed $processor
169
     *            route processor
170
     * @return bool true if the $processor can be called as function
171
     */
172
    private function isFunction($processor): bool
173
    {
174
        return is_callable($processor) && is_array($processor) === false;
175
    }
176
177
    /**
178
     * Method registeres middleware for the router
179
     *
180
     * @param callable $middleware
181
     *            middleware
182
     */
183
    public function registerMiddleware(string $router, callable $middleware): void
184
    {
185
        $routerTrimmed = trim($router, '/');
186
187
        if (! isset($this->middleware[$routerTrimmed])) {
188
            $this->middleware[$routerTrimmed] = [];
189
        }
190
191
        $this->middleware[$routerTrimmed][] = $middleware;
192
    }
193
194
    /**
195
     * Method returns middleware processing result
196
     *
197
     * @param string $route
198
     *            processed route
199
     * @return array middleware result
200
     */
201
    private function getMiddlewareResult(string $route): array
202
    {
203
        $middleWares = [];
204
205
        if (isset($this->middleware['*'])) {
206
            $middleWares = $this->middleware['*'];
207
        }
208
209
        if ($this->calledRoute !== '*' && isset($this->middleware[$this->calledRoute])) {
210
            $middleWares = array_merge($middleWares, $this->middleware[$this->calledRoute]);
211
        }
212
213
        $result = [
214
            $route,
215
            $this->parameters
216
        ];
217
218
        if (! count($middleWares)) {
219
            return $result;
220
        }
221
222
        foreach ($middleWares as $middleWare) {
223
            $result = call_user_func($middleWare, $route, $this->parameters);
224
225
            if (is_array($result)) {
226
                if (array_key_exists(0, $result)) {
227
                    $route = $result[0];
228
                }
229
230
                if (array_key_exists(1, $result)) {
231
                    $this->parameters = $result[1];
232
                }
233
            }
234
        }
235
236
        return [
237
            $route,
238
            $this->parameters
239
        ];
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 call_user_func_array($processor, $this->getMiddlewareResult($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_array($processor, $this->getMiddlewareResult($route));
260
        } else {
261
            $callableDescription = Utils::getCallableDescription($processor);
262
263
            if ($this->methodDoesNotExists($processor, $functionName)) {
264
                throw (new \Exception("'$callableDescription' does not exists"));
265
            } else {
266
                // @codeCoverageIgnoreStart
267
                throw (new \Exception("'$callableDescription' must be callable entity"));
268
                // @codeCoverageIgnoreEnd
269
            }
270
        }
271
    }
272
273
    /**
274
     * Method returns route handler
275
     *
276
     * @param string $route
277
     *            Route
278
     * @return array|callable|bool route handler
279
     */
280
    protected function getStaticRouteProcessor(string $route)
281
    {
282
        $processors = $this->staticRoutes[$_SERVER['REQUEST_METHOD']];
283
284
        if (isset($processors[$route])) {
285
            $this->calledRoute = $route;
286
287
            return $processors[$route];
288
        } else {
289
            return false;
290
        }
291
    }
292
293
    /**
294
     * Method returns route handler
295
     *
296
     * @return array|callable|bool route handler
297
     */
298
    protected function getUniversalRouteProcessor()
299
    {
300
        $processors = $this->staticRoutes[$_SERVER['REQUEST_METHOD']];
301
302
        if (isset($processors['*'])) {
303
            $this->calledRoute = '*';
304
305
            return $processors['*'];
306
        } else {
307
            return false;
308
        }
309
    }
310
311
    /**
312
     * Method searches route processor
313
     *
314
     * @param string $route
315
     *            Route
316
     * @return mixed Result of the router processor
317
     */
318
    public function findStaticRouteProcessor(string $route)
319
    {
320
        $processor = $this->getStaticRouteProcessor($route);
321
322
        if ($processor === false) {
323
            return false;
324
        }
325
326
        return $this->executeHandler($processor, $route);
327
    }
328
329
    /**
330
     * Method searches universal route processor
331
     *
332
     * @param string $route
333
     *            Route
334
     * @return mixed Result of the router processor
335
     */
336
    public function findUniversalRouteProcessor(string $route)
337
    {
338
        $processor = $this->getUniversalRouteProcessor();
339
340
        if ($processor === false) {
341
            return false;
342
        }
343
344
        return $this->executeHandler($processor, $route);
345
    }
346
347
    /**
348
     * Method returns route parameter
349
     *
350
     * @param string $name
351
     *            Route parameter
352
     * @return string Route parameter
353
     */
354
    public function getParam(string $name): string
355
    {
356
        if (isset($this->parameters[$name]) === false) {
357
            throw (new \Exception('Parameter ' . $name . ' was not found in route', - 1));
358
        }
359
360
        return $this->parameters[$name];
361
    }
362
363
    /**
364
     * Does parameter exists
365
     *
366
     * @param string $name
367
     *            Param name
368
     * @return bool True if the parameter exists
369
     */
370
    public function hasParam(string $name): bool
371
    {
372
        return isset($this->parameters[$name]);
373
    }
374
375
    /**
376
     * Getting route by name
377
     *
378
     * @param string $routeName
379
     *            route's name
380
     * @return string route
381
     */
382
    public abstract function getRouteByName(string $routeName): string;
383
384
    /**
385
     * Compiling route into URL
386
     *
387
     * @param string $routeName
388
     *            route name
389
     * @param array $parameters
390
     *            parameters to use in URL
391
     * @return string compiled route
392
     */
393
    public function reverse(string $routeName, array $parameters = []): string
394
    {
395
        $route = $this->getRouteByName($routeName);
396
397
        foreach ($parameters as $name => $value) {
398
            $route = preg_replace('/\[([A-Za-z_\-])\:' . $name . ']/', $value, $route);
399
        }
400
401
        return $route;
402
    }
403
}
404