[BUGFIX] Flush opcode caches while saving PHP files.
[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 (static::$supportedCaches['APC']['canInvalidate']) {
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 * Clears a file from an opcache, if one exists.
168 *
169 * @param string $fileAbsPath The file as absolute path to be cleared.
170 *
171 * @return void
172 */
173 static public function clearAllActive($fileAbsPath) {
174 foreach (static::getAllActive() as $properties) {
175 $callback = $properties['clearCallback'];
176 $callback($fileAbsPath);
177 }
178 }
179
180 /**
181 * Returns all supported and active opcaches
182 *
183 * @return array Array filled with supported and active opcaches
184 */
185 static public function getAllActive() {
186 if (static::$activeCaches === NULL) {
187 static::initialize();
188 }
189 return static::$activeCaches;
190 }
191
192 /**
193 * Checks if the APC configuration is useable to clear cache of one file.
194 * https://bugs.php.net/bug.php?id=66819
195 *
196 * @return bool Returns TRUE if file can be invalidated and FALSE if complete cache needs to be removed
197 */
198 static public function canApcInvalidate() {
199 // apc_delete_file() should exists since APC 3.1.1 but you never know so default is no
200 $canInvalidate = FALSE;
201
202 if (function_exists('apc_delete_file')) {
203 // Deleting files from cache depends on generating the cache key.
204 // This cache key generation depends on unnecessary configuration options
205 // http://git.php.net/?p=pecl/caching/apc.git;a=blob;f=apc_cache.c;h=d15cf8c1b4b9d09b9bac75b16c062c8b40458dda;hb=HEAD#l931
206
207 // If stat=0 then canonicalized path may be used
208 $stat = (int)ini_get('apc.stat');
209 // If canonicalize (default = 1) then file_update_protection isn't checked
210 $canonicalize = (int)ini_get('apc.canonicalize');
211 // If file file_update_protection is checked, then we will fail, 'cause we generated the file and then try to
212 // remove it. But the file is not older than file_update_protection and therefore hash generation will stop with error.
213 $protection = (int)ini_get('apc.file_update_protection');
214
215 if ($protection === 0 || ($stat === 0 && $canonicalize === 1)) {
216 $canInvalidate = TRUE;
217 }
218 }
219
220 return $canInvalidate;
221 }
222 }