1<?php
2/*
3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14 *
15 * This software consists of voluntary contributions made by many individuals
16 * and is licensed under the MIT license. For more information, see
17 * <http://www.doctrine-project.org>.
18 */
19
20namespace Doctrine\Common\Cache;
21
22/**
23 * Base class for cache provider implementations.
24 *
25 * @since  2.2
26 * @author Benjamin Eberlei <kontakt@beberlei.de>
27 * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
28 * @author Jonathan Wage <jonwage@gmail.com>
29 * @author Roman Borschel <roman@code-factory.org>
30 * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
31 */
32abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, MultiGetCache, MultiPutCache
33{
34    const DOCTRINE_NAMESPACE_CACHEKEY = 'DoctrineNamespaceCacheKey[%s]';
35
36    /**
37     * The namespace to prefix all cache ids with.
38     *
39     * @var string
40     */
41    private $namespace = '';
42
43    /**
44     * The namespace version.
45     *
46     * @var integer|null
47     */
48    private $namespaceVersion;
49
50    /**
51     * Sets the namespace to prefix all cache ids with.
52     *
53     * @param string $namespace
54     *
55     * @return void
56     */
57    public function setNamespace($namespace)
58    {
59        $this->namespace        = (string) $namespace;
60        $this->namespaceVersion = null;
61    }
62
63    /**
64     * Retrieves the namespace that prefixes all cache ids.
65     *
66     * @return string
67     */
68    public function getNamespace()
69    {
70        return $this->namespace;
71    }
72
73    /**
74     * {@inheritdoc}
75     */
76    public function fetch($id)
77    {
78        return $this->doFetch($this->getNamespacedId($id));
79    }
80
81    /**
82     * {@inheritdoc}
83     */
84    public function fetchMultiple(array $keys)
85    {
86        if (empty($keys)) {
87            return array();
88        }
89
90        // note: the array_combine() is in place to keep an association between our $keys and the $namespacedKeys
91        $namespacedKeys = array_combine($keys, array_map(array($this, 'getNamespacedId'), $keys));
92        $items          = $this->doFetchMultiple($namespacedKeys);
93        $foundItems     = array();
94
95        // no internal array function supports this sort of mapping: needs to be iterative
96        // this filters and combines keys in one pass
97        foreach ($namespacedKeys as $requestedKey => $namespacedKey) {
98            if (isset($items[$namespacedKey]) || array_key_exists($namespacedKey, $items)) {
99                $foundItems[$requestedKey] = $items[$namespacedKey];
100            }
101        }
102
103        return $foundItems;
104    }
105
106    /**
107     * {@inheritdoc}
108     */
109    public function saveMultiple(array $keysAndValues, $lifetime = 0)
110    {
111        $namespacedKeysAndValues = array();
112        foreach ($keysAndValues as $key => $value) {
113            $namespacedKeysAndValues[$this->getNamespacedId($key)] = $value;
114        }
115
116        return $this->doSaveMultiple($namespacedKeysAndValues, $lifetime);
117    }
118
119    /**
120     * {@inheritdoc}
121     */
122    public function contains($id)
123    {
124        return $this->doContains($this->getNamespacedId($id));
125    }
126
127    /**
128     * {@inheritdoc}
129     */
130    public function save($id, $data, $lifeTime = 0)
131    {
132        return $this->doSave($this->getNamespacedId($id), $data, $lifeTime);
133    }
134
135    /**
136     * {@inheritdoc}
137     */
138    public function delete($id)
139    {
140        return $this->doDelete($this->getNamespacedId($id));
141    }
142
143    /**
144     * {@inheritdoc}
145     */
146    public function getStats()
147    {
148        return $this->doGetStats();
149    }
150
151    /**
152     * {@inheritDoc}
153     */
154    public function flushAll()
155    {
156        return $this->doFlush();
157    }
158
159    /**
160     * {@inheritDoc}
161     */
162    public function deleteAll()
163    {
164        $namespaceCacheKey = $this->getNamespaceCacheKey();
165        $namespaceVersion  = $this->getNamespaceVersion() + 1;
166
167        if ($this->doSave($namespaceCacheKey, $namespaceVersion)) {
168            $this->namespaceVersion = $namespaceVersion;
169
170            return true;
171        }
172
173        return false;
174    }
175
176    /**
177     * Prefixes the passed id with the configured namespace value.
178     *
179     * @param string $id The id to namespace.
180     *
181     * @return string The namespaced id.
182     */
183    private function getNamespacedId($id)
184    {
185        $namespaceVersion  = $this->getNamespaceVersion();
186
187        return sprintf('%s[%s][%s]', $this->namespace, $id, $namespaceVersion);
188    }
189
190    /**
191     * Returns the namespace cache key.
192     *
193     * @return string
194     */
195    private function getNamespaceCacheKey()
196    {
197        return sprintf(self::DOCTRINE_NAMESPACE_CACHEKEY, $this->namespace);
198    }
199
200    /**
201     * Returns the namespace version.
202     *
203     * @return integer
204     */
205    private function getNamespaceVersion()
206    {
207        if (null !== $this->namespaceVersion) {
208            return $this->namespaceVersion;
209        }
210
211        $namespaceCacheKey = $this->getNamespaceCacheKey();
212        $this->namespaceVersion = $this->doFetch($namespaceCacheKey) ?: 1;
213
214        return $this->namespaceVersion;
215    }
216
217    /**
218     * Default implementation of doFetchMultiple. Each driver that supports multi-get should owerwrite it.
219     *
220     * @param array $keys Array of keys to retrieve from cache
221     * @return array Array of values retrieved for the given keys.
222     */
223    protected function doFetchMultiple(array $keys)
224    {
225        $returnValues = array();
226
227        foreach ($keys as $key) {
228            if (false !== ($item = $this->doFetch($key)) || $this->doContains($key)) {
229                $returnValues[$key] = $item;
230            }
231        }
232
233        return $returnValues;
234    }
235
236    /**
237     * Fetches an entry from the cache.
238     *
239     * @param string $id The id of the cache entry to fetch.
240     *
241     * @return mixed|false The cached data or FALSE, if no cache entry exists for the given id.
242     */
243    abstract protected function doFetch($id);
244
245    /**
246     * Tests if an entry exists in the cache.
247     *
248     * @param string $id The cache id of the entry to check for.
249     *
250     * @return bool TRUE if a cache entry exists for the given cache id, FALSE otherwise.
251     */
252    abstract protected function doContains($id);
253
254    /**
255     * Default implementation of doSaveMultiple. Each driver that supports multi-put should override it.
256     *
257     * @param array $keysAndValues  Array of keys and values to save in cache
258     * @param int   $lifetime       The lifetime. If != 0, sets a specific lifetime for these
259     *                              cache entries (0 => infinite lifeTime).
260     *
261     * @return bool TRUE if the operation was successful, FALSE if it wasn't.
262     */
263    protected function doSaveMultiple(array $keysAndValues, $lifetime = 0)
264    {
265        $success = true;
266
267        foreach ($keysAndValues as $key => $value) {
268            if (!$this->doSave($key, $value, $lifetime)) {
269                $success = false;
270            }
271        }
272
273        return $success;
274    }
275
276    /**
277     * Puts data into the cache.
278     *
279     * @param string $id       The cache id.
280     * @param string $data     The cache entry/data.
281     * @param int    $lifeTime The lifetime. If != 0, sets a specific lifetime for this
282     *                           cache entry (0 => infinite lifeTime).
283     *
284     * @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise.
285     */
286    abstract protected function doSave($id, $data, $lifeTime = 0);
287
288    /**
289     * Deletes a cache entry.
290     *
291     * @param string $id The cache id.
292     *
293     * @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise.
294     */
295    abstract protected function doDelete($id);
296
297    /**
298     * Flushes all cache entries.
299     *
300     * @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise.
301     */
302    abstract protected function doFlush();
303
304    /**
305     * Retrieves cached information from the data store.
306     *
307     * @since 2.2
308     *
309     * @return array|null An associative array with server's statistics if available, NULL otherwise.
310     */
311    abstract protected function doGetStats();
312}
313