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 $metadata = []; 31 protected $newMetadata = []; 32 protected $innerItem; 33 protected $poolHash; 34 protected $isTaggable = false; 35 36 /** 37 * {@inheritdoc} 38 */ 39 public function getKey(): string 40 { 41 return $this->key; 42 } 43 44 /** 45 * {@inheritdoc} 46 * 47 * @return mixed 48 */ 49 public function get() 50 { 51 return $this->value; 52 } 53 54 /** 55 * {@inheritdoc} 56 */ 57 public function isHit(): bool 58 { 59 return $this->isHit; 60 } 61 62 /** 63 * {@inheritdoc} 64 * 65 * @return $this 66 */ 67 public function set($value): self 68 { 69 $this->value = $value; 70 71 return $this; 72 } 73 74 /** 75 * {@inheritdoc} 76 * 77 * @return $this 78 */ 79 public function expiresAt($expiration): self 80 { 81 if (null === $expiration) { 82 $this->expiry = null; 83 } elseif ($expiration instanceof \DateTimeInterface) { 84 $this->expiry = (float) $expiration->format('U.u'); 85 } else { 86 throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given.', \is_object($expiration) ? \get_class($expiration) : \gettype($expiration))); 87 } 88 89 return $this; 90 } 91 92 /** 93 * {@inheritdoc} 94 * 95 * @return $this 96 */ 97 public function expiresAfter($time): self 98 { 99 if (null === $time) { 100 $this->expiry = null; 101 } elseif ($time instanceof \DateInterval) { 102 $this->expiry = microtime(true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u'); 103 } elseif (\is_int($time)) { 104 $this->expiry = $time + microtime(true); 105 } else { 106 throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \is_object($time) ? \get_class($time) : \gettype($time))); 107 } 108 109 return $this; 110 } 111 112 /** 113 * {@inheritdoc} 114 */ 115 public function tag($tags): ItemInterface 116 { 117 if (!$this->isTaggable) { 118 throw new LogicException(sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key)); 119 } 120 if (!is_iterable($tags)) { 121 $tags = [$tags]; 122 } 123 foreach ($tags as $tag) { 124 if (!\is_string($tag) && !(\is_object($tag) && method_exists($tag, '__toString'))) { 125 throw new InvalidArgumentException(sprintf('Cache tag must be string or object that implements __toString(), "%s" given.', \is_object($tag) ? \get_class($tag) : \gettype($tag))); 126 } 127 $tag = (string) $tag; 128 if (isset($this->newMetadata[self::METADATA_TAGS][$tag])) { 129 continue; 130 } 131 if ('' === $tag) { 132 throw new InvalidArgumentException('Cache tag length must be greater than zero.'); 133 } 134 if (false !== strpbrk($tag, self::RESERVED_CHARACTERS)) { 135 throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters "%s".', $tag, self::RESERVED_CHARACTERS)); 136 } 137 $this->newMetadata[self::METADATA_TAGS][$tag] = $tag; 138 } 139 140 return $this; 141 } 142 143 /** 144 * {@inheritdoc} 145 */ 146 public function getMetadata(): array 147 { 148 return $this->metadata; 149 } 150 151 /** 152 * Returns the list of tags bound to the value coming from the pool storage if any. 153 * 154 * @deprecated since Symfony 4.2, use the "getMetadata()" method instead. 155 */ 156 public function getPreviousTags(): array 157 { 158 @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "getMetadata()" method instead.', __METHOD__), \E_USER_DEPRECATED); 159 160 return $this->metadata[self::METADATA_TAGS] ?? []; 161 } 162 163 /** 164 * Validates a cache key according to PSR-6. 165 * 166 * @param mixed $key The key to validate 167 * 168 * @throws InvalidArgumentException When $key is not valid 169 */ 170 public static function validateKey($key): string 171 { 172 if (!\is_string($key)) { 173 throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key))); 174 } 175 if ('' === $key) { 176 throw new InvalidArgumentException('Cache key length must be greater than zero.'); 177 } 178 if (false !== strpbrk($key, self::RESERVED_CHARACTERS)) { 179 throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters "%s".', $key, self::RESERVED_CHARACTERS)); 180 } 181 182 return $key; 183 } 184 185 /** 186 * Internal logging helper. 187 * 188 * @internal 189 */ 190 public static function log(?LoggerInterface $logger, string $message, array $context = []) 191 { 192 if ($logger) { 193 $logger->warning($message, $context); 194 } else { 195 $replace = []; 196 foreach ($context as $k => $v) { 197 if (is_scalar($v)) { 198 $replace['{'.$k.'}'] = $v; 199 } 200 } 201 @trigger_error(strtr($message, $replace), \E_USER_WARNING); 202 } 203 } 204} 205