Test Failed
Pull Request — master (#36)
by Divine Niiquaye
10:46 queued 08:19
created

CacheTrait::export()   C

Complexity

Conditions 14
Paths 27

Size

Total Lines 45
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 14
eloc 32
nc 27
nop 2
dl 0
loc 45
rs 6.2666
c 2
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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;
0 ignored issues
show
Bug Best Practice introduced by
The property optimized does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
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))) {
0 ignored issues
show
Bug introduced by
It seems like $this->cache can also be of type null; however, parameter $path of dirname() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

104
        if (!\is_dir($directory = \dirname(/** @scrutinizer ignore-type */ $this->cache))) {
Loading history...
105
            @\mkdir($directory, 0775, true);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). 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

105
            /** @scrutinizer ignore-unhandled */ @\mkdir($directory, 0775, 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...
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");
0 ignored issues
show
Bug introduced by
It seems like $this->cache can also be of type null; however, parameter $filename of file_put_contents() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

112
            \file_put_contents(/** @scrutinizer ignore-type */ $this->cache, "<?php // auto generated: AVOID MODIFYING\n\nreturn ".$dumpData.";\n");
Loading history...
113
114
            if (\function_exists('opcache_invalidate') && \filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
115
                @\opcache_invalidate($this->cache, 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

115
                /** @scrutinizer ignore-unhandled */ @\opcache_invalidate($this->cache, 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...
Bug introduced by
It seems like $this->cache can also be of type null; however, parameter $filename of opcache_invalidate() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

115
                @\opcache_invalidate(/** @scrutinizer ignore-type */ $this->cache, true);
Loading history...
116
            }
117
        }
118
    }
119
120
    protected function buildCache(RouteCollection $collection, bool $doCache): string
121
    {
122
        $dynamicRoutes = [];
123
        $this->optimized = [[], [[], []]];
0 ignored issues
show
Bug Best Practice introduced by
The property optimized does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
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'] ?? []);
0 ignored issues
show
Bug introduced by
It seems like getCompiler() 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

132
            [$path, $var] = $this->/** @scrutinizer ignore-call */ getCompiler()->compile($path, $route['placeholders'] ?? []);
Loading history...
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