1 | <?php |
||
2 | |||
3 | namespace MatthiasMullie\Scrapbook\Adapters; |
||
4 | |||
5 | use MatthiasMullie\Scrapbook\Adapters\Collections\Apc as Collection; |
||
6 | use MatthiasMullie\Scrapbook\Exception\Exception; |
||
7 | use MatthiasMullie\Scrapbook\KeyValueStore; |
||
8 | |||
9 | /** |
||
10 | * APC adapter. Basically just a wrapper over apc_* functions, but in an |
||
11 | * exchangeable (KeyValueStore) interface. |
||
12 | * |
||
13 | * @author Matthias Mullie <[email protected]> |
||
14 | * @copyright Copyright (c) 2014, Matthias Mullie. All rights reserved |
||
15 | * @license LICENSE MIT |
||
16 | */ |
||
17 | class Apc implements KeyValueStore |
||
18 | { |
||
19 | /** |
||
20 | * APC does this crazy thing of only deleting expired data on every new |
||
21 | * (page) request, not checking it when you actually retrieve the value |
||
22 | * (which you may just have set in the same request) |
||
23 | * Since it's totally possible to store values that expire in the same |
||
24 | * request, we'll keep track of those expiration times here & filter them |
||
25 | * out in case they did expire. |
||
26 | * |
||
27 | * @see http://stackoverflow.com/questions/11750223/apc-user-cache-entries-not-expiring |
||
28 | * |
||
29 | * @var array |
||
30 | */ |
||
31 | protected $expires = array(); |
||
32 | |||
33 | public function __construct() |
||
34 | { |
||
35 | if (!function_exists('apcu_fetch') && !function_exists('apc_fetch')) { |
||
36 | throw new Exception('ext-apc(u) is not installed.'); |
||
37 | } |
||
38 | } |
||
39 | |||
40 | /** |
||
41 | * {@inheritdoc} |
||
42 | */ |
||
43 | public function get($key, &$token = null) |
||
44 | { |
||
45 | // check for values that were just stored in this request but have |
||
46 | // actually expired by now |
||
47 | if (isset($this->expires[$key]) && $this->expires[$key] < time()) { |
||
48 | return false; |
||
49 | } |
||
50 | |||
51 | $value = $this->apcu_fetch($key, $success); |
||
52 | if (false === $success) { |
||
53 | $token = null; |
||
54 | |||
55 | return false; |
||
56 | } |
||
57 | |||
58 | $token = serialize($value); |
||
59 | |||
60 | return $value; |
||
61 | } |
||
62 | |||
63 | /** |
||
64 | * {@inheritdoc} |
||
65 | */ |
||
66 | public function getMulti(array $keys, array &$tokens = null) |
||
67 | { |
||
68 | $tokens = array(); |
||
69 | if (empty($keys)) { |
||
70 | return array(); |
||
71 | } |
||
72 | |||
73 | // check for values that were just stored in this request but have |
||
74 | // actually expired by now |
||
75 | foreach ($keys as $i => $key) { |
||
76 | if (isset($this->expires[$key]) && $this->expires[$key] < time()) { |
||
77 | unset($keys[$i]); |
||
78 | } |
||
79 | } |
||
80 | |||
81 | $values = $this->apcu_fetch($keys); |
||
82 | if (false === $values) { |
||
83 | return array(); |
||
84 | } |
||
85 | |||
86 | $tokens = array(); |
||
87 | foreach ($values as $key => $value) { |
||
88 | $tokens[$key] = serialize($value); |
||
89 | } |
||
90 | |||
91 | return $values; |
||
92 | } |
||
93 | |||
94 | /** |
||
95 | * {@inheritdoc} |
||
96 | */ |
||
97 | public function set($key, $value, $expire = 0) |
||
98 | { |
||
99 | $ttl = $this->ttl($expire); |
||
100 | |||
101 | // negative TTLs don't always seem to properly treat the key as deleted |
||
102 | if ($ttl < 0) { |
||
103 | $this->delete($key); |
||
104 | |||
105 | return true; |
||
106 | } |
||
107 | |||
108 | // lock required for CAS |
||
109 | if (!$this->lock($key)) { |
||
110 | return false; |
||
111 | } |
||
112 | |||
113 | $success = $this->apcu_store($key, $value, $ttl); |
||
114 | $this->expire($key, $ttl); |
||
115 | $this->unlock($key); |
||
116 | |||
117 | return $success; |
||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
118 | } |
||
119 | |||
120 | /** |
||
121 | * {@inheritdoc} |
||
122 | */ |
||
123 | public function setMulti(array $items, $expire = 0) |
||
124 | { |
||
125 | if (empty($items)) { |
||
126 | return array(); |
||
127 | } |
||
128 | |||
129 | $ttl = $this->ttl($expire); |
||
130 | |||
131 | // negative TTLs don't always seem to properly treat the key as deleted |
||
132 | if ($ttl < 0) { |
||
133 | $this->deleteMulti(array_keys($items)); |
||
134 | |||
135 | return array_fill_keys(array_keys($items), true); |
||
136 | } |
||
137 | |||
138 | // attempt to get locks for all items |
||
139 | $locked = $this->lock(array_keys($items)); |
||
140 | $locked = array_fill_keys($locked, null); |
||
141 | $failed = array_diff_key($items, $locked); |
||
142 | $items = array_intersect_key($items, $locked); |
||
143 | |||
144 | if ($items) { |
||
0 ignored issues
–
show
The expression
$items of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using
Loading history...
|
|||
145 | // only write to those where lock was acquired |
||
146 | $this->apcu_store($items, null, $ttl); |
||
147 | $this->expire(array_keys($items), $ttl); |
||
148 | $this->unlock(array_keys($items)); |
||
149 | } |
||
150 | |||
151 | $return = array(); |
||
152 | foreach ($items as $key => $value) { |
||
153 | $return[$key] = !array_key_exists($key, $failed); |
||
154 | } |
||
155 | |||
156 | return $return; |
||
157 | } |
||
158 | |||
159 | /** |
||
160 | * {@inheritdoc} |
||
161 | */ |
||
162 | public function delete($key) |
||
163 | { |
||
164 | // lock required for CAS |
||
165 | if (!$this->lock($key)) { |
||
166 | return false; |
||
167 | } |
||
168 | |||
169 | $success = $this->apcu_delete($key); |
||
170 | unset($this->expires[$key]); |
||
171 | $this->unlock($key); |
||
172 | |||
173 | return $success; |
||
0 ignored issues
–
show
|
|||
174 | } |
||
175 | |||
176 | /** |
||
177 | * {@inheritdoc} |
||
178 | */ |
||
179 | public function deleteMulti(array $keys) |
||
180 | { |
||
181 | if (empty($keys)) { |
||
182 | return array(); |
||
183 | } |
||
184 | |||
185 | // attempt to get locks for all items |
||
186 | $locked = $this->lock($keys); |
||
187 | $failed = array_diff($keys, $locked); |
||
188 | $keys = array_intersect($keys, $locked); |
||
189 | |||
190 | // only delete those where lock was acquired |
||
191 | if ($keys) { |
||
0 ignored issues
–
show
The expression
$keys of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using
Loading history...
|
|||
192 | /** |
||
193 | * Contrary to the docs, apc_delete also accepts an array of |
||
194 | * multiple keys to be deleted. Docs for apcu_delete are ok in this |
||
195 | * regard. |
||
196 | * But both are flawed in terms of return value in this case: an |
||
197 | * array with failed keys is returned. |
||
198 | * |
||
199 | * @see http://php.net/manual/en/function.apc-delete.php |
||
200 | * |
||
201 | * @var string[] |
||
202 | */ |
||
203 | $result = $this->apcu_delete($keys); |
||
204 | $failed = array_merge($failed, $result); |
||
205 | $this->unlock($keys); |
||
206 | } |
||
207 | |||
208 | $return = array(); |
||
209 | foreach ($keys as $key) { |
||
210 | $return[$key] = !in_array($key, $failed); |
||
211 | unset($this->expires[$key]); |
||
212 | } |
||
213 | |||
214 | return $return; |
||
215 | } |
||
216 | |||
217 | /** |
||
218 | * {@inheritdoc} |
||
219 | */ |
||
220 | public function add($key, $value, $expire = 0) |
||
221 | { |
||
222 | $ttl = $this->ttl($expire); |
||
223 | |||
224 | // negative TTLs don't always seem to properly treat the key as deleted |
||
225 | if ($ttl < 0) { |
||
226 | // don't add - it's expired already; just check if it already |
||
227 | // existed to return true/false as expected from add() |
||
228 | return false === $this->get($key); |
||
229 | } |
||
230 | |||
231 | // lock required for CAS |
||
232 | if (!$this->lock($key)) { |
||
233 | return false; |
||
234 | } |
||
235 | |||
236 | $success = $this->apcu_add($key, $value, $ttl); |
||
237 | $this->expire($key, $ttl); |
||
238 | $this->unlock($key); |
||
239 | |||
240 | return $success; |
||
241 | } |
||
242 | |||
243 | /** |
||
244 | * {@inheritdoc} |
||
245 | */ |
||
246 | public function replace($key, $value, $expire = 0) |
||
247 | { |
||
248 | $ttl = $this->ttl($expire); |
||
249 | |||
250 | // APC doesn't support replace; I'll use get to check key existence, |
||
251 | // then safely replace with cas |
||
252 | $current = $this->get($key, $token); |
||
253 | if (false === $current) { |
||
254 | return false; |
||
255 | } |
||
256 | |||
257 | // negative TTLs don't always seem to properly treat the key as deleted |
||
258 | if ($ttl < 0) { |
||
259 | $this->delete($key); |
||
260 | |||
261 | return true; |
||
262 | } |
||
263 | |||
264 | // no need for locking - cas will do that |
||
265 | return $this->cas($token, $key, $value, $ttl); |
||
266 | } |
||
267 | |||
268 | /** |
||
269 | * {@inheritdoc} |
||
270 | */ |
||
271 | public function cas($token, $key, $value, $expire = 0) |
||
272 | { |
||
273 | $ttl = $this->ttl($expire); |
||
274 | |||
275 | // lock required because we can't perform an atomic CAS |
||
276 | if (!$this->lock($key)) { |
||
277 | return false; |
||
278 | } |
||
279 | |||
280 | // check for values that were just stored in this request but have |
||
281 | // actually expired by now |
||
282 | if (isset($this->expires[$key]) && $this->expires[$key] < time()) { |
||
283 | return false; |
||
284 | } |
||
285 | |||
286 | // get current value, to compare with token |
||
287 | $compare = $this->apcu_fetch($key); |
||
288 | |||
289 | if (false === $compare) { |
||
290 | $this->unlock($key); |
||
291 | |||
292 | return false; |
||
293 | } |
||
294 | |||
295 | if ($token !== serialize($compare)) { |
||
296 | $this->unlock($key); |
||
297 | |||
298 | return false; |
||
299 | } |
||
300 | |||
301 | // negative TTLs don't always seem to properly treat the key as deleted |
||
302 | if ($ttl < 0) { |
||
303 | $this->apcu_delete($key); |
||
304 | unset($this->expires[$key]); |
||
305 | $this->unlock($key); |
||
306 | |||
307 | return true; |
||
308 | } |
||
309 | |||
310 | $success = $this->apcu_store($key, $value, $ttl); |
||
311 | $this->expire($key, $ttl); |
||
312 | $this->unlock($key); |
||
313 | |||
314 | return $success; |
||
0 ignored issues
–
show
|
|||
315 | } |
||
316 | |||
317 | /** |
||
318 | * {@inheritdoc} |
||
319 | */ |
||
320 | public function increment($key, $offset = 1, $initial = 0, $expire = 0) |
||
321 | { |
||
322 | if ($offset <= 0 || $initial < 0) { |
||
323 | return false; |
||
324 | } |
||
325 | |||
326 | // not doing apc_inc because that one it doesn't let us set an initial |
||
327 | // value or TTL |
||
328 | return $this->doIncrement($key, $offset, $initial, $expire); |
||
329 | } |
||
330 | |||
331 | /** |
||
332 | * {@inheritdoc} |
||
333 | */ |
||
334 | public function decrement($key, $offset = 1, $initial = 0, $expire = 0) |
||
335 | { |
||
336 | if ($offset <= 0 || $initial < 0) { |
||
337 | return false; |
||
338 | } |
||
339 | |||
340 | // not doing apc_dec because that one it doesn't let us set an initial |
||
341 | // value or TTL |
||
342 | return $this->doIncrement($key, -$offset, $initial, $expire); |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * {@inheritdoc} |
||
347 | */ |
||
348 | public function touch($key, $expire) |
||
349 | { |
||
350 | $ttl = $this->ttl($expire); |
||
351 | |||
352 | // shortcut - expiring is similar to deleting, but the former has no |
||
353 | // 1-operation equivalent |
||
354 | if ($ttl < 0) { |
||
355 | return $this->delete($key); |
||
356 | } |
||
357 | |||
358 | // get existing TTL & quit early if it's that one already |
||
359 | $iterator = $this->APCUIterator('/^'.preg_quote($key, '/').'$/', \APC_ITER_VALUE | \APC_ITER_TTL, 1, \APC_LIST_ACTIVE); |
||
360 | if (!$iterator->valid()) { |
||
361 | return false; |
||
362 | } |
||
363 | $current = $iterator->current(); |
||
364 | if (!$current) { |
||
365 | // doesn't exist |
||
366 | return false; |
||
367 | } |
||
368 | if ($current['ttl'] === $ttl) { |
||
369 | // that's the TTL already, no need to reset it |
||
370 | return true; |
||
371 | } |
||
372 | |||
373 | // generate CAS token to safely CAS existing value with new TTL |
||
374 | $value = $current['value']; |
||
375 | $token = serialize($value); |
||
376 | |||
377 | return $this->cas($token, $key, $value, $ttl); |
||
378 | } |
||
379 | |||
380 | /** |
||
381 | * {@inheritdoc} |
||
382 | */ |
||
383 | public function flush() |
||
384 | { |
||
385 | $this->expires = array(); |
||
386 | |||
387 | return $this->apcu_clear_cache(); |
||
388 | } |
||
389 | |||
390 | /** |
||
391 | * {@inheritdoc} |
||
392 | */ |
||
393 | public function getCollection($name) |
||
394 | { |
||
395 | return new Collection($this, $name); |
||
396 | } |
||
397 | |||
398 | /** |
||
399 | * Shared between increment/decrement: both have mostly the same logic |
||
400 | * (decrement just increments a negative value), but need their validation |
||
401 | * split up (increment won't accept negative values). |
||
402 | * |
||
403 | * @param string $key |
||
404 | * @param int $offset |
||
405 | * @param int $initial |
||
406 | * @param int $expire |
||
407 | * |
||
408 | * @return int|bool |
||
409 | */ |
||
410 | protected function doIncrement($key, $offset, $initial, $expire) |
||
411 | { |
||
412 | $ttl = $this->ttl($expire); |
||
413 | |||
414 | /* |
||
415 | * APC has apc_inc & apc_dec, which work great. However, they don't |
||
416 | * allow for a TTL to be set. |
||
417 | * I could use apc_inc & apc_dec & then do a touch, but touch also |
||
418 | * doesn't have an APC implementation & needs a get & cas. That would |
||
419 | * be 2 operations + CAS. |
||
420 | * Instead, I'll just do a get, implement the increase or decrease in |
||
421 | * PHP, then CAS the new value = 1 operation + CAS. |
||
422 | */ |
||
423 | $value = $this->get($key, $token); |
||
424 | if (false === $value) { |
||
425 | // don't even set initial value, it's already expired... |
||
426 | if ($ttl < 0) { |
||
427 | return $initial; |
||
428 | } |
||
429 | |||
430 | // no need for locking - set will do that |
||
431 | $success = $this->add($key, $initial, $ttl); |
||
432 | |||
433 | return $success ? $initial : false; |
||
434 | } |
||
435 | |||
436 | if (!is_numeric($value) || $value < 0) { |
||
437 | return false; |
||
438 | } |
||
439 | |||
440 | $value += $offset; |
||
441 | // value can never be lower than 0 |
||
442 | $value = max(0, $value); |
||
443 | |||
444 | // negative TTLs don't always seem to properly treat the key as deleted |
||
445 | if ($ttl < 0) { |
||
446 | $success = $this->delete($key); |
||
447 | |||
448 | return $success ? $value : false; |
||
449 | } |
||
450 | |||
451 | // no need for locking - cas will do that |
||
452 | $success = $this->cas($token, $key, $value, $ttl); |
||
453 | |||
454 | return $success ? $value : false; |
||
455 | } |
||
456 | |||
457 | /** |
||
458 | * APC expects true TTL, not expiration timestamp. |
||
459 | * |
||
460 | * @param int $expire |
||
461 | * |
||
462 | * @return int TTL in seconds |
||
463 | */ |
||
464 | protected function ttl($expire) |
||
465 | { |
||
466 | // relative time in seconds, <30 days |
||
467 | if ($expire < 30 * 24 * 60 * 60) { |
||
468 | $expire += time(); |
||
469 | } |
||
470 | |||
471 | return $expire ? $expire - time() : 0; |
||
472 | } |
||
473 | |||
474 | /** |
||
475 | * Acquire a lock. If we failed to acquire a lock, it'll automatically try |
||
476 | * again in 1ms, for a maximum of 10 times. |
||
477 | * |
||
478 | * APC provides nothing that would allow us to do CAS. To "emulate" CAS, |
||
479 | * we'll work with locks: all cache writes also briefly create a lock |
||
480 | * cache entry (yup: #writes * 3, for lock & unlock - luckily, they're |
||
481 | * not over the network) |
||
482 | * Writes are disallows when a lock can't be obtained (= locked by |
||
483 | * another write), which makes it possible for us to first retrieve, |
||
484 | * compare & then set in a nob-atomic way. |
||
485 | * However, there's a possibility for interference with direct APC |
||
486 | * access touching the same keys - e.g. other scripts, not using this |
||
487 | * class. If CAS is of importance, make sure the only things touching |
||
488 | * APC on your server is using these classes! |
||
489 | * |
||
490 | * @param string|string[] $keys |
||
491 | * |
||
492 | * @return array Array of successfully locked keys |
||
493 | */ |
||
494 | protected function lock($keys) |
||
495 | { |
||
496 | // both string (single key) and array (multiple) are accepted |
||
497 | $keys = (array) $keys; |
||
498 | |||
499 | $locked = array(); |
||
500 | for ($i = 0; $i < 10; ++$i) { |
||
501 | $locked += $this->acquire($keys); |
||
502 | $keys = array_diff($keys, $locked); |
||
503 | |||
504 | if (empty($keys)) { |
||
505 | break; |
||
506 | } |
||
507 | |||
508 | usleep(1); |
||
509 | } |
||
510 | |||
511 | return $locked; |
||
512 | } |
||
513 | |||
514 | /** |
||
515 | * Acquire a lock - required to provide CAS functionality. |
||
516 | * |
||
517 | * @param string|string[] $keys |
||
518 | * |
||
519 | * @return string[] Array of successfully locked keys |
||
520 | */ |
||
521 | protected function acquire($keys) |
||
522 | { |
||
523 | $keys = (array) $keys; |
||
524 | |||
525 | $values = array(); |
||
526 | foreach ($keys as $key) { |
||
527 | $values["scrapbook.lock.$key"] = null; |
||
528 | } |
||
529 | |||
530 | // there's no point in locking longer than max allowed execution time |
||
531 | // for this script |
||
532 | $ttl = ini_get('max_execution_time'); |
||
533 | |||
534 | // lock these keys, then compile a list of successfully locked keys |
||
535 | // (using the returned failure array) |
||
536 | $result = (array) $this->apcu_add($values, null, $ttl); |
||
537 | $failed = array(); |
||
538 | foreach ($result as $key => $err) { |
||
539 | $failed[] = substr($key, strlen('scrapbook.lock.')); |
||
540 | } |
||
541 | |||
542 | return array_diff($keys, $failed); |
||
543 | } |
||
544 | |||
545 | /** |
||
546 | * Release a lock. |
||
547 | * |
||
548 | * @param string|string[] $keys |
||
549 | * |
||
550 | * @return bool |
||
551 | */ |
||
552 | protected function unlock($keys) |
||
553 | { |
||
554 | $keys = (array) $keys; |
||
555 | foreach ($keys as $i => $key) { |
||
556 | $keys[$i] = "scrapbook.lock.$key"; |
||
557 | } |
||
558 | |||
559 | $this->apcu_delete($keys); |
||
560 | |||
561 | return true; |
||
562 | } |
||
563 | |||
564 | /** |
||
565 | * Store the expiration time for items we're setting in this request, to |
||
566 | * work around APC's behavior of only clearing expires per page request. |
||
567 | * |
||
568 | * @see static::$expires |
||
569 | * |
||
570 | * @param array|string $key |
||
571 | * @param int $ttl |
||
572 | */ |
||
573 | protected function expire($key = array(), $ttl = 0) |
||
574 | { |
||
575 | if (0 === $ttl) { |
||
576 | // when storing indefinitely, there's no point in keeping it around, |
||
577 | // it won't just expire |
||
578 | return; |
||
579 | } |
||
580 | |||
581 | // $key can be both string (1 key) or array (multiple) |
||
582 | $keys = (array) $key; |
||
583 | |||
584 | $time = time() + $ttl; |
||
585 | foreach ($keys as $key) { |
||
586 | $this->expires[$key] = $time; |
||
587 | } |
||
588 | } |
||
589 | |||
590 | /** |
||
591 | * @param string|string[] $key |
||
592 | * @param bool $success |
||
593 | * |
||
594 | * @return mixed|false |
||
595 | */ |
||
596 | protected function apcu_fetch($key, &$success = null) |
||
597 | { |
||
598 | /* |
||
599 | * $key can also be numeric, in which case APC is able to retrieve it, |
||
600 | * but will have an invalid $key in the results array, and trying to |
||
601 | * locate it by its $key in that array will fail with `undefined index`. |
||
602 | * I'll work around this by requesting those values 1 by 1. |
||
603 | */ |
||
604 | if (is_array($key)) { |
||
605 | $nums = array_filter($key, 'is_numeric'); |
||
606 | if ($nums) { |
||
0 ignored issues
–
show
The expression
$nums of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using
Loading history...
|
|||
607 | $values = array(); |
||
608 | foreach ($nums as $k) { |
||
609 | $values[$k] = $this->apcu_fetch((string) $k, $success); |
||
610 | } |
||
611 | |||
612 | $remaining = array_diff($key, $nums); |
||
613 | if ($remaining) { |
||
0 ignored issues
–
show
The expression
$remaining of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using
Loading history...
|
|||
614 | $values += $this->apcu_fetch($remaining, $success2); |
||
615 | $success &= $success2; |
||
616 | } |
||
617 | |||
618 | return $values; |
||
619 | } |
||
620 | } |
||
621 | |||
622 | if (function_exists('apcu_fetch')) { |
||
623 | return apcu_fetch($key, $success); |
||
624 | } else { |
||
625 | return apc_fetch($key, $success); |
||
626 | } |
||
627 | } |
||
628 | |||
629 | /** |
||
630 | * @param string|string[] $key |
||
631 | * @param mixed $var |
||
632 | * @param int $ttl |
||
633 | * |
||
634 | * @return bool|bool[] |
||
635 | */ |
||
636 | protected function apcu_store($key, $var, $ttl = 0) |
||
637 | { |
||
638 | /* |
||
639 | * $key can also be a [$key => $value] array, where key is numeric, |
||
640 | * but got cast to int by PHP. APC doesn't seem to store such numerical |
||
641 | * key, so we'll have to take care of those one by one. |
||
642 | */ |
||
643 | if (is_array($key)) { |
||
644 | $nums = array_filter(array_keys($key), 'is_numeric'); |
||
645 | if ($nums) { |
||
0 ignored issues
–
show
The expression
$nums of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using
Loading history...
|
|||
646 | $success = array(); |
||
647 | $nums = array_intersect_key($key, array_fill_keys($nums, null)); |
||
648 | foreach ($nums as $k => $v) { |
||
649 | $success[$k] = $this->apcu_store((string) $k, $v, $ttl); |
||
650 | } |
||
651 | |||
652 | $remaining = array_diff_key($key, $nums); |
||
653 | if ($remaining) { |
||
0 ignored issues
–
show
The expression
$remaining of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using
Loading history...
|
|||
654 | $success += $this->apcu_store($remaining, $var, $ttl); |
||
655 | } |
||
656 | |||
657 | return $success; |
||
658 | } |
||
659 | } |
||
660 | |||
661 | if (function_exists('apcu_store')) { |
||
662 | return apcu_store($key, $var, $ttl); |
||
663 | } else { |
||
664 | return apc_store($key, $var, $ttl); |
||
665 | } |
||
666 | } |
||
667 | |||
668 | /** |
||
669 | * @param string|string[]|\APCIterator|\APCUIterator $key |
||
670 | * |
||
671 | * @return bool|string[] |
||
672 | */ |
||
673 | protected function apcu_delete($key) |
||
674 | { |
||
675 | if (function_exists('apcu_delete')) { |
||
676 | return apcu_delete($key); |
||
677 | } else { |
||
678 | return apc_delete($key); |
||
679 | } |
||
680 | } |
||
681 | |||
682 | /** |
||
683 | * @param string|string[] $key |
||
684 | * @param mixed $var |
||
685 | * @param int $ttl |
||
686 | * |
||
687 | * @return bool|bool[] |
||
688 | */ |
||
689 | protected function apcu_add($key, $var, $ttl = 0) |
||
690 | { |
||
691 | if (function_exists('apcu_add')) { |
||
692 | return apcu_add($key, $var, $ttl); |
||
693 | } else { |
||
694 | return apc_add($key, $var, $ttl); |
||
695 | } |
||
696 | } |
||
697 | |||
698 | /** |
||
699 | * @return bool |
||
700 | */ |
||
701 | protected function apcu_clear_cache() |
||
702 | { |
||
703 | if (function_exists('apcu_clear_cache')) { |
||
704 | return apcu_clear_cache(); |
||
705 | } else { |
||
706 | return apc_clear_cache('user'); |
||
707 | } |
||
708 | } |
||
709 | |||
710 | /** |
||
711 | * @param string|string[]|null $search |
||
712 | * @param int $format |
||
713 | * @param int $chunk_size |
||
714 | * @param int $list |
||
715 | * |
||
716 | * @return \APCIterator|\APCUIterator |
||
717 | */ |
||
718 | protected function APCUIterator($search = null, $format = null, $chunk_size = null, $list = null) |
||
719 | { |
||
720 | $arguments = func_get_args(); |
||
721 | |||
722 | if (class_exists('APCUIterator', false)) { |
||
723 | // I can't set the defaults parameter values because the APC_ or |
||
724 | // APCU_ constants may not exist, so I'll just initialize from |
||
725 | // func_get_args, not passing those params that haven't been set |
||
726 | $reflect = new \ReflectionClass('APCUIterator'); |
||
727 | |||
728 | return $reflect->newInstanceArgs($arguments); |
||
729 | } else { |
||
730 | array_unshift($arguments, 'user'); |
||
731 | $reflect = new \ReflectionClass('APCIterator'); |
||
732 | |||
733 | return $reflect->newInstanceArgs($arguments); |
||
734 | } |
||
735 | } |
||
736 | } |
||
737 |