[FEATURE] Add complete opcode cache reset
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Utility / OpcodeCacheUtility.php
1 <?php
2 namespace TYPO3\CMS\Core\Utility;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2014 Alexander Opitz <opitz@pluspol-interactive.de>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the text file GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29
30 /**
31 * Class with helper functions for clearing the PHP opcache.
32 * It auto detects the opcache system and invalidates/resets it.
33 * http://forge.typo3.org/issues/55252
34 * Supported opcaches are: OPcache (PHP 5.5), APC, WinCache, XCache, eAccelerator, ZendOptimizerPlus
35 *
36 * @author Alexander Opitz <opitz@pluspol-interactive.de>
37 */
38 class OpcodeCacheUtility {
39
40 /**
41 * All supported cache types
42 * @var array|null
43 */
44 static protected $supportedCaches = NULL;
45
46 /**
47 * Holds all currently active caches
48 * @var array|null
49 */
50 static protected $activeCaches = NULL;
51
52 /**
53 * Initialize the cache properties
54 */
55 static protected function initialize() {
56 $apcVersion = phpversion('apc');
57
58 static::$supportedCaches = array(
59 // The ZendOpcache aka OPcache since PHP 5.5
60 // http://php.net/manual/de/book.opcache.php
61 'OPcache' => array(
62 'active' => extension_loaded('Zend OPcache') && ini_get('opcache.enable') === '1',
63 'version' => phpversion('Zend OPcache'),
64 'canReset' => TRUE, // opcache_reset() ... it seems that it doesn't reset for current run.
65 // From documentation this function exists since first version (7.0.0) but from Changelog
66 // this function exists since 7.0.2
67 // http://pecl.php.net/package-changelog.php?package=ZendOpcache&release=7.0.2
68 'canInvalidate' => function_exists('opcache_invalidate'),
69 'error' => FALSE,
70 'clearCallback' => function ($fileAbsPath) {
71 if ($fileAbsPath !== NULL && function_exists('opcache_invalidate')) {
72 opcache_invalidate($fileAbsPath);
73 } else {
74 opcache_reset();
75 }
76 }
77 ),
78
79 // The Alternative PHP Cache aka APC
80 // http://www.php.net/manual/de/book.apc.php
81 'APC' => array(
82 // Currently APCu identifies itself both as "apcu" and "apc" (for compatibility) although it doesn't
83 // provide the APC-opcache functionality
84 'active' => extension_loaded('apc') && !extension_loaded('apcu') && ini_get('apc.enabled') === '1',
85 'version' => $apcVersion,
86 // apc_clear_cache() since APC 2.0.0 so default yes. In cli it do not clear the http cache.
87 'canReset' => TRUE,
88 'canInvalidate' => self::canApcInvalidate(),
89 // Versions lower then 3.1.7 are known as malfunction
90 'error' => $apcVersion && VersionNumberUtility::convertVersionNumberToInteger($apcVersion) < 3001007,
91 'clearCallback' => function ($fileAbsPath) {
92 if ($fileAbsPath !== NULL && OpcodeCacheUtility::getCanInvalidate('APC')) {
93 // This may output a warning like: PHP Warning: apc_delete_file(): Could not stat file
94 // This warning isn't true, this means that apc was unable to generate the cache key
95 // which depends on the configuration of APC.
96 apc_delete_file($fileAbsPath);
97 } else {
98 apc_clear_cache('opcode');
99 }
100 }
101 ),
102
103 // http://www.php.net/manual/de/book.wincache.php
104 'WinCache' => array(
105 'active' => extension_loaded('wincache') && ini_get('wincache.ocenabled') === '1',
106 'version' => phpversion('wincache'),
107 'canReset' => FALSE,
108 'canInvalidate' => TRUE, // wincache_refresh_if_changed()
109 'error' => FALSE,
110 'clearCallback' => function ($fileAbsPath) {
111 if ($fileAbsPath !== NULL) {
112 wincache_refresh_if_changed(array($fileAbsPath));
113 } else {
114 // No argument means refreshing all.
115 wincache_refresh_if_changed();
116 }
117 }
118 ),
119
120 // http://xcache.lighttpd.net/
121 'XCache' => array(
122 'active' => extension_loaded('xcache'),
123 'version' => phpversion('xcache'),
124 'canReset' => TRUE, // xcache_clear_cache()
125 'canInvalidate' => FALSE,
126 'error' => FALSE,
127 'clearCallback' => function ($fileAbsPath) {
128 if (!ini_get('xcache.admin.enable_auth')) {
129 xcache_clear_cache(XC_TYPE_PHP);
130 }
131 }
132 ),
133
134 // https://github.com/eaccelerator/eaccelerator
135 //
136 // @see https://github.com/eaccelerator/eaccelerator/blob/master/doc/php/info.php
137 // Only possible if we are in eaccelerator.admin_allowed_path and we can only remove data
138 // "that isn't used in the current requests"
139 'eAccelerator' => array(
140 'active' => extension_loaded('eAccelerator'),
141 'version' => phpversion('eaccelerator'),
142 'canReset' => FALSE,
143 'canInvalidate' => FALSE,
144 'error' => TRUE, // eAccelerator is more or less out of date and not functional for what we need.
145 'clearCallback' => function ($fileAbsPath) {
146 eaccelerator_clear();
147 }
148 ),
149
150 // https://github.com/zendtech/ZendOptimizerPlus
151 // http://files.zend.com/help/Zend-Server/zend-server.htm#zendoptimizerplus.html
152 'ZendOptimizerPlus' => array(
153 'active' => extension_loaded('Zend Optimizer+') && ini_get('zend_optimizerplus.enable') === '1',
154 'version' => phpversion('Zend Optimizer+'),
155 'canReset' => TRUE, // accelerator_reset()
156 'canInvalidate' => FALSE,
157 'error' => FALSE,
158 'clearCallback' => function ($fileAbsPath) {
159 accelerator_reset();
160 }
161 ),
162 );
163
164 static::$activeCaches = array();
165 // Cache the active ones
166 foreach (static::$supportedCaches as $opcodeCache => $properties) {
167 if ($properties['active']) {
168 static::$activeCaches[$opcodeCache] = $properties;
169 }
170 }
171 }
172
173 /**
174 * Gets the state of canInvalidate for given cache system.
175 *
176 * @param string $system The cache system to test (APC, ...)
177 *
178 * @return boolean The calculated value from array or FALSE if cache system not exists.
179 * @internal Do not rely on this function. Will be removed if PHP5.4 is minimum requirement.
180 */
181 static public function getCanInvalidate($system) {
182 return isset(static::$supportedCaches[$system])
183 ? static::$supportedCaches[$system]['canInvalidate']
184 : FALSE;
185 }
186
187 /**
188 * Clears a file from an opcache, if one exists.
189 *
190 * @param string|NULL $fileAbsPath The file as absolute path to be cleared or NULL to clear completely.
191 *
192 * @return void
193 */
194 static public function clearAllActive($fileAbsPath = NULL) {
195 foreach (static::getAllActive() as $properties) {
196 $callback = $properties['clearCallback'];
197 $callback($fileAbsPath);
198 }
199 }
200
201 /**
202 * Returns all supported and active opcaches
203 *
204 * @return array Array filled with supported and active opcaches
205 */
206 static public function getAllActive() {
207 if (static::$activeCaches === NULL) {
208 static::initialize();
209 }
210 return static::$activeCaches;
211 }
212
213 /**
214 * Checks if the APC configuration is useable to clear cache of one file.
215 * https://bugs.php.net/bug.php?id=66819
216 *
217 * @return bool Returns TRUE if file can be invalidated and FALSE if complete cache needs to be removed
218 */
219 static public function canApcInvalidate() {
220 // apc_delete_file() should exists since APC 3.1.1 but you never know so default is no
221 $canInvalidate = FALSE;
222
223 if (function_exists('apc_delete_file')) {
224 // Deleting files from cache depends on generating the cache key.
225 // This cache key generation depends on unnecessary configuration options
226 // http://git.php.net/?p=pecl/caching/apc.git;a=blob;f=apc_cache.c;h=d15cf8c1b4b9d09b9bac75b16c062c8b40458dda;hb=HEAD#l931
227
228 // If stat=0 then canonicalized path may be used
229 $stat = (int)ini_get('apc.stat');
230 // If canonicalize (default = 1) then file_update_protection isn't checked
231 $canonicalize = (int)ini_get('apc.canonicalize');
232 // If file file_update_protection is checked, then we will fail, 'cause we generated the file and then try to
233 // remove it. But the file is not older than file_update_protection and therefore hash generation will stop with error.
234 $protection = (int)ini_get('apc.file_update_protection');
235
236 if ($protection === 0 || ($stat === 0 && $canonicalize === 1)) {
237 $canInvalidate = TRUE;
238 }
239 }
240
241 return $canInvalidate;
242 }
243 }