1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\Cache; 13 14use Psr\Log\LoggerInterface; 15use Symfony\Component\Cache\Exception\InvalidArgumentException; 16use Symfony\Component\Cache\Exception\LogicException; 17use Symfony\Contracts\Cache\ItemInterface; 18 19/** 20 * @author Nicolas Grekas <p@tchwork.com> 21 */ 22final class CacheItem implements ItemInterface 23{ 24 private const METADATA_EXPIRY_OFFSET = 1527506807; 25 26 protected $key; 27 protected $value; 28 protected $isHit = false; 29 protected $expiry; 30 protected $defaultLifetime; 31 protected $metadata = []; 32 protected $newMetadata = []; 33 protected $innerItem; 34 protected $poolHash; 35 protected $isTaggable = false; 36 37 /** 38 * {@inheritdoc} 39 */ 40 public function getKey(): string 41 { 42 return $this->key; 43 } 44 45 /** 46 * {@inheritdoc} 47 */ 48 public function get() 49 { 50 return $this->value; 51 } 52 53 /** 54 * {@inheritdoc} 55 */ 56 public function isHit(): bool 57 { 58 return $this->isHit; 59 } 60 61 /** 62 * {@inheritdoc} 63 * 64 * @return $this 65 */ 66 public function set($value): self 67 { 68 $this->value = $value; 69 70 return $this; 71 } 72 73 /** 74 * {@inheritdoc} 75 * 76 * @return $this 77 */ 78 public function expiresAt($expiration): self 79 { 80 if (null === $expiration) { 81 $this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null; 82 } elseif ($expiration instanceof \DateTimeInterface) { 83 $this->expiry = (float) $expiration->format('U.u'); 84 } else { 85 throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given.', \is_object($expiration) ? \get_class($expiration) : \gettype($expiration))); 86 } 87 88 return $this; 89 } 90 91 /** 92 * {@inheritdoc} 93 * 94 * @return $this 95 */ 96 public function expiresAfter($time): self 97 { 98 if (null === $time) { 99 $this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null; 100 } elseif ($time instanceof \DateInterval) { 101 $this->expiry = microtime(true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u'); 102 } elseif (\is_int($time)) { 103 $this->expiry = $time + microtime(true); 104 } else { 105 throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \is_object($time) ? \get_class($time) : \gettype($time))); 106 } 107 108 return $this; 109 } 110 111 /** 112 * {@inheritdoc} 113 */ 114 public function tag($tags): ItemInterface 115 { 116 if (!$this->isTaggable) { 117 throw new LogicException(sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key)); 118 } 119 if (!is_iterable($tags)) { 120 $tags = [$tags]; 121 } 122 foreach ($tags as $tag) { 123 if (!\is_string($tag)) { 124 throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given.', \is_object($tag) ? \get_class($tag) : \gettype($tag))); 125 } 126 if (isset($this->newMetadata[self::METADATA_TAGS][$tag])) { 127 continue; 128 } 129 if ('' === $tag) { 130 throw new InvalidArgumentException('Cache tag length must be greater than zero.'); 131 } 132 if (false !== strpbrk($tag, self::RESERVED_CHARACTERS)) { 133 throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters "%s".', $tag, self::RESERVED_CHARACTERS)); 134 } 135 $this->newMetadata[self::METADATA_TAGS][$tag] = $tag; 136 } 137 138 return $this; 139 } 140 141 /** 142 * {@inheritdoc} 143 */ 144 public function getMetadata(): array 145 { 146 return $this->metadata; 147 } 148 149 /** 150 * Validates a cache key according to PSR-6. 151 * 152 * @param string $key The key to validate 153 * 154 * @throws InvalidArgumentException When $key is not valid 155 */ 156 public static function validateKey($key): string 157 { 158 if (!\is_string($key)) { 159 throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key))); 160 } 161 if ('' === $key) { 162 throw new InvalidArgumentException('Cache key length must be greater than zero.'); 163 } 164 if (false !== strpbrk($key, self::RESERVED_CHARACTERS)) { 165 throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters "%s".', $key, self::RESERVED_CHARACTERS)); 166 } 167 168 return $key; 169 } 170 171 /** 172 * Internal logging helper. 173 * 174 * @internal 175 */ 176 public static function log(?LoggerInterface $logger, string $message, array $context = []) 177 { 178 if ($logger) { 179 $logger->warning($message, $context); 180 } else { 181 $replace = []; 182 foreach ($context as $k => $v) { 183 if (is_scalar($v)) { 184 $replace['{'.$k.'}'] = $v; 185 } 186 } 187 @trigger_error(strtr($message, $replace), E_USER_WARNING); 188 } 189 } 190} 191