Test Failed
Push — master ( d3660e...c7a4a9 )
by Divine Niiquaye
10:08
created

DumperTrait::exportRoute()   B

Complexity

Conditions 9
Paths 8

Size

Total Lines 32
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 20
c 2
b 0
f 0
dl 0
loc 32
rs 8.0555
cc 9
nc 8
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Flight Routing.
7
 *
8
 * PHP version 7.1 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\Matchers\ExpressionCollection;
21
use Flight\Routing\Route;
22
23
/**
24
 * @codeCoverageIgnore
25
 */
26
trait DumperTrait
27
{
28
    /** @var bool */
29
    protected $export = true;
30
31
    protected static function indent(string $code, int $level = 1): string
32
    {
33
        return (string) \preg_replace('/^./m', \str_repeat('    ', $level) . '$0', $code);
34
    }
35
36
    /**
37
     * @internal
38
     *
39
     * @param mixed $value
40
     */
41
    protected static function export($value): string
42
    {
43
        if (null === $value) {
44
            return 'null';
45
        }
46
47
        if (!\is_array($value)) {
48
            if ($value instanceof Route) {
49
                return self::exportRoute($value);
50
            } elseif (\is_object($value)) {
51
                return \sprintf('unserialize(\'%s\')', \serialize($value));
52
            } elseif (is_string($value) && str_starts_with($value, 'static function ()')) {
53
                return $value;
54
            }
55
56
            return \str_replace("\n", '\'."\n".\'', \var_export($value, true));
57
        }
58
59
        if (!$value) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $value of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
60
            return '[]';
61
        }
62
63
        $i      = 0;
64
        $export = '[';
65
66
        foreach ($value as $k => $v) {
67
            if ($i === $k) {
68
                ++$i;
69
            } else {
70
                $export .= self::export($k) . ' => ';
71
72
                if (\is_int($k) && $i < $k) {
73
                    $i = 1 + $k;
74
                }
75
            }
76
77
            if (\is_string($v) && 0 === \strpos($v, 'unserialize')) {
78
                $v = '\\' . $v . ', ';
79
            } elseif ($v instanceof Route) {
80
                $v .= self::exportRoute($v);
81
            } elseif (\is_object($v)) {
82
                $v = \sprintf('unserialize(\'%s\'), ', \serialize($v));
83
            } else {
84
                $v = self::export($v) . ', ';
85
            }
86
87
            $export .= $v;
88
        }
89
90
        return \substr_replace($export, ']', -2);
0 ignored issues
show
Bug Best Practice introduced by
The expression return substr_replace($export, ']', -2) could return the type array which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
91
    }
92
93
    /**
94
     * @param Route $route
95
     *
96
     * @return string
97
     */
98
    protected static function exportRoute(Route $route): string
99
    {
100
        $properties = $route->getAll();
101
        $controller = $properties['controller'];
102
        $exported   = '';
103
104
        if ($controller instanceof \Closure) {
105
            $closureRef = new \ReflectionFunction($controller);
106
107
            if (empty($closureRef->getParameters()) && null === $closureRef->getClosureThis()) {
108
                \ob_start();
109
110
                $closureReturn = $controller();
111
                $closureEcho   = \ob_get_clean();
112
113
                $properties['controller'] = \sprintf(
114
                    "static function () {\n            %s %s;\n        }",
115
                    null !== $closureReturn ? 'return' : 'echo',
116
                    self::export($closureReturn ?? $closureEcho),
117
                );
118
            }
119
        } elseif (\is_object($controller) || (\is_array($controller) && \is_object($controller[0]))) {
120
            $properties['controller'] = \sprintf('unserialize(\'%s\')', \serialize($controller));
121
        }
122
123
        foreach ($properties as $key => $value) {
124
            $exported .= \sprintf('        %s => ', self::export($key));
125
            $exported .= self::export($value);
126
            $exported .= ",\n";
127
        }
128
129
        return "\Flight\Routing\Route::__set_state([\n{$exported}    ])";
130
    }
131
132
    /**
133
     * Compiles a regexp tree of subpatterns that matches nested same-prefix routes.
134
     *
135
     * @param array<string,string> $vars
136
     */
137
    private function compileExpressionCollection(ExpressionCollection $tree, int $prefixLen, array &$vars): string
138
    {
139
        $code   = '';
140
        $routes = $tree->getRoutes();
141
142
        foreach ($routes as $route) {
143
            if ($route instanceof ExpressionCollection) {
144
                $prefix = \substr($route->getPrefix(), $prefixLen);
145
                $rx     = "|{$prefix}(?";
146
147
                $regexpCode = $this->compileExpressionCollection($route, $prefixLen + \strlen($prefix), $vars);
148
149
                $code .= $this->export ? "\n        ." . self::export($rx) : $rx;
150
                $code .= $this->export ? self::indent($regexpCode) : $regexpCode;
151
                $code .= $this->export ? "\n        .')'" : ')';
152
153
                continue;
154
            }
155
            [$name, $regex, $variable] = $route;
156
157
            $rx = \sprintf('|%s(*:%s)', \substr($regex, $prefixLen), $name);
158
            $code .= $this->export ? "\n        ." . self::export($rx) : $rx;
159
160
            $vars[$name] = $variable;
161
        }
162
163
        return $code;
164
    }
165
166
    /**
167
     * Warm up routes to speed up routes handling.
168
     *
169
     * @internal
170
     */
171
    private function generateCompiledRoutes(): string
172
    {
173
        $code = '';
174
175
        [$staticRoutes, $dynamicRoutes, $regexList, $collection] = $this->getCompiledRoutes();
0 ignored issues
show
Bug introduced by
It seems like getCompiledRoutes() 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

175
        /** @scrutinizer ignore-call */ 
176
        [$staticRoutes, $dynamicRoutes, $regexList, $collection] = $this->getCompiledRoutes();
Loading history...
176
        $code .= '[ // $staticRoutes' . "\n";
177
178
        foreach ($staticRoutes as $path => $route) {
179
            $code .= \sprintf('    %s => ', self::export($path));
180
            $code .= self::export($route);
181
            $code .= ", \n";
182
        }
183
        $code .= "],\n";
184
185
        $code .= '[ // $dynamicRoutes' . "\n";
186
187
        foreach ($dynamicRoutes as $name => $route) {
188
            $code .= \sprintf('    %s => ', self::export($name));
189
            $code .= self::export($route);
190
            $code .= ", \n";
191
        }
192
        $code .= "],\n";
193
194
        [$regex, $variables] = $regexList;
195
196
        $regexpCode = "    {$regex},\n    [\n";
197
198
        foreach ($variables as $key => $value) {
199
            $regexpCode .= \sprintf('        %s => ', self::export($key));
200
            $regexpCode .= self::export($value);
201
            $regexpCode .= ",\n";
202
        }
203
204
        $code .= \sprintf("[ // \$regexpList\n%s    ],\n],\n", $regexpCode);
205
206
        $code .= '[ // $routeCollection' . "\n";
207
208
        foreach ($collection as $name => $route) {
209
            $code .= \sprintf('    %s => ', self::export($name));
210
            $code .= self::export($route);
211
            $code .= ", \n";
212
        }
213
        $code .= "],\n";
214
215
        $generatedCode = self::indent($code);
216
217
        return <<<EOF
218
<?php
219
220
/**
221
 * This file has been auto-generated by the Flight Routing.
222
 */
223
return [
224
{$generatedCode}];
225
226
EOF;
227
    }
228
}
229