f45849d5145968df86131ac990aa4dcf79fd0eae
[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
45 static::$supportedCaches = array(
46 // The ZendOpcache aka OPcache since PHP 5.5
47 // http://php.net/manual/de/book.opcache.php
48 'OPcache' => array(
49 'active' => extension_loaded('Zend OPcache') && ini_get('opcache.enable') === '1',
50 'version' => phpversion('Zend OPcache'),
51 'canReset' => TRUE, // opcache_reset() ... it seems that it doesn't reset for current run.
52 // From documentation this function exists since first version (7.0.0) but from Changelog
53 // this function exists since 7.0.2
54 // http://pecl.php.net/package-changelog.php?package=ZendOpcache&release=7.0.2
55 'canInvalidate' => function_exists('opcache_invalidate'),
56 'error' => FALSE,
57 'clearCallback' => function ($fileAbsPath) {
58 if ($fileAbsPath !== NULL && function_exists('opcache_invalidate')) {
59 opcache_invalidate($fileAbsPath);
60 } else {
61 opcache_reset();
62 }
63 }
64 ),
65
66 // The Alternative PHP Cache aka APC
67 // http://www.php.net/manual/de/book.apc.php
68 'APC' => array(
69 // Currently APCu identifies itself both as "apcu" and "apc" (for compatibility) although it doesn't
70 // provide the APC-opcache functionality
71 'active' => extension_loaded('apc') && !extension_loaded('apcu') && ini_get('apc.enabled') === '1',
72 'version' => $apcVersion,
73 // apc_clear_cache() since APC 2.0.0 so default yes. In cli it do not clear the http cache.
74 'canReset' => TRUE,
75 'canInvalidate' => self::canApcInvalidate(),
76 // Versions lower then 3.1.7 are known as malfunction
77 'error' => $apcVersion && VersionNumberUtility::convertVersionNumberToInteger($apcVersion) < 3001007,
78 'clearCallback' => function ($fileAbsPath) {
79 if ($fileAbsPath !== NULL && OpcodeCacheUtility::getCanInvalidate('APC')) {
80 // This may output a warning like: PHP Warning: apc_delete_file(): Could not stat file
81 // This warning isn't true, this means that apc was unable to generate the cache key
82 // which depends on the configuration of APC.
83 apc_delete_file($fileAbsPath);
84 } else {
85 apc_clear_cache('opcode');
86 }
87 }
88 ),
89
90 // http://www.php.net/manual/de/book.wincache.php
91 'WinCache' => array(
92 'active' => extension_loaded('wincache') && ini_get('wincache.ocenabled') === '1',
93 'version' => phpversion('wincache'),
94 'canReset' => FALSE,
95 'canInvalidate' => TRUE, // wincache_refresh_if_changed()
96 'error' => FALSE,
97 'clearCallback' => function ($fileAbsPath) {
98 if ($fileAbsPath !== NULL) {
99 wincache_refresh_if_changed(array($fileAbsPath));
100 } else {
101 // No argument means refreshing all.
102 wincache_refresh_if_changed();
103 }
104 }
105 ),
106
107 // http://xcache.lighttpd.net/
108 'XCache' => array(
109 'active' => extension_loaded('xcache'),
110 'version' => phpversion('xcache'),
111 'canReset' => TRUE, // xcache_clear_cache()
112 'canInvalidate' => FALSE,
113 'error' => FALSE,
114 'clearCallback' => function ($fileAbsPath) {
115 if (!ini_get('xcache.admin.enable_auth')) {
116 xcache_clear_cache(XC_TYPE_PHP);
117 }
118 }
119 ),
120
121 // https://github.com/eaccelerator/eaccelerator
122 //
123 // @see https://github.com/eaccelerator/eaccelerator/blob/master/doc/php/info.php
124 // Only possible if we are in eaccelerator.admin_allowed_path and we can only remove data
125 // "that isn't used in the current requests"
126 'eAccelerator' => array(
127 'active' => extension_loaded('eAccelerator'),
128 'version' => phpversion('eaccelerator'),
129 'canReset' => FALSE,
130 'canInvalidate' => FALSE,
131 'error' => TRUE, // eAccelerator is more or less out of date and not functional for what we need.
132 'clearCallback' => function ($fileAbsPath) {
133 eaccelerator_clear();
134 }
135 ),
136
137 // https://github.com/zendtech/ZendOptimizerPlus
138 // http://files.zend.com/help/Zend-Server/zend-server.htm#zendoptimizerplus.html
139 'ZendOptimizerPlus' => array(
140 'active' => extension_loaded('Zend Optimizer+') && ini_get('zend_optimizerplus.enable') === '1',
141 'version' => phpversion('Zend Optimizer+'),
142 'canReset' => TRUE, // accelerator_reset()
143 'canInvalidate' => FALSE,
144 'error' => FALSE,
145 'clearCallback' => function ($fileAbsPath) {
146 accelerator_reset();
147 }
148 ),
149 );
150
151 static::$activeCaches = array();
152 // Cache the active ones
153 foreach (static::$supportedCaches as $opcodeCache => $properties) {
154 if ($properties['active']) {
155 static::$activeCaches[$opcodeCache] = $properties;
156 }
157 }
158 }
159
160 /**
161 * Gets the state of canInvalidate for given cache system.
162 *
163 * @param string $system The cache system to test (APC, ...)
164 *
165 * @return boolean The calculated value from array or FALSE if cache system not exists.
166 * @internal Do not rely on this function. Will be removed if PHP5.4 is minimum requirement.
167 */
168 static public function getCanInvalidate($system) {
169 return isset(static::$supportedCaches[$system])
170 ? static::$supportedCaches[$system]['canInvalidate']
171 : FALSE;
172 }
173
174 /**
175 * Clears a file from an opcache, if one exists.
176 *
177 * @param string|NULL $fileAbsPath The file as absolute path to be cleared or NULL to clear completely.
178 *
179 * @return void
180 */
181 static public function clearAllActive($fileAbsPath = NULL) {
182 foreach (static::getAllActive() as $properties) {
183 $callback = $properties['clearCallback'];
184 $callback($fileAbsPath);
185 }
186 }
187
188 /**
189 * Returns all supported and active opcaches
190 *
191 * @return array Array filled with supported and active opcaches
192 */
193 static public function getAllActive() {
194 if (static::$activeCaches === NULL) {
195 static::initialize();
196 }
197 return static::$activeCaches;
198 }
199
200 /**
201 * Checks if the APC configuration is useable to clear cache of one file.
202 * https://bugs.php.net/bug.php?id=66819
203 *
204 * @return bool Returns TRUE if file can be invalidated and FALSE if complete cache needs to be removed
205 */
206 static public function canApcInvalidate() {
207 // apc_delete_file() should exists since APC 3.1.1 but you never know so default is no
208 $canInvalidate = FALSE;
209
210 if (function_exists('apc_delete_file')) {
211 // Deleting files from cache depends on generating the cache key.
212 // This cache key generation depends on unnecessary configuration options
213 // http://git.php.net/?p=pecl/caching/apc.git;a=blob;f=apc_cache.c;h=d15cf8c1b4b9d09b9bac75b16c062c8b40458dda;hb=HEAD#l931
214
215 // If stat=0 then canonicalized path may be used
216 $stat = (int)ini_get('apc.stat');
217 // If canonicalize (default = 1) then file_update_protection isn't checked
218 $canonicalize = (int)ini_get('apc.canonicalize');
219 // If file file_update_protection is checked, then we will fail, 'cause we generated the file and then try to
220 // remove it. But the file is not older than file_update_protection and therefore hash generation will stop with error.
221 $protection = (int)ini_get('apc.file_update_protection');
222
223 if ($protection === 0 || ($stat === 0 && $canonicalize === 1)) {
224 $canInvalidate = TRUE;
225 }
226 }
227
228 return $canInvalidate;
229 }
230 }