1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/* |
6
|
|
|
* This file is part of Flight Routing. |
7
|
|
|
* |
8
|
|
|
* PHP version 7.4 and above required |
9
|
|
|
* |
10
|
|
|
* @author Divine Niiquaye Ibok <[email protected]> |
11
|
|
|
* @copyright 2019 Biurad Group (https://biurad.com/) |
12
|
|
|
* @license https://opensource.org/licenses/BSD-3-Clause License |
13
|
|
|
* |
14
|
|
|
* For the full copyright and license information, please view the LICENSE |
15
|
|
|
* file that was distributed with this source code. |
16
|
|
|
*/ |
17
|
|
|
|
18
|
|
|
namespace Flight\Routing\Traits; |
19
|
|
|
|
20
|
|
|
use Flight\Routing\Handlers\ResourceHandler; |
21
|
|
|
use Flight\Routing\RouteCollection; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* A default cache implementation for route match. |
25
|
|
|
* |
26
|
|
|
* @author Divine Niiquaye Ibok <[email protected]> |
27
|
|
|
*/ |
28
|
|
|
trait CacheTrait |
29
|
|
|
{ |
30
|
|
|
private ?string $cache = null; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @param string $path file path to store compiled routes |
34
|
|
|
*/ |
35
|
|
|
public function setCache(string $path): void |
36
|
|
|
{ |
37
|
|
|
$this->cache = $path; |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* A well php value formatter, better than (var_export). |
42
|
|
|
*/ |
43
|
|
|
public static function export(mixed $value, string $indent = ''): string |
44
|
|
|
{ |
45
|
|
|
switch (true) { |
46
|
|
|
case [] === $value: |
47
|
|
|
return '[]'; |
48
|
|
|
case \is_array($value): |
49
|
|
|
$j = -1; |
50
|
|
|
$code = ($t = \count($value, \COUNT_RECURSIVE)) > 15 ? "[\n" : '['; |
51
|
|
|
$subIndent = $t > 15 ? $indent.' ' : $indent = ''; |
52
|
|
|
|
53
|
|
|
foreach ($value as $k => $v) { |
54
|
|
|
$code .= $subIndent; |
55
|
|
|
|
56
|
|
|
if (!\is_int($k) || $k !== ++$j) { |
57
|
|
|
$code .= self::export($k, $subIndent).' => '; |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
$code .= self::export($v, $subIndent).($t > 15 ? ",\n" : ', '); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
return \rtrim($code, ', ').$indent.']'; |
64
|
|
|
case $value instanceof ResourceHandler: |
65
|
|
|
return $value::class.'('.self::export($value(''), $indent).')'; |
66
|
|
|
case $value instanceof \stdClass: |
67
|
|
|
return '(object) '.self::export((array) $value, $indent); |
68
|
|
|
case $value instanceof RouteCollection: |
69
|
|
|
return $value::class.'::__set_state('.self::export([ |
70
|
|
|
'routes' => $value->getRoutes(), |
71
|
|
|
'defaultIndex' => $value->count() - 1, |
72
|
|
|
'sorted' => true, |
73
|
|
|
], $indent).')'; |
74
|
|
|
case \is_object($value): |
75
|
|
|
if (\method_exists($value, '__set_state')) { |
76
|
|
|
return $value::class.'::__set_state('.self::export( |
77
|
|
|
\array_merge(...\array_map(function (\ReflectionProperty $v) use ($value): array { |
78
|
|
|
$v->setAccessible(true); |
79
|
|
|
|
80
|
|
|
return [$v->getName() => $v->getValue($value)]; |
81
|
|
|
}, (new \ReflectionObject($value))->getProperties())) |
82
|
|
|
); |
83
|
|
|
} |
84
|
|
|
return 'unserialize(\''.\serialize($value).'\')'; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
return \var_export($value, true); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
protected function doCache(): RouteCollection |
91
|
|
|
{ |
92
|
|
|
if (\is_array($a = @include $this->cache)) { |
93
|
|
|
$this->optimized = $a; |
|
|
|
|
94
|
|
|
|
95
|
|
|
return $this->optimized[2] ??= $this->collection ?? new RouteCollection(); |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
if (\is_callable($collection = $this->collection ?? new RouteCollection())) { |
99
|
|
|
$collection($collection = new RouteCollection()); |
100
|
|
|
$collection->sort(); |
101
|
|
|
$doCache = true; |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
if (!\is_dir($directory = \dirname($this->cache))) { |
|
|
|
|
105
|
|
|
@\mkdir($directory, 0775, true); |
|
|
|
|
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
try { |
109
|
|
|
return $collection; |
110
|
|
|
} finally { |
111
|
|
|
$dumpData = $this->buildCache($collection, $doCache ?? false); |
112
|
|
|
\file_put_contents($this->cache, "<?php // auto generated: AVOID MODIFYING\n\nreturn ".$dumpData.";\n"); |
|
|
|
|
113
|
|
|
|
114
|
|
|
if (\function_exists('opcache_invalidate') && \filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) { |
115
|
|
|
@\opcache_invalidate($this->cache, true); |
|
|
|
|
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
protected function buildCache(RouteCollection $collection, bool $doCache): string |
121
|
|
|
{ |
122
|
|
|
$dynamicRoutes = []; |
123
|
|
|
$this->optimized = [[], [[], []]]; |
|
|
|
|
124
|
|
|
|
125
|
|
|
foreach ($collection->getRoutes() as $i => $route) { |
126
|
|
|
$trimmed = \preg_replace('/\W$/', '', $path = $route['path']); |
127
|
|
|
|
128
|
|
|
if (\in_array($prefix = $route['prefix'] ?? '/', [$trimmed, $path], true)) { |
129
|
|
|
$this->optimized[0][$trimmed ?: '/'][] = $i; |
130
|
|
|
continue; |
131
|
|
|
} |
132
|
|
|
[$path, $var] = $this->getCompiler()->compile($path, $route['placeholders'] ?? []); |
|
|
|
|
133
|
|
|
$path = \str_replace('\/', '/', \substr($path, 1 + \strpos($path, '^'), -(\strlen($path) - \strrpos($path, '$')))); |
134
|
|
|
|
135
|
|
|
if (($l = \array_key_last($dynamicRoutes)) && !\in_array($l, ['/', $prefix], true)) { |
136
|
|
|
for ($o = 0, $new = ''; $o < \strlen($prefix); ++$o) { |
137
|
|
|
if ($prefix[$o] !== ($l[$o] ?? null)) { |
138
|
|
|
break; |
139
|
|
|
} |
140
|
|
|
$new .= $l[$o]; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
if ($new && '/' !== $new) { |
144
|
|
|
if ($l !== $new) { |
145
|
|
|
$dynamicRoutes[$new] = $dynamicRoutes[$l]; |
146
|
|
|
unset($dynamicRoutes[$l]); |
147
|
|
|
} |
148
|
|
|
$prefix = $new; |
149
|
|
|
} |
150
|
|
|
} |
151
|
|
|
$dynamicRoutes[$prefix][] = \preg_replace('#\?(?|P<\w+>|<\w+>|\'\w+\')#', '', $path)."(*:{$i})"; |
152
|
|
|
$this->optimized[1][1][$i] = $var; |
153
|
|
|
} |
154
|
|
|
\ksort($this->optimized[0], \SORT_NATURAL); |
155
|
|
|
\uksort($dynamicRoutes, fn (string $a, string $b): int => \in_array('/', [$a, $b], true) ? \strcmp($b, $a) : \strcmp($a, $b)); |
156
|
|
|
|
157
|
|
|
foreach ($dynamicRoutes as $offset => $paths) { |
158
|
|
|
$numParts = \max(1, \round(($c = \count($paths)) / 30)); |
159
|
|
|
|
160
|
|
|
foreach (\array_chunk($paths, (int) \ceil($c / $numParts)) as $chunk) { |
161
|
|
|
$this->optimized[1][0]['/'.\ltrim($offset, '/')][] = '~^(?|'.\implode('|', $chunk).')$~'; |
162
|
|
|
} |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
return self::export([...$this->optimized, $doCache ? $collection : null]); |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
|