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