Passed
Push — master ( 17d331...42ece1 )
by Alex
02:01
created

RoutesSet::getAllRoutesTrace()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 33
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 20
nc 9
nop 0
dl 0
loc 33
rs 8.6666
c 0
b 0
f 0
1
<?php
2
namespace Mezon\Router;
3
4
trait RoutesSet
5
{
6
7
    /**
8
     * List of static routes for all supported request methods
9
     *
10
     * @var array
11
     */
12
    protected $staticRoutes = [
13
        'GET' => [],
14
        'POST' => [],
15
        'PUT' => [],
16
        'DELETE' => [],
17
        'OPTION' => [],
18
        'PATCH' => []
19
    ];
20
21
    /**
22
     * List of non static routes
23
     *
24
     * @var array
25
     */
26
    protected $paramRoutes = [
27
        'GET' => [],
28
        'POST' => [],
29
        'PUT' => [],
30
        'DELETE' => [],
31
        'OPTION' => [],
32
        'PATCH' => []
33
    ];
34
35
    /**
36
     * Bunch size
37
     *
38
     * @var integer
39
     */
40
    private $bunchSize = 100;
41
42
    /**
43
     * Generating appendix for the RegExp
44
     *
45
     * @param int $i
46
     *            count of ()
47
     * @return string appendix
48
     */
49
    private function getRegExpAppendix(int $i): string
50
    {
51
        $return = '';
52
53
        for ($n = 0; $n <= $i; $n ++) {
54
            $return .= '()';
55
        }
56
57
        return $return;
58
    }
59
60
    /**
61
     * Method compiles regexp for the bunch of routes
62
     *
63
     * @param array $bunch
64
     */
65
    protected function compileRegexpForBunch(array &$bunch): void
66
    {
67
        if (! empty($bunch['bunch'])) {
68
            $bunch['regexp'] = '';
69
            $hashTable = [];
70
            $items = [];
71
            $previousIndex = 0;
72
73
            foreach ($bunch['bunch'] as $route) {
74
                $vars = $this->_getParameterNames($route['pattern']);
0 ignored issues
show
Bug introduced by
It seems like _getParameterNames() 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

74
                /** @scrutinizer ignore-call */ 
75
                $vars = $this->_getParameterNames($route['pattern']);
Loading history...
75
                $routeMatcher = $this->_getRouteMatcherRegExPattern($route['pattern']);
0 ignored issues
show
Bug introduced by
It seems like _getRouteMatcherRegExPattern() 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

75
                /** @scrutinizer ignore-call */ 
76
                $routeMatcher = $this->_getRouteMatcherRegExPattern($route['pattern']);
Loading history...
76
                $currentIndex = count($vars) + 1;
77
                if (isset($hashTable[$currentIndex])) {
78
                    $items[] = $routeMatcher . $this->getRegExpAppendix($previousIndex - $currentIndex);
79
                    $currentIndex = $previousIndex + 1;
80
                    $hashTable[$currentIndex] = $route;
81
                } else {
82
                    $items[] = $routeMatcher;
83
                    $hashTable[$currentIndex] = $route;
84
                }
85
                $previousIndex = $currentIndex;
86
            }
87
88
            $bunch['regexp'] = '~^(?|' . implode('|', $items) . ')$~';
89
            $bunch['bunch'] = $hashTable;
90
        }
91
    }
92
93
    /**
94
     * Were regexps compiled?
95
     */
96
    private $regExpsWereCompiled = false;
97
98
    /**
99
     * Method compiles all regeps for all routes
100
     */
101
    protected function compileRegexpForBunches(): void
102
    {
103
        if (! $this->regExpsWereCompiled) {
104
            foreach (self::getListOfSupportedRequestMethods() as $requestMethod) {
105
                foreach ($this->paramRoutes[$requestMethod] as &$bunch) {
106
                    $this->compileRegexpForBunch($bunch);
107
                }
108
            }
109
110
            $this->regExpsWereCompiled = true;
111
        }
112
    }
113
114
    /**
115
     * Method adds param router
116
     *
117
     * @param string $requestMethod
118
     * @param string $route
119
     * @param mixed $callback
120
     */
121
    protected function addParamRoute(string $requestMethod, string $route, $callback): void
122
    {
123
        if (empty($this->paramRoutes[$requestMethod])) {
124
            $this->paramRoutes[$requestMethod] = [
125
                [
126
                    'bunch' => []
127
                ]
128
            ];
129
        }
130
131
        $bunchCursor = count($this->paramRoutes[$requestMethod]) - 1;
132
133
        $lastBunchSize = count($this->paramRoutes[$requestMethod][$bunchCursor]['bunch']);
134
135
        if ($lastBunchSize == $this->bunchSize) {
136
            $this->paramRoutes[$requestMethod][] = [
137
                'bunch' => []
138
            ];
139
            $bunchCursor ++;
140
            $lastBunchSize = 0;
141
        }
142
143
        $this->paramRoutes[$requestMethod][$bunchCursor]['bunch'][$lastBunchSize + 1] = [
144
            'pattern' => $route,
145
            'callback' => $callback
146
        ];
147
    }
148
149
    /**
150
     * Route names
151
     *
152
     * @var array
153
     */
154
    private $routeNames = [];
155
156
    /**
157
     * Method validates request method
158
     *
159
     * @param string $requestMethod
160
     *            HTTP request method
161
     */
162
    protected function validateRequestMethod(string $requestMethod): void
163
    {
164
        if (isset($this->staticRoutes[$requestMethod]) === false) {
165
            throw (new \Exception('Unsupported request method'));
166
        }
167
    }
168
169
    /**
170
     * Method returns a list of supported request methods
171
     *
172
     * @return array list of supported request methods
173
     */
174
    public static function getListOfSupportedRequestMethods(): array
175
    {
176
        return [
177
            'GET',
178
            'POST',
179
            'PUT',
180
            'DELETE',
181
            'OPTION',
182
            'PATCH'
183
        ];
184
    }
185
186
    /**
187
     * Method clears router data.
188
     */
189
    public function clear()
190
    {
191
        $this->routeNames = [];
192
193
        foreach (self::getListOfSupportedRequestMethods() as $requestMethod) {
194
            $this->staticRoutes[$requestMethod] = [];
195
            $this->paramRoutes[$requestMethod] = [];
196
        }
197
198
        $this->cachedRegExps = [];
0 ignored issues
show
Bug Best Practice introduced by
The property cachedRegExps does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
199
200
        $this->middleware = [];
0 ignored issues
show
Bug Best Practice introduced by
The property middleware does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
201
202
        $this->regExpsWereCompiled = false;
203
    }
204
205
    /**
206
     * Method returns true if the param router exists
207
     *
208
     * @param string $route
209
     *            checking route
210
     * @param string $requestMethod
211
     *            HTTP request method
212
     * @return bool true if the param router exists, false otherwise
213
     */
214
    private function paramRouteExists(string $route, string $requestMethod): bool
215
    {
216
        foreach ($this->paramRoutes[$requestMethod] as $bunch) {
217
            foreach ($bunch['bunch'] as $item) {
218
                if ($item['pattern'] === $route) {
219
                    return true;
220
                }
221
            }
222
        }
223
224
        return false;
225
    }
226
227
    /**
228
     * Method returns true if the router exists
229
     *
230
     * @param string $route
231
     *            checking route
232
     * @return bool true if the router exists, false otherwise
233
     */
234
    public function routeExists(string $route): bool
235
    {
236
        $this->compileRegexpForBunches();
237
238
        $route = trim($route, '/');
239
240
        foreach (self::getListOfSupportedRequestMethods() as $requestMethod) {
241
            if (isset($this->staticRoutes[$requestMethod][$route])) {
242
                return true;
243
            } else {
244
                if ($this->paramRouteExists($route, $requestMethod)) {
245
                    return true;
246
                }
247
            }
248
        }
249
250
        return false;
251
    }
252
253
    /**
254
     * Method rturns all available routes
255
     */
256
    public function getAllRoutesTrace()
257
    {
258
        $fullTrace = [];
259
260
        foreach (self::getListOfSupportedRequestMethods() as $requestMethod) {
261
            $trace = [
262
                $requestMethod . ' : '
263
            ];
264
            $hasRoutes = false;
265
            if (! empty($this->staticRoutes[$requestMethod])) {
266
                $trace[] = implode(', ', array_keys($this->staticRoutes[$requestMethod]));
267
                $trace[] = ', ';
268
                $hasRoutes = true;
269
            }
270
            if (! empty($this->paramRoutes[$requestMethod])) {
271
                foreach ($this->paramRoutes[$requestMethod] as $bunch) {
272
                    $items = [];
273
                    foreach ($bunch['bunch'] as $item) {
274
                        $items[] = $item['pattern'];
275
                        $hasRoutes = true;
276
                    }
277
                    $trace[] = implode(', ', $items);
278
                }
279
            }
280
281
            if (! $hasRoutes) {
282
                $trace[] = '<none>';
283
            }
284
285
            $fullTrace[] = implode('', $trace);
286
        }
287
288
        return implode('; ', $fullTrace);
289
    }
290
291
    /**
292
     * Method adds route and it's handler
293
     *
294
     * $callback function may have two parameters - $route and $parameters. Where $route is a called route,
295
     * and $parameters is associative array (parameter name => parameter value) with URL parameters
296
     *
297
     * @param string $route
298
     *            Route
299
     * @param mixed $callback
300
     *            Collback wich will be processing route call.
301
     * @param string|array $requestMethod
302
     *            Request type
303
     * @param string $routeName
304
     *            name of the route
305
     */
306
    public function addRoute(string $route, $callback, $requestMethod = 'GET', string $routeName = ''): void
307
    {
308
        $route = Utils::prepareRoute($route);
309
310
        if (is_array($requestMethod)) {
311
            foreach ($requestMethod as $r) {
312
                $this->addRoute($route, $callback, $r, $routeName);
313
            }
314
        } else {
315
            $this->validateRequestMethod($requestMethod);
316
317
            if (strpos($route, '[') === false) {
318
                $this->staticRoutes[$requestMethod][$route] = $callback;
319
            } else {
320
                $this->addParamRoute($requestMethod, $route, $callback);
321
            }
322
            // register route name
323
            $this->registerRouteName($routeName, $route);
324
        }
325
    }
326
327
    /**
328
     * Additing route for GET request
329
     *
330
     * @param string $route
331
     *            route
332
     * @param object $object
333
     *            callback object
334
     * @param string $method
335
     *            callback method
336
     */
337
    public function addGetRoute(string $route, object $object, string $method): void
338
    {
339
        $this->addRoute($route, [
340
            $object,
341
            $method
342
        ], 'GET');
343
    }
344
345
    /**
346
     * Additing route for GET request
347
     *
348
     * @param string $route
349
     *            route
350
     * @param object $object
351
     *            callback object
352
     * @param string $method
353
     *            callback method
354
     */
355
    public function addPostRoute(string $route, object $object, string $method): void
356
    {
357
        $this->addRoute($route, [
358
            $object,
359
            $method
360
        ], 'POST');
361
    }
362
363
    /**
364
     * Method registers name of the route
365
     *
366
     * @param string $routeName
367
     *            route's name
368
     * @param string $route
369
     *            route
370
     */
371
    protected function registerRouteName(string $routeName, string $route): void
372
    {
373
        if ($routeName != '') {
374
            $this->routeNames[$routeName] = $route;
375
        }
376
    }
377
378
    /**
379
     * Validating that route name exists
380
     *
381
     * @param string $routeName
382
     * @return bool
383
     */
384
    protected function routeNameExists(string $routeName): bool
385
    {
386
        return isset($this->routeNames[$routeName]);
387
    }
388
389
    /**
390
     * Getting route by name
391
     *
392
     * @param string $routeName
393
     *            route's name
394
     * @return string route
395
     */
396
    public function getRouteByName(string $routeName): string
397
    {
398
        if ($this->routeNameExists($routeName) === false) {
399
            throw (new \Exception('Route with name ' . $routeName . ' does not exist'));
400
        }
401
402
        return $this->routeNames[$routeName];
403
    }
404
405
    /**
406
     * Method dumps all routes and their names on disk
407
     *
408
     * @param string $filePath
409
     *            file path to cache
410
     * @codeCoverageIgnore
411
     */
412
    public function dumpOnDisk(string $filePath = './cache/cache.php'): void
413
    {
414
        $this->compileRegexpForBunches();
415
416
        file_put_contents($filePath, '<?php return ' . var_export([
417
            0 => $this->staticRoutes,
418
            1 => $this->paramRoutes,
419
            2 => $this->routeNames,
420
            3 => $this->cachedRegExps,
421
            4 => $this->cachedParameters,
422
            5 => $this->regExpsWereCompiled
423
        ], true) . ';');
424
    }
425
426
    /**
427
     * Method loads routes from disk
428
     *
429
     * @param string $filePath
430
     *            file path to cache
431
     * @codeCoverageIgnore
432
     */
433
    public function loadFromDisk(string $filePath = './cache/cache.php'): void
434
    {
435
        list ($this->staticRoutes, $this->paramRoutes, $this->routeNames, $this->cachedRegExps, $this->cachedParameters, $this->regExpsWereCompiled) = require ($filePath);
0 ignored issues
show
Bug Best Practice introduced by
The property cachedRegExps does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
Bug Best Practice introduced by
The property cachedParameters does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
436
    }
437
}
438