Completed
Push — master ( d60927...421821 )
by Divine Niiquaye
02:18
created

Preloader::errorHandler()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
c 1
b 0
f 0
nc 3
nop 4
dl 0
loc 11
rs 10
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 BiuradPHP\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 informations 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
     * Returrns 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(\get_declared_classes(), \get_declared_interfaces(), \get_declared_traits());
114
            }
115
        } finally {
116
            \restore_error_handler();
117
        }
118
    }
119
120
    /**
121
     * @param string             $class
122
     * @param array<string,bool> $preloaded
123
     */
124
    private static function doPreload(string $class, array &$preloaded): void
125
    {
126
        if (isset($preloaded[$class]) || \in_array($class, ['self', 'static', 'parent'], true)) {
127
            return;
128
        }
129
130
        $preloaded[$class] = true;
131
132
        try {
133
            $r = new ReflectionClass($class);
134
135
            if ($r->isInternal()) {
136
                return;
137
            }
138
139
            $r->getConstants();
140
            $r->getDefaultProperties();
141
142
            if (\PHP_VERSION_ID >= 70400) {
143
                foreach ($r->getProperties(ReflectionProperty::IS_PUBLIC) as $p) {
144
                    if (($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

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