Passed
Push — master ( 56c5da...22541f )
by Divine Niiquaye
03:34
created

DumperTrait::exportRoute()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 15
nc 2
nop 1
dl 0
loc 22
rs 9.7666
c 1
b 0
f 0
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\Interfaces\RouteInterface;
21
use Flight\Routing\Matchers\SimpleRouteMatcher;
22
use Flight\Routing\Route;
23
24
/**
25
 * @codeCoverageIgnore
26
 */
27
trait DumperTrait
28
{
29
    /**
30
     * @internal
31
     *
32
     * @param mixed $value
33
     */
34
    protected static function export($value): string
35
    {
36
        if (null === $value) {
37
            return 'null';
38
        }
39
40
        if (!\is_array($value)) {
41
            if ($value instanceof RouteInterface) {
42
                return self::exportRoute($value);
43
            }
44
45
            return \str_replace("\n", '\'."\n".\'', \var_export($value, true));
46
        }
47
48
        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...
49
            return '[]';
50
        }
51
52
        $i      = 0;
53
        $export = '[';
54
55
        foreach ($value as $k => $v) {
56
            if ($i === $k) {
57
                ++$i;
58
            } else {
59
                $export .= self::export($k) . ' => ';
60
61
                if (\is_int($k) && $i < $k) {
62
                    $i = 1 + $k;
63
                }
64
            }
65
66
            if (\is_string($v) && 0 === \strpos($v, 'unserialize')) {
67
                $v = '\\' . $v . ', ';
68
            } elseif ($v instanceof RouteInterface) {
69
                $v .= self::exportRoute($v);
70
            } else {
71
                $v = self::export($v) . ', ';
72
            }
73
74
            $export .= $v;
75
        }
76
77
        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...
78
    }
79
80
    /**
81
     * @param RouteInterface $route
82
     *
83
     * @return string
84
     */
85
    protected static function exportRoute(RouteInterface $route): string
86
    {
87
        $controller = $route->getController();
88
89
        if (!\is_string($controller)) {
90
            $controller = \sprintf('unserialize(\'%s\')', \serialize($controller));
91
        }
92
93
        $exported = self::export([
94
            $route->getName(),
95
            $route->getMethods(),
96
            $route->getPath(),
97
            $route->getSchemes(),
98
            $route->getDomain(),
99
            $controller,
100
            $route->getMiddlewares(),
101
            $route->getPatterns(),
102
            $route->getDefaults(),
103
            $route->getArguments(),
104
        ]);
105
106
        return \sprintf('%s::__set_state(%s)', Route::class, $exported);
107
    }
108
109
    /**
110
     * Export the matcher, this method can be override if
111
     * RouteMatcherInterfaqce implementation changes.
112
     *
113
     * @param mixed $compiledRoutes
114
     *
115
     * @return string
116
     */
117
    protected function exportMatcher($compiledRoutes): string
118
    {
119
        $code = '';
120
121
        if ($this->matcher instanceof SimpleRouteMatcher) {
122
            [$staticRoutes, $dynamicRoutes] = $compiledRoutes;
123
            $code .= '[ // $staticRoutes' . "\n";
124
125
            foreach ($staticRoutes as $path => $route) {
126
                $code .= \sprintf('    %s => ', self::export($path));
127
128
                if (\is_array($route)) {
129
                    $code .= \sprintf(
130
                        "[\n        %s,\n        %s\n    ]",
131
                        self::export(\current($route)),
132
                        \sprintf('\unserialize(\'%s\')', \serialize(\end($route)))
133
                    );
134
                } else {
135
                    $code .= self::export($route);
136
                }
137
                $code .= ", \n";
138
            }
139
            $code .= "],\n";
140
141
            $code .= '[ // $dynamicRoutes' . "\n";
142
143
            foreach ($dynamicRoutes as $name => $route) {
144
                $code .= \sprintf('    %s =>', self::export($name));
145
                $code .= \sprintf(
146
                    "[\n        %s,\n        %s\n    ], \n",
147
                    self::export(\current($route)),
148
                    \sprintf('\unserialize(\'%s\')', \serialize(\end($route)))
149
                );
150
            }
151
            $code .= "],\n";
152
        } elseif (null !== $compiledRoutes || false !== $compiledRoutes) {
153
            $code .= self::export($compiledRoutes);
154
        }
155
156
        return $code;
157
    }
158
159
    /**
160
     * Warm up routes to speed up routes handling.
161
     *
162
     * @internal
163
     *
164
     * @param string $cacheFile
165
     */
166
    private function generateCompiledRoutes(string $cacheFile): void
167
    {
168
        $compiledRoutes = $this->matcher->warmCompiler($this->getCollection());
0 ignored issues
show
Bug introduced by
It seems like getCollection() 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

168
        $compiledRoutes = $this->matcher->warmCompiler($this->/** @scrutinizer ignore-call */ getCollection());
Loading history...
169
170
        $generatedCode = (string) \preg_replace(
171
            '/^./m',
172
            \str_repeat('    ', 1) . '$0',
173
            $this->exportMatcher($compiledRoutes)
174
        );
175
176
        $dumpCode = <<<EOF
177
<?php
178
179
/**
180
 * This file has been auto-generated by the Flight Routing.
181
 */
182
183
return [
184
{$generatedCode}];
185
186
EOF;
187
188
        \file_put_contents($cacheFile, $dumpCode);
189
190
        if (
191
            \function_exists('opcache_invalidate') &&
192
            \filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)
193
        ) {
194
            @opcache_invalidate($cacheFile, true);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for opcache_invalidate(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

194
            /** @scrutinizer ignore-unhandled */ @opcache_invalidate($cacheFile, true);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
195
        }
196
    }
197
}
198