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