Completed
Push — master ( b2545f...e44389 )
by Divine Niiquaye
02:25
created

Preloader::doPreload()   F

Complexity

Conditions 18
Paths 432

Size

Total Lines 49
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
eloc 27
nc 432
nop 2
dl 0
loc 49
rs 1.4887
c 0
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 BiuradPHP opensource projects.
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 Biurad\Cache;
19
20
use ErrorException;
21
use LogicException;
22
use ReflectionClass;
23
use ReflectionException;
24
use ReflectionMethod;
25
use ReflectionNamedType;
26
use ReflectionProperty;
27
28
class Preloader
29
{
30
    private const PRELOAD_KEY_CACHE = 'preload_statistics';
31
32
    /**
33
     * Append an array of classes to preload.
34
     *
35
     * @param string        $file
36
     * @param array<string> $list
37
     */
38
    public static function append(string $file, array $list): void
39
    {
40
        if (!\file_exists($file)) {
41
            throw new LogicException(\sprintf('File "%s" does not exist.', $file));
42
        }
43
44
        $cacheDir = \dirname($file);
45
        $classes  = [];
46
47
        foreach ($list as $item) {
48
            if (0 === \strpos($item, $cacheDir)) {
49
                \file_put_contents(
50
                    $file,
51
                    \sprintf("require_once __DIR__.%s;\n", \var_export(\substr($item, \strlen($cacheDir)), true)),
52
                    \FILE_APPEND
53
                );
54
55
                continue;
56
            }
57
58
            $classes[] = \sprintf("\$classes[] = %s;\n", \var_export($item, true));
59
        }
60
61
        \file_put_contents(
62
            $file,
63
            \sprintf("\n\$classes = [];\n%sPreloader::preload(\$classes);\n", \implode('', $classes)),
64
            \FILE_APPEND
65
        );
66
    }
67
68
    /**
69
     * Gives some information's about opcache preloading.
70
     *
71
     * @param string $type of 'functions', 'scripts' or 'classes'
72
     *
73
     * @return null|array<string,mixed>
74
     */
75
    public static function getStatus(string $type): ?array
76
    {
77
        $opcacheStatus = (array) \opcache_get_status();
78
79
        return $opcacheStatus[self::PRELOAD_KEY_CACHE][$type] ?? null;
80
    }
81
82
    /**
83
     * Returns the opcache preload statistics
84
     *
85
     * @return null|array<string,mixed>
86
     */
87
    public static function getStatistics(): ?array
88
    {
89
        $opcacheStatus = (array) \opcache_get_status();
90
91
        return $opcacheStatus[self::PRELOAD_KEY_CACHE] ?? null;
92
    }
93
94
    /**
95
     * @param array<string> $classes
96
     */
97
    public static function preload(array $classes): void
98
    {
99
        \set_error_handler([__CLASS__, 'errorHandler']);
100
101
        $prev      = [];
102
        $preloaded = [];
103
104
        try {
105
            while ($prev !== $classes) {
106
                $prev = $classes;
107
108
                foreach ($classes as $c) {
109
                    if (!isset($preloaded[$c])) {
110
                        self::doPreload($c, $preloaded);
111
                    }
112
                }
113
                $classes = \array_merge(
114
                    \get_declared_classes(),
115
                    \get_declared_interfaces(),
116
                    \get_declared_traits() ?? []
117
                );
118
            }
119
        } finally {
120
            \restore_error_handler();
121
        }
122
    }
123
124
    /**
125
     * @param string             $class
126
     * @param array<string,bool> $preloaded
127
     *
128
     * @psalm-suppress ArgumentTypeCoercion
129
     */
130
    private static function doPreload(string $class, array &$preloaded): void
131
    {
132
        if (isset($preloaded[$class]) || \in_array($class, ['self', 'static', 'parent'], true)) {
133
            return;
134
        }
135
136
        $preloaded[$class] = true;
137
138
        try {
139
            $r = new ReflectionClass($class);
140
141
            if ($r->isInternal()) {
142
                return;
143
            }
144
145
            $r->getConstants();
146
            $r->getDefaultProperties();
147
148
            if (\PHP_VERSION_ID >= 70400) {
149
                foreach ($r->getProperties(ReflectionProperty::IS_PUBLIC) as $p) {
150
                    if (null !== ($t = $p->getType()) && !$t->isBuiltin()) {
0 ignored issues
show
Bug introduced by
The method getType() does not exist on ReflectionProperty. ( Ignorable by Annotation )

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

150
                    if (null !== ($t = $p->/** @scrutinizer ignore-call */ getType()) && !$t->isBuiltin()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
151
                        \assert($t instanceof ReflectionNamedType);
152
                        self::doPreload($t->getName(), $preloaded);
153
                    }
154
                }
155
            }
156
157
            foreach ($r->getMethods(ReflectionMethod::IS_PUBLIC) as $m) {
158
                foreach ($m->getParameters() as $p) {
159
                    if ($p->isDefaultValueAvailable() && $p->isDefaultValueConstant()) {
160
                        $c = (string) $p->getDefaultValueConstantName();
161
162
                        if ($i = \strpos($c, '::')) {
163
                            self::doPreload(\substr($c, 0, $i), $preloaded);
164
                        }
165
                    }
166
167
                    if (null !== ($t = $p->getType()) && !$t->isBuiltin()) {
168
                        \assert($t instanceof ReflectionNamedType);
169
                        self::doPreload($t->getName(), $preloaded);
170
                    }
171
                }
172
173
                if (null !== ($t = $m->getReturnType()) && !$t->isBuiltin()) {
174
                    \assert($t instanceof ReflectionNamedType);
175
                    self::doPreload($t->getName(), $preloaded);
176
                }
177
            }
178
        } catch (ReflectionException $e) {
179
            // ignore missing classes
180
        }
181
    }
182
183
    private static function errorHandler(int $type, string $message, string $file, int $line): bool
184
    {
185
        if (\error_reporting() & $type) {
186
            if (__FILE__ !== $file) {
187
                throw new ErrorException($message, 0, $type, $file, $line);
188
            }
189
190
            throw new ReflectionException($message);
191
        }
192
193
        return false;
194
    }
195
}
196