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); |
||||
0 ignored issues
–
show
It seems like
$result can also be of type true ; however, parameter $arrays of array_merge() does only seem to accept array , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
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); |
||||
0 ignored issues
–
show
$ttl of type string is incompatible with the type integer expected by parameter $ttl of MatthiasMullie\Scrapbook\Adapters\Apc::apcu_add() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
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); |
||||
0 ignored issues
–
show
It seems like
$key can also be of type APCIterator ; however, parameter $key of apcu_delete() does only seem to accept APCuIterator|string|string[] , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
677 | } else { |
||||
678 | return apc_delete($key); |
||||
0 ignored issues
–
show
It seems like
$key can also be of type APCUIterator ; however, parameter $key of apc_delete() does only seem to accept APCIterator|string|string[] , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
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); |
||||
0 ignored issues
–
show
It seems like
$key can also be of type string[] ; however, parameter $key of apc_add() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
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 |