Passed
Pull Request — master (#13)
by Divine Niiquaye
02:26
created

DumperTrait   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 156
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 23
eloc 74
c 2
b 0
f 0
dl 0
loc 156
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
C export() 0 44 12
A generateCompiledRoutes() 0 31 4
A exportRoute() 0 18 3
A exportMatcher() 0 28 4
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\RouteMatcherInterface;
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 Route) {
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 Route) {
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 Route $route
82
     *
83
     * @return string
84
     */
85
    protected static function exportRoute(Route $route): string
86
    {
87
        $properties = $route->getAll();
88
89
        if (!\is_string($controller = $properties['controller'])) {
90
            $properties['controller'] = \sprintf('unserialize(\'%s\')', \serialize($controller));
91
        }
92
93
        if (isset($properties['defaults']['_compiler'])) {
94
            $properties['defaults']['_compiler'] = \sprintf(
95
                'unserialize(\'%s\')',
96
                \serialize($properties['defaults']['_compiler'])
97
            );
98
        }
99
100
        $exported = self::export($properties);
101
102
        return \sprintf('%s::__set_state(%s)', Route::class, $exported);
103
    }
104
105
    /**
106
     * Export the matcher, this method can be override if
107
     * RouteMatcherInterfaqce implementation changes.
108
     *
109
     * @param mixed                 $compiledRoutes
110
     * @param RouteMatcherInterface $matcher
111
     *
112
     * @return string
113
     */
114
    protected function exportMatcher($compiledRoutes, RouteMatcherInterface $matcher): string
115
    {
116
        $code = '';
117
118
        if ($matcher instanceof SimpleRouteMatcher) {
119
            [$staticRoutes, $dynamicRoutes] = $compiledRoutes;
120
            $code .= '[ // $staticRoutes' . "\n";
121
122
            foreach ($staticRoutes as $path => $route) {
123
                $code .= \sprintf('    %s => ', self::export($path));
124
                $code .= self::export($route);
125
                $code .= ", \n";
126
            }
127
            $code .= "],\n";
128
129
            $code .= '[ // $dynamicRoutes' . "\n";
130
131
            foreach ($dynamicRoutes as $name => $route) {
132
                $code .= \sprintf('    %s => ', self::export($name));
133
                $code .= self::export($route);
134
                $code .= ", \n";
135
            }
136
            $code .= "],\n";
137
        } else {
138
            $code .= self::export($compiledRoutes);
139
        }
140
141
        return $code;
142
    }
143
144
    /**
145
     * Warm up routes to speed up routes handling.
146
     *
147
     * @internal
148
     *
149
     * @param string                $cacheFile
150
     * @param RouteMatcherInterface $matcher
151
     */
152
    private function generateCompiledRoutes(string $cacheFile, RouteMatcherInterface $matcher): void
153
    {
154
        if (false === $compiledRoutes = $matcher->getCompiledRoutes()) {
155
            return;
156
        }
157
158
        $generatedCode = (string) \preg_replace(
159
            '/^./m',
160
            \str_repeat('    ', 1) . '$0',
161
            $this->exportMatcher($compiledRoutes, $matcher)
162
        );
163
164
        $dumpCode = <<<EOF
165
<?php
166
167
/**
168
 * This file has been auto-generated by the Flight Routing.
169
 */
170
171
return [
172
{$generatedCode}];
173
174
EOF;
175
176
        \file_put_contents($cacheFile, $dumpCode);
177
178
        if (
179
            \function_exists('opcache_invalidate') &&
180
            \filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)
181
        ) {
182
            @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

182
            /** @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...
183
        }
184
    }
185
}
186