1 <?php
2 /**
3 * @package Joomla.Platform
4 * @subpackage Cache
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 /**
13 * APCu cache storage handler
14 *
15 * @link https://secure.php.net/manual/en/ref.apcu.php
16 * @since 3.5
17 */
18 class JCacheStorageApcu extends JCacheStorage
19 {
20 /**
21 * Check if the cache contains data stored by ID and group
22 *
23 * @param string $id The cache data ID
24 * @param string $group The cache data group
25 *
26 * @return boolean
27 *
28 * @since 3.7.0
29 */
30 public function contains($id, $group)
31 {
32 return apcu_exists($this->_getCacheId($id, $group));
33 }
34
35 /**
36 * Get cached data by ID and group
37 *
38 * @param string $id The cache data ID
39 * @param string $group The cache data group
40 * @param boolean $checkTime True to verify cache time expiration threshold
41 *
42 * @return mixed Boolean false on failure or a cached data object
43 *
44 * @since 3.5
45 */
46 public function get($id, $group, $checkTime = true)
47 {
48 return apcu_fetch($this->_getCacheId($id, $group));
49 }
50
51 /**
52 * Get all cached data
53 *
54 * @return mixed Boolean false on failure or a cached data object
55 *
56 * @since 3.5
57 */
58 public function getAll()
59 {
60 $allinfo = apcu_cache_info();
61 $keys = $allinfo['cache_list'];
62 $secret = $this->_hash;
63
64 $data = array();
65
66 foreach ($keys as $key)
67 {
68 if (isset($key['info']))
69 {
70 // The internal key name changed with APCu 4.0.7 from key to info
71 $name = $key['info'];
72 }
73 elseif (isset($key['entry_name']))
74 {
75 // Some APCu modules changed the internal key name from key to entry_name
76 $name = $key['entry_name'];
77 }
78 else
79 {
80 // A fall back for the old internal key name
81 $name = $key['key'];
82 }
83
84 $namearr = explode('-', $name);
85
86 if ($namearr !== false && $namearr[0] == $secret && $namearr[1] == 'cache')
87 {
88 $group = $namearr[2];
89
90 if (!isset($data[$group]))
91 {
92 $item = new JCacheStorageHelper($group);
93 }
94 else
95 {
96 $item = $data[$group];
97 }
98
99 $item->updateSize($key['mem_size']);
100
101 $data[$group] = $item;
102 }
103 }
104
105 return $data;
106 }
107
108 /**
109 * Store the data to cache by ID and group
110 *
111 * @param string $id The cache data ID
112 * @param string $group The cache data group
113 * @param string $data The data to store in cache
114 *
115 * @return boolean
116 *
117 * @since 3.5
118 */
119 public function store($id, $group, $data)
120 {
121 return apcu_store($this->_getCacheId($id, $group), $data, $this->_lifetime);
122 }
123
124 /**
125 * Remove a cached data entry by ID and group
126 *
127 * @param string $id The cache data ID
128 * @param string $group The cache data group
129 *
130 * @return boolean
131 *
132 * @since 3.5
133 */
134 public function remove($id, $group)
135 {
136 $cache_id = $this->_getCacheId($id, $group);
137
138 // The apcu_delete function returns false if the ID does not exist
139 if (apcu_exists($cache_id))
140 {
141 return apcu_delete($cache_id);
142 }
143
144 return true;
145 }
146
147 /**
148 * Clean cache for a group given a mode.
149 *
150 * group mode : cleans all cache in the group
151 * notgroup mode : cleans all cache not in the group
152 *
153 * @param string $group The cache data group
154 * @param string $mode The mode for cleaning cache [group|notgroup]
155 *
156 * @return boolean
157 *
158 * @since 3.5
159 */
160 public function clean($group, $mode = null)
161 {
162 $allinfo = apcu_cache_info();
163 $keys = $allinfo['cache_list'];
164 $secret = $this->_hash;
165
166 foreach ($keys as $key)
167 {
168 if (isset($key['info']))
169 {
170 // The internal key name changed with APCu 4.0.7 from key to info
171 $internalKey = $key['info'];
172 }
173 elseif (isset($key['entry_name']))
174 {
175 // Some APCu modules changed the internal key name from key to entry_name
176 $internalKey = $key['entry_name'];
177 }
178 else
179 {
180 // A fall back for the old internal key name
181 $internalKey = $key['key'];
182 }
183
184 if (strpos($internalKey, $secret . '-cache-' . $group . '-') === 0 xor $mode != 'group')
185 {
186 apcu_delete($internalKey);
187 }
188 }
189
190 return true;
191 }
192
193 /**
194 * Garbage collect expired cache data
195 *
196 * @return boolean
197 *
198 * @since 3.5
199 */
200 public function gc()
201 {
202 $allinfo = apcu_cache_info();
203 $keys = $allinfo['cache_list'];
204 $secret = $this->_hash;
205
206 foreach ($keys as $key)
207 {
208 if (isset($key['info']))
209 {
210 // The internal key name changed with APCu 4.0.7 from key to info
211 $internalKey = $key['info'];
212 }
213 elseif (isset($key['entry_name']))
214 {
215 // Some APCu modules changed the internal key name from key to entry_name
216 $internalKey = $key['entry_name'];
217 }
218 else
219 {
220 // A fall back for the old internal key name
221 $internalKey = $key['key'];
222 }
223
224 if (strpos($internalKey, $secret . '-cache-'))
225 {
226 apcu_fetch($internalKey);
227 }
228 }
229
230 return true;
231 }
232
233 /**
234 * Test to see if the storage handler is available.
235 *
236 * @return boolean
237 *
238 * @since 3.5
239 */
240 public static function isSupported()
241 {
242 $supported = extension_loaded('apcu') && ini_get('apc.enabled');
243
244 // If on the CLI interface, the `apc.enable_cli` option must also be enabled
245 if ($supported && php_sapi_name() === 'cli')
246 {
247 $supported = ini_get('apc.enable_cli');
248 }
249
250 return (bool) $supported;
251 }
252
253 /**
254 * Lock cached item
255 *
256 * @param string $id The cache data ID
257 * @param string $group The cache data group
258 * @param integer $locktime Cached item max lock time
259 *
260 * @return mixed Boolean false if locking failed or an object containing properties lock and locklooped
261 *
262 * @since 3.5
263 */
264 public function lock($id, $group, $locktime)
265 {
266 $returning = new stdClass;
267 $returning->locklooped = false;
268
269 $looptime = $locktime * 10;
270
271 $cache_id = $this->_getCacheId($id, $group) . '_lock';
272
273 $data_lock = apcu_add($cache_id, 1, $locktime);
274
275 if ($data_lock === false)
276 {
277 $lock_counter = 0;
278
279 // Loop until you find that the lock has been released.
280 // That implies that data get from other thread has finished
281 while ($data_lock === false)
282 {
283 if ($lock_counter > $looptime)
284 {
285 $returning->locked = false;
286 $returning->locklooped = true;
287 break;
288 }
289
290 usleep(100);
291 $data_lock = apcu_add($cache_id, 1, $locktime);
292 $lock_counter++;
293 }
294 }
295
296 $returning->locked = $data_lock;
297
298 return $returning;
299 }
300
301 /**
302 * Unlock cached item
303 *
304 * @param string $id The cache data ID
305 * @param string $group The cache data group
306 *
307 * @return boolean
308 *
309 * @since 3.5
310 */
311 public function unlock($id, $group = null)
312 {
313 $cache_id = $this->_getCacheId($id, $group) . '_lock';
314
315 // The apcu_delete function returns false if the ID does not exist
316 if (apcu_exists($cache_id))
317 {
318 return apcu_delete($cache_id);
319 }
320
321 return true;
322 }
323 }
324