PluginProbe
DecaLog / 4.4.0
DecaLog v4.4.0
3.0.2 3.1.0 3.10.0 3.2.0 3.3.0 3.4.0 3.4.1 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.6.3 3.7.0 3.7.1 3.8.0 3.9.0 3.9.1 4.0.0 4.1.0 4.2.0 4.3.0 4.3.1 4.4.0 4.5.0 All 75 releases
decalog / includes / system / class-opcache.php

class-opcache.php in DecaLog 4.4.0, at includes/system/class-opcache.php

306 lines 8.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OPcache handling
4 *
5 * Handles all OPcache operations and detection.
6 *
7 * @package System
8 * @author Pierre Lannoy <https://pierre.lannoy.fr/>.
9 * @since 1.0.0
10 */
11
12 namespace Decalog\System;
13
14 use Decalog\Logger;
15 use Decalog\System\Option;
16 use Decalog\System\File;
17
18 /**
19 * Define the OPcache functionality.
20 *
21 * Handles all OPcache operations and detection.
22 *
23 * @package System
24 * @author Pierre Lannoy <https://pierre.lannoy.fr/>.
25 * @since 1.0.0
26 */
27 class OPcache {
28
29 /**
30 * The list of status.
31 *
32 * @since 1.0.0
33 * @var array $status Maintains the status list.
34 */
35 public static $status = [ 'disabled', 'enabled', 'cache_full', 'restart_pending', 'restart_in_progress', 'recycle_in_progress', 'warmup', 'reset_warmup' ];
36
37 /**
38 * The list of reset types.
39 *
40 * @since 1.0.0
41 * @var array $status Maintains the status list.
42 */
43 public static $resets = [ 'none', 'oom', 'hash', 'manual' ];
44
45 /**
46 * The list of file not compilable/recompilable.
47 *
48 * @since 1.0.0
49 * @var array $status Maintains the file list.
50 */
51 public static $do_not_compile = [ 'includes/plugin.php', 'includes/options.php', 'includes/misc.php', 'includes/menu.php' ];
52
53 /**
54 * Initializes the class and set its properties.
55 *
56 * @since 1.0.0
57 */
58 public function __construct() {
59 }
60
61 /**
62 * Verify if OPcache API usage is restricted.
63 *
64 * @return boolean True if it is restricted, false otherwise.
65 * @since 1.0.0
66 */
67 public static function is_restricted() {
68 // phpcs:ignore
69 set_error_handler( null );
70 // phpcs:ignore
71 $test = @opcache_get_configuration();
72 // phpcs:ignore
73 restore_error_handler();
74 return ! is_array( $test );
75 }
76
77 /**
78 * Get the options infos for Site Health "info" tab.
79 *
80 * @since 1.0.0
81 */
82 public static function debug_info() {
83 $result['product'] = [
84 'label' => 'Product',
85 'value' => self::name(),
86 ];
87 if ( function_exists( 'opcache_get_configuration' ) && function_exists( 'opcache_get_status' ) ) {
88 if ( ! self::is_restricted() ) {
89 // phpcs:ignore
90 $raw = @opcache_get_configuration();
91 if ( array_key_exists( 'directives', $raw ) ) {
92 foreach ( $raw['directives'] as $key => $directive ) {
93 $result[ 'directive_' . $key ] = [
94 'label' => '[Directive] ' . str_replace( 'opcache.', '', $key ),
95 'value' => $directive,
96 ];
97 }
98 }
99 $raw = opcache_get_status();
100 foreach ( $raw as $key => $status ) {
101 if ( 'scripts' === $key ) {
102 continue;
103 }
104 if ( is_array( $status ) ) {
105 foreach ( $status as $skey => $sstatus ) {
106 $result[ 'status_' . $skey ] = [
107 'label' => '[Status] ' . $skey,
108 'value' => $sstatus,
109 ];
110 }
111 } else {
112 $result[ 'status_' . $key ] = [
113 'label' => '[Status] ' . $key,
114 'value' => $status,
115 ];
116 }
117 }
118 } else {
119 $result['product'] = [
120 'label' => 'Status',
121 'value' => 'Unknown - OPcache API usage is restricted',
122 ];
123 }
124 } else {
125 $result['product'] = [
126 'label' => 'Status',
127 'value' => 'Disabled',
128 ];
129 }
130 return $result;
131 }
132
133 /**
134 * Get name and version.
135 *
136 * @return string The name and version of the product.
137 * @since 1.0.0
138 */
139 public static function name() {
140 $result = '';
141 if ( function_exists( 'opcache_get_configuration' ) ) {
142 if ( ! self::is_restricted() ) {
143 // phpcs:ignore
144 $raw = @opcache_get_configuration();
145 if ( array_key_exists( 'version', $raw ) ) {
146 if ( array_key_exists( 'opcache_product_name', $raw['version'] ) ) {
147 $result = $raw['version']['opcache_product_name'];
148 }
149 if ( array_key_exists( 'version', $raw['version'] ) ) {
150 $version = $raw['version']['version'];
151 if ( false !== strpos( $version, '-' ) ) {
152 $version = substr( $version, 0, strpos( $version, '-' ) );
153 }
154 $result .= ' ' . $version;
155 }
156 }
157 } else {
158 $result = '';
159 }
160 }
161 return $result;
162 }
163
164 /**
165 * Invalidate files.
166 *
167 * @param array $files List of files to invalidate.
168 * @param boolean $force Optional. Has the invalidation to be forced.
169 * @return integer The number of invalidated files.
170 * @since 1.0.0
171 */
172 public static function invalidate( $files, $force = false ) {
173 $cpt = 0;
174 if ( function_exists( 'opcache_invalidate' ) && ! self::is_restricted() ) {
175 $logger = new Logger( 'plugin', DECALOG_PRODUCT_NAME, DECALOG_VERSION );
176 foreach ( $files as $file ) {
177 if ( 0 === strpos( $file, './' ) ) {
178 $file = str_replace( '..', '', $file );
179 $file = str_replace( './', ABSPATH, $file );
180 if ( opcache_invalidate( $file, $force ) ) {
181 $cpt++;
182 }
183 }
184 }
185 if ( $force ) {
186 $s = 'Forced invalidation';
187 } else {
188 $s = 'Invalidation';
189 }
190 $logger->info( sprintf( '%s: %d file(s).', $s, $cpt ) );
191 }
192 return $cpt;
193 }
194
195 /**
196 * Recompile files.
197 *
198 * @param array $files List of files to recompile.
199 * @param boolean $force Optional. Has the invalidation to be forced.
200 * @return integer The number of recompiled files.
201 * @since 1.0.0
202 */
203 public static function recompile( $files, $force = false ) {
204 $cpt = 0;
205 if ( function_exists( 'opcache_invalidate' ) && function_exists( 'opcache_compile_file' ) && function_exists( 'opcache_is_script_cached' ) && ! self::is_restricted() ) {
206 $logger = new Logger( 'plugin', DECALOG_PRODUCT_NAME, DECALOG_VERSION );
207 foreach ( $files as $file ) {
208 if ( 0 === strpos( $file, './' ) ) {
209 foreach ( self::$do_not_compile as $item ) {
210 if ( false !== strpos( $file, $item ) ) {
211 $logger->debug( sprintf( 'File "%s" must not be recompiled.', $file ) );
212 continue 2;
213 }
214 }
215 $file = str_replace( '..', '', $file );
216 $file = str_replace( './', ABSPATH, $file );
217 if ( $force ) {
218 opcache_invalidate( $file, true );
219 }
220 if ( ! opcache_is_script_cached( $file ) ) {
221 try {
222 // phpcs:ignore
223 if ( @opcache_compile_file( $file ) ) {
224 $cpt++;
225 } else {
226 $logger->debug( sprintf( 'Unable to compile file "%s".', $file ) );
227 }
228 } catch ( \Throwable $e ) {
229 $logger->debug( sprintf( 'Unable to compile file "%s": %s.', $file, $e->getMessage() ), $e->getCode() );
230 }
231 } else {
232 $logger->debug( sprintf( 'File "%s" already cached.', $file ) );
233 }
234 }
235 }
236 $logger->info( sprintf( 'Recompilation: %d file(s).', $cpt ) );
237 }
238 return $cpt;
239 }
240
241 /**
242 * Reset the cache (force invalidate all).
243 *
244 * @param boolean $automatic Optional. Is the reset automatically done (via cron, for example).
245 * @since 1.0.0
246 */
247 public static function reset( $automatic = true ) {
248 if ( $automatic && Option::network_get( 'warmup', false ) ) {
249 self::warmup( $automatic, true );
250 } else {
251 $files = [];
252 if ( function_exists( 'opcache_get_status' ) && ! self::is_restricted() ) {
253 try {
254 $raw = opcache_get_status( true );
255 if ( array_key_exists( 'scripts', $raw ) ) {
256 foreach ( $raw['scripts'] as $script ) {
257 if ( false === strpos( $script['full_path'], ABSPATH ) ) {
258 continue;
259 }
260 $files[] = str_replace( ABSPATH, './', $script['full_path'] );
261 }
262 self::invalidate( $files, true );
263 }
264 } catch ( \Throwable $e ) {
265 $logger = new Logger( 'plugin', DECALOG_PRODUCT_NAME, DECALOG_VERSION );
266 $logger->error( sprintf( 'Unable to query OPcache status: %s.', $e->getMessage() ), $e->getCode() );
267 }
268 }
269 }
270 }
271
272 /**
273 * Warm-up the site.
274 *
275 * @param boolean $automatic Optional. Is the warmup done (via cron, for example).
276 * @param boolean $force Optional. Has invalidation to be forced.
277 * @return integer The number of recompiled files.
278 * @since 1.0.0
279 */
280 public static function warmup( $automatic = true, $force = false ) {
281 $logger = new Logger( 'plugin', DECALOG_PRODUCT_NAME, DECALOG_VERSION );
282 $files = [];
283 foreach ( File::list_files( ABSPATH, 100, [ '/^.*\.php$/i' ], [], true ) as $file ) {
284 $files[] = str_replace( ABSPATH, './', $file );
285 }
286 if ( Environment::is_wordpress_multisite() ) {
287 $logger->info( $automatic ? 'Network reset and warm-up initiated via cron.' : 'Network warm-up initiated via manual action.' );
288 } else {
289 $logger->info( $automatic ? 'Site reset and warm-up initiated via cron.' : 'Site warm-up initiated via manual action.' );
290 }
291 $result = self::recompile( $files, $force );
292 if ( $automatic ) {
293 Cache::set_global( '/Data/ResetWarmupTimestamp', time(), 'check' );
294 } else {
295 Cache::set_global( '/Data/WarmupTimestamp', time(), 'check' );
296 }
297 if ( Environment::is_wordpress_multisite() ) {
298 $logger->info( sprintf( 'Network warm-up terminated. %d files were recompiled', $result ) );
299 } else {
300 $logger->info( sprintf( 'Site warm-up terminated. %d files were recompiled', $result ) );
301 }
302 return $result;
303 }
304
305 }
306