[BUGFIX] Fix OpcodeCache for PHP 5.3
[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 (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 (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 wincache_refresh_if_changed(array($fileAbsPath));
112 }
113 ),
114
115 // http://xcache.lighttpd.net/
116 'XCache' => array(
117 'active' => extension_loaded('xcache'),
118 'version' => phpversion('xcache'),
119 'canReset' => TRUE, // xcache_clear_cache()
120 'canInvalidate' => FALSE,
121 'error' => FALSE,
122 'clearCallback' => function ($fileAbsPath) {
123 xcache_clear_cache(XC_TYPE_PHP);
124 }
125 ),
126
127 // https://github.com/eaccelerator/eaccelerator
128 //
129 // @see https://github.com/eaccelerator/eaccelerator/blob/master/doc/php/info.php
130 // Only possible if we are in eaccelerator.admin_allowed_path and we can only remove data
131 // "that isn't used in the current requests"
132 'eAccelerator' => array(
133 'active' => extension_loaded('eAccelerator'),
134 'version' => phpversion('eaccelerator'),
135 'canReset' => FALSE,
136 'canInvalidate' => FALSE,
137 'error' => TRUE, // eAccelerator is more or less out of date and not functional for what we need.
138 'clearCallback' => function ($fileAbsPath) {
139 eaccelerator_clear();
140 }
141 ),
142
143 // https://github.com/zendtech/ZendOptimizerPlus
144 // http://files.zend.com/help/Zend-Server/zend-server.htm#zendoptimizerplus.html
145 'ZendOptimizerPlus' => array(
146 'active' => extension_loaded('Zend Optimizer+') && ini_get('zend_optimizerplus.enable') === '1',
147 'version' => phpversion('Zend Optimizer+'),
148 'canReset' => TRUE, // accelerator_reset()
149 'canInvalidate' => FALSE,
150 'error' => FALSE,
151 'clearCallback' => function ($fileAbsPath) {
152 accelerator_reset();
153 }
154 ),
155 );
156
157 static::$activeCaches = array();
158 // Cache the active ones
159 foreach (static::$supportedCaches as $opcodeCache => $properties) {
160 if ($properties['active']) {
161 static::$activeCaches[$opcodeCache] = $properties;
162 }
163 }
164 }
165
166 /**
167 * Gets the state of canInvalidate for given cache system.
168 *
169 * @param string $system The cache system to test (APC, ...)
170 *
171 * @return boolean The calculated value from array or FALSE if cache system not exists.
172 * @internal Do not rely on this function. Will be removed if PHP5.4 is minimum requirement.
173 */
174 static public function getCanInvalidate($system) {
175 return isset(static::$supportedCaches[$system])
176 ? static::$supportedCaches[$system]['canInvalidate']
177 : FALSE;
178 }
179
180 /**
181 * Clears a file from an opcache, if one exists.
182 *
183 * @param string $fileAbsPath The file as absolute path to be cleared.
184 *
185 * @return void
186 */
187 static public function clearAllActive($fileAbsPath) {
188 foreach (static::getAllActive() as $properties) {
189 $callback = $properties['clearCallback'];
190 $callback($fileAbsPath);
191 }
192 }
193
194 /**
195 * Returns all supported and active opcaches
196 *
197 * @return array Array filled with supported and active opcaches
198 */
199 static public function getAllActive() {
200 if (static::$activeCaches === NULL) {
201 static::initialize();
202 }
203 return static::$activeCaches;
204 }
205
206 /**
207 * Checks if the APC configuration is useable to clear cache of one file.
208 * https://bugs.php.net/bug.php?id=66819
209 *
210 * @return bool Returns TRUE if file can be invalidated and FALSE if complete cache needs to be removed
211 */
212 static public function canApcInvalidate() {
213 // apc_delete_file() should exists since APC 3.1.1 but you never know so default is no
214 $canInvalidate = FALSE;
215
216 if (function_exists('apc_delete_file')) {
217 // Deleting files from cache depends on generating the cache key.
218 // This cache key generation depends on unnecessary configuration options
219 // http://git.php.net/?p=pecl/caching/apc.git;a=blob;f=apc_cache.c;h=d15cf8c1b4b9d09b9bac75b16c062c8b40458dda;hb=HEAD#l931
220
221 // If stat=0 then canonicalized path may be used
222 $stat = (int)ini_get('apc.stat');
223 // If canonicalize (default = 1) then file_update_protection isn't checked
224 $canonicalize = (int)ini_get('apc.canonicalize');
225 // If file file_update_protection is checked, then we will fail, 'cause we generated the file and then try to
226 // remove it. But the file is not older than file_update_protection and therefore hash generation will stop with error.
227 $protection = (int)ini_get('apc.file_update_protection');
228
229 if ($protection === 0 || ($stat === 0 && $canonicalize === 1)) {
230 $canInvalidate = TRUE;
231 }
232 }
233
234 return $canInvalidate;
235 }
236 }