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

RoutesSet::compileRegexpForBunches()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 5
nc 4
nop 0
dl 0
loc 10
rs 10
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 = 10;
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
        $bunch['regexp'] = '';
68
69
        if (! empty($bunch['bunch'])) {
70
            $items = [];
71
72
            foreach ($bunch['bunch'] as $i => $route) {
73
                $items[] = $this->_getRouteMatcherRegExPattern($route['pattern'], false) .
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

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