composer package updates
[openemr.git] / vendor / zendframework / zend-cache / src / Psr / SimpleCache / SimpleCacheDecorator.php
blobdf7cba9d62b7f632034b5b2d464ced8609266876
1 <?php
2 /**
3 * @see https://github.com/zendframework/zend-cache for the canonical source repository
4 * @copyright Copyright (c) 2018 Zend Technologies USA Inc. (https://www.zend.com)
5 * @license https://github.com/zendframework/zend-cache/blob/master/LICENSE.md New BSD License
6 */
8 namespace Zend\Cache\Psr\SimpleCache;
10 use DateInterval;
11 use DateTimeImmutable;
12 use DateTimeZone;
13 use Exception;
14 use Psr\SimpleCache\CacheInterface as SimpleCacheInterface;
15 use Throwable;
16 use Traversable;
17 use Zend\Cache\Exception\InvalidArgumentException as ZendCacheInvalidArgumentException;
18 use Zend\Cache\Psr\SerializationTrait;
19 use Zend\Cache\Storage\ClearByNamespaceInterface;
20 use Zend\Cache\Storage\FlushableInterface;
21 use Zend\Cache\Storage\StorageInterface;
23 /**
24 * Decorate a zend-cache storage adapter for usage as a PSR-16 implementation.
26 class SimpleCacheDecorator implements SimpleCacheInterface
28 use SerializationTrait;
30 /**
31 * Characters reserved by PSR-16 that are not valid in cache keys.
33 const INVALID_KEY_CHARS = ':@{}()/\\';
35 /**
36 * @var bool
38 private $providesPerItemTtl = true;
40 /**
41 * @var StorageInterface
43 private $storage;
45 /**
46 * Reference used by storage when calling getItem() to indicate status of
47 * operation.
49 * @var null|bool
51 private $success;
53 /**
54 * @var DateTimeZone
56 private $utc;
58 public function __construct(StorageInterface $storage)
60 if ($this->isSerializationRequired($storage)) {
61 throw new SimpleCacheException(sprintf(
62 'The storage adapter "%s" requires a serializer plugin; please see'
63 . ' https://docs.zendframework.com/zend-cache/storage/plugin/#quick-start'
64 . ' for details on how to attach the plugin to your adapter.',
65 get_class($storage)
66 ));
69 $this->memoizeTtlCapabilities($storage);
70 $this->storage = $storage;
71 $this->utc = new DateTimeZone('UTC');
74 /**
75 * {@inheritDoc}
77 public function get($key, $default = null)
79 $this->validateKey($key);
81 $this->success = null;
82 try {
83 $result = $this->storage->getItem($key, $this->success);
84 } catch (Throwable $e) {
85 throw static::translateException($e);
86 } catch (Exception $e) {
87 throw static::translateException($e);
90 $result = $result === null ? $default : $result;
91 return $this->success ? $result : $default;
94 /**
95 * {@inheritDoc}
97 public function set($key, $value, $ttl = null)
99 $this->validateKey($key);
100 $ttl = $this->convertTtlToInteger($ttl);
102 // PSR-16 states that 0 or negative TTL values should result in cache
103 // invalidation for the item.
104 if (null !== $ttl && 1 > $ttl) {
105 return $this->delete($key);
108 // If a positive TTL is set, but the adapter does not support per-item
109 // TTL, we return false immediately.
110 if (null !== $ttl && ! $this->providesPerItemTtl) {
111 return false;
114 $options = $this->storage->getOptions();
115 $previousTtl = $options->getTtl();
116 $options->setTtl($ttl);
118 try {
119 $result = $this->storage->setItem($key, $value);
120 } catch (Throwable $e) {
121 throw static::translateException($e);
122 } catch (Exception $e) {
123 throw static::translateException($e);
124 } finally {
125 $options->setTtl($previousTtl);
128 return $result;
132 * {@inheritDoc}
134 public function delete($key)
136 $this->validateKey($key);
138 try {
139 return null !== $this->storage->removeItem($key);
140 } catch (Throwable $e) {
141 return false;
142 } catch (Exception $e) {
143 return false;
148 * {@inheritDoc}
150 public function clear()
152 $namespace = $this->storage->getOptions()->getNamespace();
154 if ($this->storage instanceof ClearByNamespaceInterface && $namespace) {
155 return $this->storage->clearByNamespace($namespace);
158 if ($this->storage instanceof FlushableInterface) {
159 return $this->storage->flush();
162 return false;
166 * {@inheritDoc}
168 public function getMultiple($keys, $default = null)
170 $keys = $this->convertIterableToArray($keys, false, __FUNCTION__);
171 array_walk($keys, [$this, 'validateKey']);
173 try {
174 $results = $this->storage->getItems($keys);
175 } catch (Throwable $e) {
176 throw static::translateException($e);
177 } catch (Exception $e) {
178 throw static::translateException($e);
181 foreach ($keys as $key) {
182 if (! isset($results[$key])) {
183 $results[$key] = $default;
184 continue;
188 return $results;
192 * {@inheritDoc}
194 public function setMultiple($values, $ttl = null)
196 $values = $this->convertIterableToArray($values, true, __FUNCTION__);
197 $keys = array_keys($values);
198 $ttl = $this->convertTtlToInteger($ttl);
200 // PSR-16 states that 0 or negative TTL values should result in cache
201 // invalidation for the items.
202 if (null !== $ttl && 1 > $ttl) {
203 return $this->deleteMultiple($keys);
206 array_walk($keys, [$this, 'validateKey']);
208 // If a positive TTL is set, but the adapter does not support per-item
209 // TTL, we return false -- but not until after we validate keys.
210 if (null !== $ttl && ! $this->providesPerItemTtl) {
211 return false;
214 $options = $this->storage->getOptions();
215 $previousTtl = $options->getTtl();
216 $options->setTtl($ttl);
218 try {
219 $result = $this->storage->setItems($values);
220 } catch (Throwable $e) {
221 throw static::translateException($e);
222 } catch (Exception $e) {
223 throw static::translateException($e);
224 } finally {
225 $options->setTtl($previousTtl);
228 if (empty($result)) {
229 return true;
232 foreach ($result as $index => $key) {
233 if (! $this->storage->hasItem($key)) {
234 unset($result[$index]);
238 return empty($result);
242 * {@inheritDoc}
244 public function deleteMultiple($keys)
246 $keys = $this->convertIterableToArray($keys, false, __FUNCTION__);
247 if (empty($keys)) {
248 return true;
251 array_walk($keys, [$this, 'validateKey']);
253 try {
254 $result = $this->storage->removeItems($keys);
255 } catch (Throwable $e) {
256 return false;
257 } catch (Exception $e) {
258 return false;
261 if (empty($result)) {
262 return true;
265 foreach ($result as $index => $key) {
266 if (! $this->storage->hasItem($key)) {
267 unset($result[$index]);
271 return empty($result);
275 * {@inheritDoc}
277 public function has($key)
279 $this->validateKey($key);
281 try {
282 return $this->storage->hasItem($key);
283 } catch (Throwable $e) {
284 throw static::translateException($e);
285 } catch (Exception $e) {
286 throw static::translateException($e);
291 * @param Throwable|Exception $e
292 * @return SimpleCacheException
294 private static function translateException($e)
296 $exceptionClass = $e instanceof ZendCacheInvalidArgumentException
297 ? SimpleCacheInvalidArgumentException::class
298 : SimpleCacheException::class;
300 return new $exceptionClass($e->getMessage(), $e->getCode(), $e);
304 * @param string $key
305 * @return void
306 * @throws SimpleCacheInvalidArgumentException if key is invalid
308 private function validateKey($key)
310 if ('' === $key) {
311 throw new SimpleCacheInvalidArgumentException(
312 'Invalid key provided; cannot be empty'
316 if (0 === $key) {
317 // cache/integration-tests erroneously tests that ['0' => 'value']
318 // is a valid payload to setMultiple(). However, PHP silently
319 // converts '0' to 0, which would normally be invalid. For now,
320 // we need to catch just this single value so tests pass.
321 // I have filed an issue to correct the test:
322 // https://github.com/php-cache/integration-tests/issues/92
323 return $key;
326 if (! is_string($key)) {
327 throw new SimpleCacheInvalidArgumentException(sprintf(
328 'Invalid key provided of type "%s"%s; must be a string',
329 is_object($key) ? get_class($key) : gettype($key),
330 is_scalar($key) ? sprintf(' (%s)', var_export($key, true)) : ''
334 $regex = sprintf('/[%s]/', preg_quote(self::INVALID_KEY_CHARS, '/'));
335 if (preg_match($regex, $key)) {
336 throw new SimpleCacheInvalidArgumentException(sprintf(
337 'Invalid key "%s" provided; cannot contain any of (%s)',
338 $key,
339 self::INVALID_KEY_CHARS
343 if (preg_match('/^.{65,}/u', $key)) {
344 throw new SimpleCacheInvalidArgumentException(sprintf(
345 'Invalid key "%s" provided; key is too long. Must be no more than 64 characters',
346 $key
352 * Determine if the storage adapter provides per-item TTL capabilities
354 * @param StorageInterface $storage
355 * @return void
357 private function memoizeTtlCapabilities(StorageInterface $storage)
359 $capabilities = $storage->getCapabilities();
360 $this->providesPerItemTtl = $capabilities->getStaticTtl() && (0 < $capabilities->getMinTtl());
364 * @param int|DateInterval
365 * @return null|int
366 * @throws SimpleCacheInvalidArgumentException for invalid arguments
368 private function convertTtlToInteger($ttl)
370 // null === absence of a TTL
371 if (null === $ttl) {
372 return null;
375 // integers are always okay
376 if (is_int($ttl)) {
377 return $ttl;
380 // Numeric strings evaluating to integers can be cast
381 if (is_string($ttl)
382 && ('0' === $ttl
383 || preg_match('/^[1-9][0-9]+$/', $ttl)
386 return (int) $ttl;
389 // DateIntervals require conversion
390 if ($ttl instanceof DateInterval) {
391 $now = new DateTimeImmutable('now', $this->utc);
392 $end = $now->add($ttl);
393 return $end->getTimestamp() - $now->getTimestamp();
396 // All others are invalid
397 throw new SimpleCacheInvalidArgumentException(sprintf(
398 'Invalid TTL "%s" provided; must be null, an integer, or a %s instance',
399 is_object($ttl) ? get_class($ttl) : var_export($ttl, true),
400 DateInterval::class
405 * @param array|iterable $iterable
406 * @param bool $useKeys Whether or not to preserve keys during conversion
407 * @param string $forMethod Method that called this one; used for reporting
408 * invalid values.
409 * @return array
410 * @throws SimpleCacheInvalidArgumentException for invalid $iterable values
412 private function convertIterableToArray($iterable, $useKeys, $forMethod)
414 if (is_array($iterable)) {
415 return $iterable;
418 if (! $iterable instanceof Traversable) {
419 throw new SimpleCacheInvalidArgumentException(sprintf(
420 'Invalid value provided to %s::%s; must be an array or Traversable',
421 __CLASS__,
422 $forMethod
426 $array = [];
427 foreach ($iterable as $key => $value) {
428 if (! $useKeys) {
429 $array[] = $value;
430 continue;
433 if (! is_string($key) && ! is_int($key) && ! is_float($key)) {
434 throw new SimpleCacheInvalidArgumentException(sprintf(
435 'Invalid key detected of type "%s"; must be a scalar',
436 is_object($key) ? get_class($key) : gettype($key)
439 $array[$key] = $value;
441 return $array;