matthiasmullie /
scrapbook
| 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 |