first commit

This commit is contained in:
root
2020-02-19 16:42:35 +01:00
commit d668d90f82
2224 changed files with 334338 additions and 0 deletions

View File

@ -0,0 +1,105 @@
<?php
namespace Doctrine\Common\Cache;
use const PHP_VERSION_ID;
use function apc_cache_info;
use function apc_clear_cache;
use function apc_delete;
use function apc_exists;
use function apc_fetch;
use function apc_sma_info;
use function apc_store;
/**
* APC cache provider.
*
* @deprecated since version 1.6, use ApcuCache instead
*
* @link www.doctrine-project.org
*/
class ApcCache extends CacheProvider
{
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return apc_fetch($id);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return apc_exists($id);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
return apc_store($id, $data, $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
// apc_delete returns false if the id does not exist
return apc_delete($id) || ! apc_exists($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return apc_clear_cache() && apc_clear_cache('user');
}
/**
* {@inheritdoc}
*/
protected function doFetchMultiple(array $keys)
{
return apc_fetch($keys) ?: [];
}
/**
* {@inheritdoc}
*/
protected function doSaveMultiple(array $keysAndValues, $lifetime = 0)
{
$result = apc_store($keysAndValues, null, $lifetime);
return empty($result);
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$info = apc_cache_info('', true);
$sma = apc_sma_info();
// @TODO - Temporary fix @see https://github.com/krakjoe/apcu/pull/42
if (PHP_VERSION_ID >= 50500) {
$info['num_hits'] = $info['num_hits'] ?? $info['nhits'];
$info['num_misses'] = $info['num_misses'] ?? $info['nmisses'];
$info['start_time'] = $info['start_time'] ?? $info['stime'];
}
return [
Cache::STATS_HITS => $info['num_hits'],
Cache::STATS_MISSES => $info['num_misses'],
Cache::STATS_UPTIME => $info['start_time'],
Cache::STATS_MEMORY_USAGE => $info['mem_size'],
Cache::STATS_MEMORY_AVAILABLE => $sma['avail_mem'],
];
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace Doctrine\Common\Cache;
use function apcu_cache_info;
use function apcu_clear_cache;
use function apcu_delete;
use function apcu_exists;
use function apcu_fetch;
use function apcu_sma_info;
use function apcu_store;
use function count;
/**
* APCu cache provider.
*
* @link www.doctrine-project.org
*/
class ApcuCache extends CacheProvider
{
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return apcu_fetch($id);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return apcu_exists($id);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
return apcu_store($id, $data, $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
// apcu_delete returns false if the id does not exist
return apcu_delete($id) || ! apcu_exists($id);
}
/**
* {@inheritdoc}
*/
protected function doDeleteMultiple(array $keys)
{
$result = apcu_delete($keys);
return $result !== false && count($result) !== count($keys);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return apcu_clear_cache();
}
/**
* {@inheritdoc}
*/
protected function doFetchMultiple(array $keys)
{
return apcu_fetch($keys) ?: [];
}
/**
* {@inheritdoc}
*/
protected function doSaveMultiple(array $keysAndValues, $lifetime = 0)
{
$result = apcu_store($keysAndValues, null, $lifetime);
return empty($result);
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$info = apcu_cache_info(true);
$sma = apcu_sma_info();
return [
Cache::STATS_HITS => $info['num_hits'],
Cache::STATS_MISSES => $info['num_misses'],
Cache::STATS_UPTIME => $info['start_time'],
Cache::STATS_MEMORY_USAGE => $info['mem_size'],
Cache::STATS_MEMORY_AVAILABLE => $sma['avail_mem'],
];
}
}

View File

@ -0,0 +1,113 @@
<?php
namespace Doctrine\Common\Cache;
use function time;
/**
* Array cache driver.
*
* @link www.doctrine-project.org
*/
class ArrayCache extends CacheProvider
{
/** @var array[] $data each element being a tuple of [$data, $expiration], where the expiration is int|bool */
private $data = [];
/** @var int */
private $hitsCount = 0;
/** @var int */
private $missesCount = 0;
/** @var int */
private $upTime;
/**
* {@inheritdoc}
*/
public function __construct()
{
$this->upTime = time();
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
if (! $this->doContains($id)) {
$this->missesCount += 1;
return false;
}
$this->hitsCount += 1;
return $this->data[$id][0];
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
if (! isset($this->data[$id])) {
return false;
}
$expiration = $this->data[$id][1];
if ($expiration && $expiration < time()) {
$this->doDelete($id);
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
$this->data[$id] = [$data, $lifeTime ? time() + $lifeTime : false];
return true;
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
unset($this->data[$id]);
return true;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
$this->data = [];
return true;
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
return [
Cache::STATS_HITS => $this->hitsCount,
Cache::STATS_MISSES => $this->missesCount,
Cache::STATS_UPTIME => $this->upTime,
Cache::STATS_MEMORY_USAGE => null,
Cache::STATS_MEMORY_AVAILABLE => null,
];
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace Doctrine\Common\Cache;
/**
* Interface for cache drivers.
*
* @link www.doctrine-project.org
*/
interface Cache
{
public const STATS_HITS = 'hits';
public const STATS_MISSES = 'misses';
public const STATS_UPTIME = 'uptime';
public const STATS_MEMORY_USAGE = 'memory_usage';
public const STATS_MEMORY_AVAILABLE = 'memory_available';
/**
* Only for backward compatibility (may be removed in next major release)
*
* @deprecated
*/
public const STATS_MEMORY_AVAILIABLE = 'memory_available';
/**
* Fetches an entry from the cache.
*
* @param string $id The id of the cache entry to fetch.
*
* @return mixed The cached data or FALSE, if no cache entry exists for the given id.
*/
public function fetch($id);
/**
* Tests if an entry exists in the cache.
*
* @param string $id The cache id of the entry to check for.
*
* @return bool TRUE if a cache entry exists for the given cache id, FALSE otherwise.
*/
public function contains($id);
/**
* Puts data into the cache.
*
* If a cache entry with the given id already exists, its data will be replaced.
*
* @param string $id The cache id.
* @param mixed $data The cache entry/data.
* @param int $lifeTime The lifetime in number of seconds for this cache entry.
* If zero (the default), the entry never expires (although it may be deleted from the cache
* to make place for other entries).
*
* @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise.
*/
public function save($id, $data, $lifeTime = 0);
/**
* Deletes a cache entry.
*
* @param string $id The cache id.
*
* @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise.
* Deleting a non-existing entry is considered successful.
*/
public function delete($id);
/**
* Retrieves cached information from the data store.
*
* The server's statistics array has the following values:
*
* - <b>hits</b>
* Number of keys that have been requested and found present.
*
* - <b>misses</b>
* Number of items that have been requested and not found.
*
* - <b>uptime</b>
* Time that the server is running.
*
* - <b>memory_usage</b>
* Memory used by this server to store items.
*
* - <b>memory_available</b>
* Memory allowed to use for storage.
*
* @return array|null An associative array with server's statistics if available, NULL otherwise.
*/
public function getStats();
}

View File

@ -0,0 +1,325 @@
<?php
namespace Doctrine\Common\Cache;
use function array_combine;
use function array_key_exists;
use function array_map;
use function sprintf;
/**
* Base class for cache provider implementations.
*/
abstract class CacheProvider implements Cache, FlushableCache, ClearableCache, MultiOperationCache
{
public const DOCTRINE_NAMESPACE_CACHEKEY = 'DoctrineNamespaceCacheKey[%s]';
/**
* The namespace to prefix all cache ids with.
*
* @var string
*/
private $namespace = '';
/**
* The namespace version.
*
* @var int|null
*/
private $namespaceVersion;
/**
* Sets the namespace to prefix all cache ids with.
*
* @param string $namespace
*
* @return void
*/
public function setNamespace($namespace)
{
$this->namespace = (string) $namespace;
$this->namespaceVersion = null;
}
/**
* Retrieves the namespace that prefixes all cache ids.
*
* @return string
*/
public function getNamespace()
{
return $this->namespace;
}
/**
* {@inheritdoc}
*/
public function fetch($id)
{
return $this->doFetch($this->getNamespacedId($id));
}
/**
* {@inheritdoc}
*/
public function fetchMultiple(array $keys)
{
if (empty($keys)) {
return [];
}
// note: the array_combine() is in place to keep an association between our $keys and the $namespacedKeys
$namespacedKeys = array_combine($keys, array_map([$this, 'getNamespacedId'], $keys));
$items = $this->doFetchMultiple($namespacedKeys);
$foundItems = [];
// no internal array function supports this sort of mapping: needs to be iterative
// this filters and combines keys in one pass
foreach ($namespacedKeys as $requestedKey => $namespacedKey) {
if (! isset($items[$namespacedKey]) && ! array_key_exists($namespacedKey, $items)) {
continue;
}
$foundItems[$requestedKey] = $items[$namespacedKey];
}
return $foundItems;
}
/**
* {@inheritdoc}
*/
public function saveMultiple(array $keysAndValues, $lifetime = 0)
{
$namespacedKeysAndValues = [];
foreach ($keysAndValues as $key => $value) {
$namespacedKeysAndValues[$this->getNamespacedId($key)] = $value;
}
return $this->doSaveMultiple($namespacedKeysAndValues, $lifetime);
}
/**
* {@inheritdoc}
*/
public function contains($id)
{
return $this->doContains($this->getNamespacedId($id));
}
/**
* {@inheritdoc}
*/
public function save($id, $data, $lifeTime = 0)
{
return $this->doSave($this->getNamespacedId($id), $data, $lifeTime);
}
/**
* {@inheritdoc}
*/
public function deleteMultiple(array $keys)
{
return $this->doDeleteMultiple(array_map([$this, 'getNamespacedId'], $keys));
}
/**
* {@inheritdoc}
*/
public function delete($id)
{
return $this->doDelete($this->getNamespacedId($id));
}
/**
* {@inheritdoc}
*/
public function getStats()
{
return $this->doGetStats();
}
/**
* {@inheritDoc}
*/
public function flushAll()
{
return $this->doFlush();
}
/**
* {@inheritDoc}
*/
public function deleteAll()
{
$namespaceCacheKey = $this->getNamespaceCacheKey();
$namespaceVersion = $this->getNamespaceVersion() + 1;
if ($this->doSave($namespaceCacheKey, $namespaceVersion)) {
$this->namespaceVersion = $namespaceVersion;
return true;
}
return false;
}
/**
* Prefixes the passed id with the configured namespace value.
*
* @param string $id The id to namespace.
*
* @return string The namespaced id.
*/
private function getNamespacedId(string $id) : string
{
$namespaceVersion = $this->getNamespaceVersion();
return sprintf('%s[%s][%s]', $this->namespace, $id, $namespaceVersion);
}
/**
* Returns the namespace cache key.
*/
private function getNamespaceCacheKey() : string
{
return sprintf(self::DOCTRINE_NAMESPACE_CACHEKEY, $this->namespace);
}
/**
* Returns the namespace version.
*/
private function getNamespaceVersion() : int
{
if ($this->namespaceVersion !== null) {
return $this->namespaceVersion;
}
$namespaceCacheKey = $this->getNamespaceCacheKey();
$this->namespaceVersion = (int) $this->doFetch($namespaceCacheKey) ?: 1;
return $this->namespaceVersion;
}
/**
* Default implementation of doFetchMultiple. Each driver that supports multi-get should owerwrite it.
*
* @param array $keys Array of keys to retrieve from cache
*
* @return array Array of values retrieved for the given keys.
*/
protected function doFetchMultiple(array $keys)
{
$returnValues = [];
foreach ($keys as $key) {
$item = $this->doFetch($key);
if ($item === false && ! $this->doContains($key)) {
continue;
}
$returnValues[$key] = $item;
}
return $returnValues;
}
/**
* Fetches an entry from the cache.
*
* @param string $id The id of the cache entry to fetch.
*
* @return mixed|false The cached data or FALSE, if no cache entry exists for the given id.
*/
abstract protected function doFetch($id);
/**
* Tests if an entry exists in the cache.
*
* @param string $id The cache id of the entry to check for.
*
* @return bool TRUE if a cache entry exists for the given cache id, FALSE otherwise.
*/
abstract protected function doContains($id);
/**
* Default implementation of doSaveMultiple. Each driver that supports multi-put should override it.
*
* @param array $keysAndValues Array of keys and values to save in cache
* @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these
* cache entries (0 => infinite lifeTime).
*
* @return bool TRUE if the operation was successful, FALSE if it wasn't.
*/
protected function doSaveMultiple(array $keysAndValues, $lifetime = 0)
{
$success = true;
foreach ($keysAndValues as $key => $value) {
if ($this->doSave($key, $value, $lifetime)) {
continue;
}
$success = false;
}
return $success;
}
/**
* Puts data into the cache.
*
* @param string $id The cache id.
* @param string $data The cache entry/data.
* @param int $lifeTime The lifetime. If != 0, sets a specific lifetime for this
* cache entry (0 => infinite lifeTime).
*
* @return bool TRUE if the entry was successfully stored in the cache, FALSE otherwise.
*/
abstract protected function doSave($id, $data, $lifeTime = 0);
/**
* Default implementation of doDeleteMultiple. Each driver that supports multi-delete should override it.
*
* @param array $keys Array of keys to delete from cache
*
* @return bool TRUE if the operation was successful, FALSE if it wasn't
*/
protected function doDeleteMultiple(array $keys)
{
$success = true;
foreach ($keys as $key) {
if ($this->doDelete($key)) {
continue;
}
$success = false;
}
return $success;
}
/**
* Deletes a cache entry.
*
* @param string $id The cache id.
*
* @return bool TRUE if the cache entry was successfully deleted, FALSE otherwise.
*/
abstract protected function doDelete($id);
/**
* Flushes all cache entries.
*
* @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise.
*/
abstract protected function doFlush();
/**
* Retrieves cached information from the data store.
*
* @return array|null An associative array with server's statistics if available, NULL otherwise.
*/
abstract protected function doGetStats();
}

View File

@ -0,0 +1,187 @@
<?php
namespace Doctrine\Common\Cache;
use Traversable;
use function array_values;
use function count;
use function iterator_to_array;
/**
* Cache provider that allows to easily chain multiple cache providers
*/
class ChainCache extends CacheProvider
{
/** @var CacheProvider[] */
private $cacheProviders = [];
/**
* @param CacheProvider[] $cacheProviders
*/
public function __construct($cacheProviders = [])
{
$this->cacheProviders = $cacheProviders instanceof Traversable
? iterator_to_array($cacheProviders, false)
: array_values($cacheProviders);
}
/**
* {@inheritDoc}
*/
public function setNamespace($namespace)
{
parent::setNamespace($namespace);
foreach ($this->cacheProviders as $cacheProvider) {
$cacheProvider->setNamespace($namespace);
}
}
/**
* {@inheritDoc}
*/
protected function doFetch($id)
{
foreach ($this->cacheProviders as $key => $cacheProvider) {
if ($cacheProvider->doContains($id)) {
$value = $cacheProvider->doFetch($id);
// We populate all the previous cache layers (that are assumed to be faster)
for ($subKey = $key - 1; $subKey >= 0; $subKey--) {
$this->cacheProviders[$subKey]->doSave($id, $value);
}
return $value;
}
}
return false;
}
/**
* {@inheritdoc}
*/
protected function doFetchMultiple(array $keys)
{
/** @var CacheProvider[] $traversedProviders */
$traversedProviders = [];
$keysCount = count($keys);
$fetchedValues = [];
foreach ($this->cacheProviders as $key => $cacheProvider) {
$fetchedValues = $cacheProvider->doFetchMultiple($keys);
// We populate all the previous cache layers (that are assumed to be faster)
if (count($fetchedValues) === $keysCount) {
foreach ($traversedProviders as $previousCacheProvider) {
$previousCacheProvider->doSaveMultiple($fetchedValues);
}
return $fetchedValues;
}
$traversedProviders[] = $cacheProvider;
}
return $fetchedValues;
}
/**
* {@inheritDoc}
*/
protected function doContains($id)
{
foreach ($this->cacheProviders as $cacheProvider) {
if ($cacheProvider->doContains($id)) {
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
$stored = true;
foreach ($this->cacheProviders as $cacheProvider) {
$stored = $cacheProvider->doSave($id, $data, $lifeTime) && $stored;
}
return $stored;
}
/**
* {@inheritdoc}
*/
protected function doSaveMultiple(array $keysAndValues, $lifetime = 0)
{
$stored = true;
foreach ($this->cacheProviders as $cacheProvider) {
$stored = $cacheProvider->doSaveMultiple($keysAndValues, $lifetime) && $stored;
}
return $stored;
}
/**
* {@inheritDoc}
*/
protected function doDelete($id)
{
$deleted = true;
foreach ($this->cacheProviders as $cacheProvider) {
$deleted = $cacheProvider->doDelete($id) && $deleted;
}
return $deleted;
}
/**
* {@inheritdoc}
*/
protected function doDeleteMultiple(array $keys)
{
$deleted = true;
foreach ($this->cacheProviders as $cacheProvider) {
$deleted = $cacheProvider->doDeleteMultiple($keys) && $deleted;
}
return $deleted;
}
/**
* {@inheritDoc}
*/
protected function doFlush()
{
$flushed = true;
foreach ($this->cacheProviders as $cacheProvider) {
$flushed = $cacheProvider->doFlush() && $flushed;
}
return $flushed;
}
/**
* {@inheritDoc}
*/
protected function doGetStats()
{
// We return all the stats from all adapters
$stats = [];
foreach ($this->cacheProviders as $cacheProvider) {
$stats[] = $cacheProvider->doGetStats();
}
return $stats;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Doctrine\Common\Cache;
/**
* Interface for cache that can be flushed.
*
* Intended to be used for partial clearing of a cache namespace. For a more
* global "flushing", see {@see FlushableCache}.
*
* @link www.doctrine-project.org
*/
interface ClearableCache
{
/**
* Deletes all cache entries in the current cache namespace.
*
* @return bool TRUE if the cache entries were successfully deleted, FALSE otherwise.
*/
public function deleteAll();
}

View File

@ -0,0 +1,197 @@
<?php
declare(strict_types=1);
namespace Doctrine\Common\Cache;
use Couchbase\Bucket;
use Couchbase\Document;
use Couchbase\Exception;
use RuntimeException;
use function phpversion;
use function serialize;
use function sprintf;
use function substr;
use function time;
use function unserialize;
use function version_compare;
/**
* Couchbase ^2.3.0 cache provider.
*/
final class CouchbaseBucketCache extends CacheProvider
{
private const MINIMUM_VERSION = '2.3.0';
private const KEY_NOT_FOUND = 13;
private const MAX_KEY_LENGTH = 250;
private const THIRTY_DAYS_IN_SECONDS = 2592000;
/** @var Bucket */
private $bucket;
public function __construct(Bucket $bucket)
{
if (version_compare(phpversion('couchbase'), self::MINIMUM_VERSION) < 0) {
// Manager is required to flush cache and pull stats.
throw new RuntimeException(sprintf('ext-couchbase:^%s is required.', self::MINIMUM_VERSION));
}
$this->bucket = $bucket;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
$id = $this->normalizeKey($id);
try {
$document = $this->bucket->get($id);
} catch (Exception $e) {
return false;
}
if ($document instanceof Document && $document->value !== false) {
return unserialize($document->value);
}
return false;
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
$id = $this->normalizeKey($id);
try {
$document = $this->bucket->get($id);
} catch (Exception $e) {
return false;
}
if ($document instanceof Document) {
return ! $document->error;
}
return false;
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
$id = $this->normalizeKey($id);
$lifeTime = $this->normalizeExpiry($lifeTime);
try {
$encoded = serialize($data);
$document = $this->bucket->upsert($id, $encoded, [
'expiry' => (int) $lifeTime,
]);
} catch (Exception $e) {
return false;
}
if ($document instanceof Document) {
return ! $document->error;
}
return false;
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
$id = $this->normalizeKey($id);
try {
$document = $this->bucket->remove($id);
} catch (Exception $e) {
return $e->getCode() === self::KEY_NOT_FOUND;
}
if ($document instanceof Document) {
return ! $document->error;
}
return false;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
$manager = $this->bucket->manager();
// Flush does not return with success or failure, and must be enabled per bucket on the server.
// Store a marker item so that we will know if it was successful.
$this->doSave(__METHOD__, true, 60);
$manager->flush();
if ($this->doContains(__METHOD__)) {
$this->doDelete(__METHOD__);
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$manager = $this->bucket->manager();
$stats = $manager->info();
$nodes = $stats['nodes'];
$node = $nodes[0];
$interestingStats = $node['interestingStats'];
return [
Cache::STATS_HITS => $interestingStats['get_hits'],
Cache::STATS_MISSES => $interestingStats['cmd_get'] - $interestingStats['get_hits'],
Cache::STATS_UPTIME => $node['uptime'],
Cache::STATS_MEMORY_USAGE => $interestingStats['mem_used'],
Cache::STATS_MEMORY_AVAILABLE => $node['memoryFree'],
];
}
private function normalizeKey(string $id) : string
{
$normalized = substr($id, 0, self::MAX_KEY_LENGTH);
if ($normalized === false) {
return $id;
}
return $normalized;
}
/**
* Expiry treated as a unix timestamp instead of an offset if expiry is greater than 30 days.
*
* @src https://developer.couchbase.com/documentation/server/4.1/developer-guide/expiry.html
*/
private function normalizeExpiry(int $expiry) : int
{
if ($expiry > self::THIRTY_DAYS_IN_SECONDS) {
return time() + $expiry;
}
return $expiry;
}
}

View File

@ -0,0 +1,105 @@
<?php
namespace Doctrine\Common\Cache;
use Couchbase;
use function explode;
use function time;
/**
* Couchbase cache provider.
*
* @deprecated Couchbase SDK 1.x is now deprecated. Use \Doctrine\Common\Cache\CouchbaseBucketCache instead.
* https://developer.couchbase.com/documentation/server/current/sdk/php/compatibility-versions-features.html
*
* @link www.doctrine-project.org
*/
class CouchbaseCache extends CacheProvider
{
/** @var Couchbase|null */
private $couchbase;
/**
* Sets the Couchbase instance to use.
*
* @return void
*/
public function setCouchbase(Couchbase $couchbase)
{
$this->couchbase = $couchbase;
}
/**
* Gets the Couchbase instance used by the cache.
*
* @return Couchbase|null
*/
public function getCouchbase()
{
return $this->couchbase;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->couchbase->get($id) ?: false;
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return $this->couchbase->get($id) !== null;
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
if ($lifeTime > 30 * 24 * 3600) {
$lifeTime = time() + $lifeTime;
}
return $this->couchbase->set($id, $data, (int) $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return $this->couchbase->delete($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return $this->couchbase->flush();
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$stats = $this->couchbase->getStats();
$servers = $this->couchbase->getServers();
$server = explode(':', $servers[0]);
$key = $server[0] . ':11210';
$stats = $stats[$key];
return [
Cache::STATS_HITS => $stats['get_hits'],
Cache::STATS_MISSES => $stats['get_misses'],
Cache::STATS_UPTIME => $stats['uptime'],
Cache::STATS_MEMORY_USAGE => $stats['bytes'],
Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'],
];
}
}

View File

@ -0,0 +1,198 @@
<?php
declare(strict_types=1);
namespace Doctrine\Common\Cache;
use DateTime;
use MongoDB\BSON\Binary;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Collection;
use MongoDB\Database;
use MongoDB\Driver\Exception\Exception;
use MongoDB\Model\BSONDocument;
use function serialize;
use function time;
use function unserialize;
/**
* MongoDB cache provider for ext-mongodb
*
* @internal Do not use - will be removed in 2.0. Use MongoDBCache instead
*/
class ExtMongoDBCache extends CacheProvider
{
/** @var Database */
private $database;
/** @var Collection */
private $collection;
/** @var bool */
private $expirationIndexCreated = false;
/**
* This provider will default to the write concern and read preference
* options set on the Database instance (or inherited from MongoDB or
* Client). Using an unacknowledged write concern (< 1) may make the return
* values of delete() and save() unreliable. Reading from secondaries may
* make contain() and fetch() unreliable.
*
* @see http://www.php.net/manual/en/mongo.readpreferences.php
* @see http://www.php.net/manual/en/mongo.writeconcerns.php
*/
public function __construct(Collection $collection)
{
// Ensure there is no typemap set - we want to use our own
$this->collection = $collection->withOptions(['typeMap' => null]);
$this->database = new Database($collection->getManager(), $collection->getDatabaseName());
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
$document = $this->collection->findOne(['_id' => $id], [MongoDBCache::DATA_FIELD, MongoDBCache::EXPIRATION_FIELD]);
if ($document === null) {
return false;
}
if ($this->isExpired($document)) {
$this->createExpirationIndex();
$this->doDelete($id);
return false;
}
return unserialize($document[MongoDBCache::DATA_FIELD]->getData());
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
$document = $this->collection->findOne(['_id' => $id], [MongoDBCache::EXPIRATION_FIELD]);
if ($document === null) {
return false;
}
if ($this->isExpired($document)) {
$this->createExpirationIndex();
$this->doDelete($id);
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
try {
$this->collection->updateOne(
['_id' => $id],
[
'$set' => [
MongoDBCache::EXPIRATION_FIELD => ($lifeTime > 0 ? new UTCDateTime((time() + $lifeTime) * 1000): null),
MongoDBCache::DATA_FIELD => new Binary(serialize($data), Binary::TYPE_GENERIC),
],
],
['upsert' => true]
);
} catch (Exception $e) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
try {
$this->collection->deleteOne(['_id' => $id]);
} catch (Exception $e) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
try {
// Use remove() in lieu of drop() to maintain any collection indexes
$this->collection->deleteMany([]);
} catch (Exception $e) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$uptime = null;
$memoryUsage = null;
try {
$serverStatus = $this->database->command([
'serverStatus' => 1,
'locks' => 0,
'metrics' => 0,
'recordStats' => 0,
'repl' => 0,
])->toArray()[0];
$uptime = $serverStatus['uptime'] ?? null;
} catch (Exception $e) {
}
try {
$collStats = $this->database->command(['collStats' => $this->collection->getCollectionName()])->toArray()[0];
$memoryUsage = $collStats['size'] ?? null;
} catch (Exception $e) {
}
return [
Cache::STATS_HITS => null,
Cache::STATS_MISSES => null,
Cache::STATS_UPTIME => $uptime,
Cache::STATS_MEMORY_USAGE => $memoryUsage,
Cache::STATS_MEMORY_AVAILABLE => null,
];
}
/**
* Check if the document is expired.
*/
private function isExpired(BSONDocument $document) : bool
{
return isset($document[MongoDBCache::EXPIRATION_FIELD]) &&
$document[MongoDBCache::EXPIRATION_FIELD] instanceof UTCDateTime &&
$document[MongoDBCache::EXPIRATION_FIELD]->toDateTime() < new DateTime();
}
private function createExpirationIndex() : void
{
if ($this->expirationIndexCreated) {
return;
}
$this->collection->createIndex([MongoDBCache::EXPIRATION_FIELD => 1], ['background' => true, 'expireAfterSeconds' => 0]);
}
}

View File

@ -0,0 +1,281 @@
<?php
namespace Doctrine\Common\Cache;
use FilesystemIterator;
use InvalidArgumentException;
use Iterator;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use const DIRECTORY_SEPARATOR;
use const PATHINFO_DIRNAME;
use function bin2hex;
use function chmod;
use function defined;
use function disk_free_space;
use function file_exists;
use function file_put_contents;
use function gettype;
use function hash;
use function is_dir;
use function is_int;
use function is_writable;
use function mkdir;
use function pathinfo;
use function realpath;
use function rename;
use function rmdir;
use function sprintf;
use function strlen;
use function strrpos;
use function substr;
use function tempnam;
use function unlink;
/**
* Base file cache driver.
*/
abstract class FileCache extends CacheProvider
{
/**
* The cache directory.
*
* @var string
*/
protected $directory;
/**
* The cache file extension.
*
* @var string
*/
private $extension;
/** @var int */
private $umask;
/** @var int */
private $directoryStringLength;
/** @var int */
private $extensionStringLength;
/** @var bool */
private $isRunningOnWindows;
/**
* @param string $directory The cache directory.
* @param string $extension The cache file extension.
*
* @throws InvalidArgumentException
*/
public function __construct($directory, $extension = '', $umask = 0002)
{
// YES, this needs to be *before* createPathIfNeeded()
if (! is_int($umask)) {
throw new InvalidArgumentException(sprintf(
'The umask parameter is required to be integer, was: %s',
gettype($umask)
));
}
$this->umask = $umask;
if (! $this->createPathIfNeeded($directory)) {
throw new InvalidArgumentException(sprintf(
'The directory "%s" does not exist and could not be created.',
$directory
));
}
if (! is_writable($directory)) {
throw new InvalidArgumentException(sprintf(
'The directory "%s" is not writable.',
$directory
));
}
// YES, this needs to be *after* createPathIfNeeded()
$this->directory = realpath($directory);
$this->extension = (string) $extension;
$this->directoryStringLength = strlen($this->directory);
$this->extensionStringLength = strlen($this->extension);
$this->isRunningOnWindows = defined('PHP_WINDOWS_VERSION_BUILD');
}
/**
* Gets the cache directory.
*
* @return string
*/
public function getDirectory()
{
return $this->directory;
}
/**
* Gets the cache file extension.
*
* @return string
*/
public function getExtension()
{
return $this->extension;
}
/**
* @param string $id
*
* @return string
*/
protected function getFilename($id)
{
$hash = hash('sha256', $id);
// This ensures that the filename is unique and that there are no invalid chars in it.
if ($id === ''
|| ((strlen($id) * 2 + $this->extensionStringLength) > 255)
|| ($this->isRunningOnWindows && ($this->directoryStringLength + 4 + strlen($id) * 2 + $this->extensionStringLength) > 258)
) {
// Most filesystems have a limit of 255 chars for each path component. On Windows the the whole path is limited
// to 260 chars (including terminating null char). Using long UNC ("\\?\" prefix) does not work with the PHP API.
// And there is a bug in PHP (https://bugs.php.net/bug.php?id=70943) with path lengths of 259.
// So if the id in hex representation would surpass the limit, we use the hash instead. The prefix prevents
// collisions between the hash and bin2hex.
$filename = '_' . $hash;
} else {
$filename = bin2hex($id);
}
return $this->directory
. DIRECTORY_SEPARATOR
. substr($hash, 0, 2)
. DIRECTORY_SEPARATOR
. $filename
. $this->extension;
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
$filename = $this->getFilename($id);
return @unlink($filename) || ! file_exists($filename);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
foreach ($this->getIterator() as $name => $file) {
if ($file->isDir()) {
// Remove the intermediate directories which have been created to balance the tree. It only takes effect
// if the directory is empty. If several caches share the same directory but with different file extensions,
// the other ones are not removed.
@rmdir($name);
} elseif ($this->isFilenameEndingWithExtension($name)) {
// If an extension is set, only remove files which end with the given extension.
// If no extension is set, we have no other choice than removing everything.
@unlink($name);
}
}
return true;
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$usage = 0;
foreach ($this->getIterator() as $name => $file) {
if ($file->isDir() || ! $this->isFilenameEndingWithExtension($name)) {
continue;
}
$usage += $file->getSize();
}
$free = disk_free_space($this->directory);
return [
Cache::STATS_HITS => null,
Cache::STATS_MISSES => null,
Cache::STATS_UPTIME => null,
Cache::STATS_MEMORY_USAGE => $usage,
Cache::STATS_MEMORY_AVAILABLE => $free,
];
}
/**
* Create path if needed.
*
* @return bool TRUE on success or if path already exists, FALSE if path cannot be created.
*/
private function createPathIfNeeded(string $path) : bool
{
if (! is_dir($path)) {
if (@mkdir($path, 0777 & (~$this->umask), true) === false && ! is_dir($path)) {
return false;
}
}
return true;
}
/**
* Writes a string content to file in an atomic way.
*
* @param string $filename Path to the file where to write the data.
* @param string $content The content to write
*
* @return bool TRUE on success, FALSE if path cannot be created, if path is not writable or an any other error.
*/
protected function writeFile(string $filename, string $content) : bool
{
$filepath = pathinfo($filename, PATHINFO_DIRNAME);
if (! $this->createPathIfNeeded($filepath)) {
return false;
}
if (! is_writable($filepath)) {
return false;
}
$tmpFile = tempnam($filepath, 'swap');
@chmod($tmpFile, 0666 & (~$this->umask));
if (file_put_contents($tmpFile, $content) !== false) {
@chmod($tmpFile, 0666 & (~$this->umask));
if (@rename($tmpFile, $filename)) {
return true;
}
@unlink($tmpFile);
}
return false;
}
private function getIterator() : Iterator
{
return new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->directory, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
}
/**
* @param string $name The filename
*/
private function isFilenameEndingWithExtension(string $name) : bool
{
return $this->extension === ''
|| strrpos($name, $this->extension) === (strlen($name) - $this->extensionStringLength);
}
}

View File

@ -0,0 +1,102 @@
<?php
namespace Doctrine\Common\Cache;
use const PHP_EOL;
use function fclose;
use function fgets;
use function fopen;
use function is_file;
use function serialize;
use function time;
use function unserialize;
/**
* Filesystem cache driver.
*/
class FilesystemCache extends FileCache
{
public const EXTENSION = '.doctrinecache.data';
/**
* {@inheritdoc}
*/
public function __construct($directory, $extension = self::EXTENSION, $umask = 0002)
{
parent::__construct($directory, $extension, $umask);
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
$data = '';
$lifetime = -1;
$filename = $this->getFilename($id);
if (! is_file($filename)) {
return false;
}
$resource = fopen($filename, 'r');
$line = fgets($resource);
if ($line !== false) {
$lifetime = (int) $line;
}
if ($lifetime !== 0 && $lifetime < time()) {
fclose($resource);
return false;
}
while (($line = fgets($resource)) !== false) {
$data .= $line;
}
fclose($resource);
return unserialize($data);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
$lifetime = -1;
$filename = $this->getFilename($id);
if (! is_file($filename)) {
return false;
}
$resource = fopen($filename, 'r');
$line = fgets($resource);
if ($line !== false) {
$lifetime = (int) $line;
}
fclose($resource);
return $lifetime === 0 || $lifetime > time();
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
if ($lifeTime > 0) {
$lifeTime = time() + $lifeTime;
}
$data = serialize($data);
$filename = $this->getFilename($id);
return $this->writeFile($filename, $lifeTime . PHP_EOL . $data);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Doctrine\Common\Cache;
/**
* Interface for cache that can be flushed.
*
* @link www.doctrine-project.org
*/
interface FlushableCache
{
/**
* Flushes all cache entries, globally.
*
* @return bool TRUE if the cache entries were successfully flushed, FALSE otherwise.
*/
public function flushAll();
}

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Doctrine\Common\Cache;
use InvalidArgumentException;
use function sprintf;
final class InvalidCacheId extends InvalidArgumentException
{
public static function exceedsMaxLength($id, int $maxLength) : self
{
return new self(sprintf('Cache id "%s" exceeds maximum length %d', $id, $maxLength));
}
public static function containsUnauthorizedCharacter($id, string $character) : self
{
return new self(sprintf('Cache id "%s" contains unauthorized character "%s"', $id, $character));
}
public static function containsControlCharacter($id) : self
{
return new self(sprintf('Cache id "%s" contains at least one control character', $id));
}
}

View File

@ -0,0 +1,175 @@
<?php
namespace Doctrine\Common\Cache;
use MongoBinData;
use MongoCollection;
use MongoCursorException;
use MongoDate;
use const E_USER_DEPRECATED;
use function serialize;
use function time;
use function trigger_error;
use function unserialize;
/**
* MongoDB cache provider.
*
* @internal Do not use - will be removed in 2.0. Use MongoDBCache instead
*/
class LegacyMongoDBCache extends CacheProvider
{
/** @var MongoCollection */
private $collection;
/** @var bool */
private $expirationIndexCreated = false;
/**
* This provider will default to the write concern and read preference
* options set on the MongoCollection instance (or inherited from MongoDB or
* MongoClient). Using an unacknowledged write concern (< 1) may make the
* return values of delete() and save() unreliable. Reading from secondaries
* may make contain() and fetch() unreliable.
*
* @see http://www.php.net/manual/en/mongo.readpreferences.php
* @see http://www.php.net/manual/en/mongo.writeconcerns.php
*/
public function __construct(MongoCollection $collection)
{
@trigger_error('Using the legacy MongoDB cache provider is deprecated and will be removed in 2.0', E_USER_DEPRECATED);
$this->collection = $collection;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
$document = $this->collection->findOne(['_id' => $id], [MongoDBCache::DATA_FIELD, MongoDBCache::EXPIRATION_FIELD]);
if ($document === null) {
return false;
}
if ($this->isExpired($document)) {
$this->createExpirationIndex();
$this->doDelete($id);
return false;
}
return unserialize($document[MongoDBCache::DATA_FIELD]->bin);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
$document = $this->collection->findOne(['_id' => $id], [MongoDBCache::EXPIRATION_FIELD]);
if ($document === null) {
return false;
}
if ($this->isExpired($document)) {
$this->createExpirationIndex();
$this->doDelete($id);
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
try {
$result = $this->collection->update(
['_id' => $id],
[
'$set' => [
MongoDBCache::EXPIRATION_FIELD => ($lifeTime > 0 ? new MongoDate(time() + $lifeTime) : null),
MongoDBCache::DATA_FIELD => new MongoBinData(serialize($data), MongoBinData::BYTE_ARRAY),
],
],
['upsert' => true, 'multiple' => false]
);
} catch (MongoCursorException $e) {
return false;
}
return ($result['ok'] ?? 1) == 1;
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
$result = $this->collection->remove(['_id' => $id]);
return ($result['ok'] ?? 1) == 1;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
// Use remove() in lieu of drop() to maintain any collection indexes
$result = $this->collection->remove();
return ($result['ok'] ?? 1) == 1;
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$serverStatus = $this->collection->db->command([
'serverStatus' => 1,
'locks' => 0,
'metrics' => 0,
'recordStats' => 0,
'repl' => 0,
]);
$collStats = $this->collection->db->command(['collStats' => 1]);
return [
Cache::STATS_HITS => null,
Cache::STATS_MISSES => null,
Cache::STATS_UPTIME => $serverStatus['uptime'] ?? null,
Cache::STATS_MEMORY_USAGE => $collStats['size'] ?? null,
Cache::STATS_MEMORY_AVAILABLE => null,
];
}
/**
* Check if the document is expired.
*
* @param array $document
*/
private function isExpired(array $document) : bool
{
return isset($document[MongoDBCache::EXPIRATION_FIELD]) &&
$document[MongoDBCache::EXPIRATION_FIELD] instanceof MongoDate &&
$document[MongoDBCache::EXPIRATION_FIELD]->sec < time();
}
private function createExpirationIndex() : void
{
if ($this->expirationIndexCreated) {
return;
}
$this->expirationIndexCreated = true;
$this->collection->createIndex([MongoDBCache::EXPIRATION_FIELD => 1], ['background' => true, 'expireAfterSeconds' => 0]);
}
}

View File

@ -0,0 +1,104 @@
<?php
namespace Doctrine\Common\Cache;
use Memcache;
use function time;
/**
* Memcache cache provider.
*
* @deprecated
*
* @link www.doctrine-project.org
*/
class MemcacheCache extends CacheProvider
{
/** @var Memcache|null */
private $memcache;
/**
* Sets the memcache instance to use.
*
* @return void
*/
public function setMemcache(Memcache $memcache)
{
$this->memcache = $memcache;
}
/**
* Gets the memcache instance used by the cache.
*
* @return Memcache|null
*/
public function getMemcache()
{
return $this->memcache;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->memcache->get($id);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
$flags = null;
$this->memcache->get($id, $flags);
//if memcache has changed the value of "flags", it means the value exists
return $flags !== null;
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
if ($lifeTime > 30 * 24 * 3600) {
$lifeTime = time() + $lifeTime;
}
return $this->memcache->set($id, $data, 0, (int) $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
// Memcache::delete() returns false if entry does not exist
return $this->memcache->delete($id) || ! $this->doContains($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return $this->memcache->flush();
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$stats = $this->memcache->getStats();
return [
Cache::STATS_HITS => $stats['get_hits'],
Cache::STATS_MISSES => $stats['get_misses'],
Cache::STATS_UPTIME => $stats['uptime'],
Cache::STATS_MEMORY_USAGE => $stats['bytes'],
Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'],
];
}
}

View File

@ -0,0 +1,170 @@
<?php
namespace Doctrine\Common\Cache;
use Memcached;
use function array_keys;
use function preg_match;
use function strlen;
use function strpos;
use function time;
/**
* Memcached cache provider.
*
* @link www.doctrine-project.org
*/
class MemcachedCache extends CacheProvider
{
public const CACHE_ID_MAX_LENGTH = 250;
/** @var Memcached|null */
private $memcached;
/**
* Sets the memcache instance to use.
*
* @return void
*/
public function setMemcached(Memcached $memcached)
{
$this->memcached = $memcached;
}
/**
* Gets the memcached instance used by the cache.
*
* @return Memcached|null
*/
public function getMemcached()
{
return $this->memcached;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->memcached->get($id);
}
/**
* {@inheritdoc}
*/
protected function doFetchMultiple(array $keys)
{
return $this->memcached->getMulti($keys) ?: [];
}
/**
* {@inheritdoc}
*/
protected function doSaveMultiple(array $keysAndValues, $lifetime = 0)
{
foreach (array_keys($keysAndValues) as $id) {
$this->validateCacheId($id);
}
if ($lifetime > 30 * 24 * 3600) {
$lifetime = time() + $lifetime;
}
return $this->memcached->setMulti($keysAndValues, $lifetime);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
$this->memcached->get($id);
return $this->memcached->getResultCode() === Memcached::RES_SUCCESS;
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
$this->validateCacheId($id);
if ($lifeTime > 30 * 24 * 3600) {
$lifeTime = time() + $lifeTime;
}
return $this->memcached->set($id, $data, (int) $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDeleteMultiple(array $keys)
{
return $this->memcached->deleteMulti($keys)
|| $this->memcached->getResultCode() === Memcached::RES_NOTFOUND;
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return $this->memcached->delete($id)
|| $this->memcached->getResultCode() === Memcached::RES_NOTFOUND;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return $this->memcached->flush();
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$stats = $this->memcached->getStats();
$servers = $this->memcached->getServerList();
$key = $servers[0]['host'] . ':' . $servers[0]['port'];
$stats = $stats[$key];
return [
Cache::STATS_HITS => $stats['get_hits'],
Cache::STATS_MISSES => $stats['get_misses'],
Cache::STATS_UPTIME => $stats['uptime'],
Cache::STATS_MEMORY_USAGE => $stats['bytes'],
Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'],
];
}
/**
* Validate the cache id
*
* @see https://github.com/memcached/memcached/blob/1.5.12/doc/protocol.txt#L41-L49
*
* @param string $id
*
* @return void
*
* @throws InvalidCacheId
*/
private function validateCacheId($id)
{
if (strlen($id) > self::CACHE_ID_MAX_LENGTH) {
throw InvalidCacheId::exceedsMaxLength($id, self::CACHE_ID_MAX_LENGTH);
}
if (strpos($id, ' ') !== false) {
throw InvalidCacheId::containsUnauthorizedCharacter($id, ' ');
}
if (preg_match('/[\t\r\n]/', $id) === 1) {
throw InvalidCacheId::containsControlCharacter($id);
}
}
}

View File

@ -0,0 +1,112 @@
<?php
namespace Doctrine\Common\Cache;
use InvalidArgumentException;
use MongoCollection;
use MongoDB\Collection;
use const E_USER_DEPRECATED;
use function trigger_error;
/**
* MongoDB cache provider.
*/
class MongoDBCache extends CacheProvider
{
/**
* The data field will store the serialized PHP value.
*/
public const DATA_FIELD = 'd';
/**
* The expiration field will store a MongoDate value indicating when the
* cache entry should expire.
*
* With MongoDB 2.2+, entries can be automatically deleted by MongoDB by
* indexing this field with the "expireAfterSeconds" option equal to zero.
* This will direct MongoDB to regularly query for and delete any entries
* whose date is older than the current time. Entries without a date value
* in this field will be ignored.
*
* The cache provider will also check dates on its own, in case expired
* entries are fetched before MongoDB's TTLMonitor pass can expire them.
*
* @see http://docs.mongodb.org/manual/tutorial/expire-data/
*/
public const EXPIRATION_FIELD = 'e';
/** @var CacheProvider */
private $provider;
/**
* This provider will default to the write concern and read preference
* options set on the collection instance (or inherited from MongoDB or
* MongoClient). Using an unacknowledged write concern (< 1) may make the
* return values of delete() and save() unreliable. Reading from secondaries
* may make contain() and fetch() unreliable.
*
* @see http://www.php.net/manual/en/mongo.readpreferences.php
* @see http://www.php.net/manual/en/mongo.writeconcerns.php
*
* @param MongoCollection|Collection $collection
*/
public function __construct($collection)
{
if ($collection instanceof MongoCollection) {
@trigger_error('Using a MongoCollection instance for creating a cache adapter is deprecated and will be removed in 2.0', E_USER_DEPRECATED);
$this->provider = new LegacyMongoDBCache($collection);
} elseif ($collection instanceof Collection) {
$this->provider = new ExtMongoDBCache($collection);
} else {
throw new InvalidArgumentException('Invalid collection given - expected a MongoCollection or MongoDB\Collection instance');
}
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->provider->doFetch($id);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return $this->provider->doContains($id);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
return $this->provider->doSave($id, $data, $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return $this->provider->doDelete($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return $this->provider->doFlush();
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
return $this->provider->doGetStats();
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Doctrine\Common\Cache;
/**
* Interface for cache drivers that allows to put many items at once.
*
* @deprecated
*
* @link www.doctrine-project.org
*/
interface MultiDeleteCache
{
/**
* Deletes several cache entries.
*
* @param string[] $keys Array of keys to delete from cache
*
* @return bool TRUE if the operation was successful, FALSE if it wasn't.
*/
public function deleteMultiple(array $keys);
}

View File

@ -0,0 +1,23 @@
<?php
namespace Doctrine\Common\Cache;
/**
* Interface for cache drivers that allows to get many items at once.
*
* @deprecated
*
* @link www.doctrine-project.org
*/
interface MultiGetCache
{
/**
* Returns an associative array of values for keys is found in cache.
*
* @param string[] $keys Array of keys to retrieve from cache
*
* @return mixed[] Array of retrieved values, indexed by the specified keys.
* Values that couldn't be retrieved are not contained in this array.
*/
public function fetchMultiple(array $keys);
}

View File

@ -0,0 +1,12 @@
<?php
namespace Doctrine\Common\Cache;
/**
* Interface for cache drivers that supports multiple items manipulation.
*
* @link www.doctrine-project.org
*/
interface MultiOperationCache extends MultiGetCache, MultiDeleteCache, MultiPutCache
{
}

View File

@ -0,0 +1,24 @@
<?php
namespace Doctrine\Common\Cache;
/**
* Interface for cache drivers that allows to put many items at once.
*
* @deprecated
*
* @link www.doctrine-project.org
*/
interface MultiPutCache
{
/**
* Returns a boolean value indicating if the operation succeeded.
*
* @param array $keysAndValues Array of keys and values to save in cache
* @param int $lifetime The lifetime. If != 0, sets a specific lifetime for these
* cache entries (0 => infinite lifeTime).
*
* @return bool TRUE if the operation was successful, FALSE if it wasn't.
*/
public function saveMultiple(array $keysAndValues, $lifetime = 0);
}

View File

@ -0,0 +1,118 @@
<?php
namespace Doctrine\Common\Cache;
use function is_object;
use function method_exists;
use function restore_error_handler;
use function serialize;
use function set_error_handler;
use function sprintf;
use function time;
use function var_export;
/**
* Php file cache driver.
*/
class PhpFileCache extends FileCache
{
public const EXTENSION = '.doctrinecache.php';
/**
* @var callable
*
* This is cached in a local static variable to avoid instantiating a closure each time we need an empty handler
*/
private static $emptyErrorHandler;
/**
* {@inheritdoc}
*/
public function __construct($directory, $extension = self::EXTENSION, $umask = 0002)
{
parent::__construct($directory, $extension, $umask);
self::$emptyErrorHandler = static function () {
};
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
$value = $this->includeFileForId($id);
if ($value === null) {
return false;
}
if ($value['lifetime'] !== 0 && $value['lifetime'] < time()) {
return false;
}
return $value['data'];
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
$value = $this->includeFileForId($id);
if ($value === null) {
return false;
}
return $value['lifetime'] === 0 || $value['lifetime'] > time();
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
if ($lifeTime > 0) {
$lifeTime = time() + $lifeTime;
}
$filename = $this->getFilename($id);
$value = [
'lifetime' => $lifeTime,
'data' => $data,
];
if (is_object($data) && method_exists($data, '__set_state')) {
$value = var_export($value, true);
$code = sprintf('<?php return %s;', $value);
} else {
$value = var_export(serialize($value), true);
$code = sprintf('<?php return unserialize(%s);', $value);
}
return $this->writeFile($filename, $code);
}
/**
* @return array|null
*/
private function includeFileForId(string $id) : ?array
{
$fileName = $this->getFilename($id);
// note: error suppression is still faster than `file_exists`, `is_file` and `is_readable`
set_error_handler(self::$emptyErrorHandler);
$value = include $fileName;
restore_error_handler();
if (! isset($value['lifetime'])) {
return null;
}
return $value;
}
}

View File

@ -0,0 +1,143 @@
<?php
namespace Doctrine\Common\Cache;
use Predis\ClientInterface;
use function array_combine;
use function array_filter;
use function array_map;
use function call_user_func_array;
use function serialize;
use function unserialize;
/**
* Predis cache provider.
*/
class PredisCache extends CacheProvider
{
/** @var ClientInterface */
private $client;
public function __construct(ClientInterface $client)
{
$this->client = $client;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
$result = $this->client->get($id);
if ($result === null) {
return false;
}
return unserialize($result);
}
/**
* {@inheritdoc}
*/
protected function doFetchMultiple(array $keys)
{
$fetchedItems = call_user_func_array([$this->client, 'mget'], $keys);
return array_map('unserialize', array_filter(array_combine($keys, $fetchedItems)));
}
/**
* {@inheritdoc}
*/
protected function doSaveMultiple(array $keysAndValues, $lifetime = 0)
{
if ($lifetime) {
$success = true;
// Keys have lifetime, use SETEX for each of them
foreach ($keysAndValues as $key => $value) {
$response = (string) $this->client->setex($key, $lifetime, serialize($value));
if ($response == 'OK') {
continue;
}
$success = false;
}
return $success;
}
// No lifetime, use MSET
$response = $this->client->mset(array_map(static function ($value) {
return serialize($value);
}, $keysAndValues));
return (string) $response == 'OK';
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return (bool) $this->client->exists($id);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
$data = serialize($data);
if ($lifeTime > 0) {
$response = $this->client->setex($id, $lifeTime, $data);
} else {
$response = $this->client->set($id, $data);
}
return $response === true || $response == 'OK';
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return $this->client->del($id) >= 0;
}
/**
* {@inheritdoc}
*/
protected function doDeleteMultiple(array $keys)
{
return $this->client->del($keys) >= 0;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
$response = $this->client->flushdb();
return $response === true || $response == 'OK';
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$info = $this->client->info();
return [
Cache::STATS_HITS => $info['Stats']['keyspace_hits'],
Cache::STATS_MISSES => $info['Stats']['keyspace_misses'],
Cache::STATS_UPTIME => $info['Server']['uptime_in_seconds'],
Cache::STATS_MEMORY_USAGE => $info['Memory']['used_memory'],
Cache::STATS_MEMORY_AVAILABLE => false,
];
}
}

View File

@ -0,0 +1,181 @@
<?php
namespace Doctrine\Common\Cache;
use Redis;
use function array_combine;
use function array_diff_key;
use function array_fill_keys;
use function array_filter;
use function array_keys;
use function count;
use function defined;
use function extension_loaded;
use function is_bool;
/**
* Redis cache provider.
*
* @link www.doctrine-project.org
*/
class RedisCache extends CacheProvider
{
/** @var Redis|null */
private $redis;
/**
* Sets the redis instance to use.
*
* @return void
*/
public function setRedis(Redis $redis)
{
$redis->setOption(Redis::OPT_SERIALIZER, $this->getSerializerValue());
$this->redis = $redis;
}
/**
* Gets the redis instance used by the cache.
*
* @return Redis|null
*/
public function getRedis()
{
return $this->redis;
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->redis->get($id);
}
/**
* {@inheritdoc}
*/
protected function doFetchMultiple(array $keys)
{
$fetchedItems = array_combine($keys, $this->redis->mget($keys));
// Redis mget returns false for keys that do not exist. So we need to filter those out unless it's the real data.
$keysToFilter = array_keys(array_filter($fetchedItems, static function ($item) : bool {
return $item === false;
}));
if ($keysToFilter) {
$multi = $this->redis->multi(Redis::PIPELINE);
foreach ($keysToFilter as $key) {
$multi->exists($key);
}
$existItems = array_filter($multi->exec());
$missedItemKeys = array_diff_key($keysToFilter, $existItems);
$fetchedItems = array_diff_key($fetchedItems, array_fill_keys($missedItemKeys, true));
}
return $fetchedItems;
}
/**
* {@inheritdoc}
*/
protected function doSaveMultiple(array $keysAndValues, $lifetime = 0)
{
if ($lifetime) {
// Keys have lifetime, use SETEX for each of them
$multi = $this->redis->multi(Redis::PIPELINE);
foreach ($keysAndValues as $key => $value) {
$multi->setex($key, $lifetime, $value);
}
$succeeded = array_filter($multi->exec());
return count($succeeded) == count($keysAndValues);
}
// No lifetime, use MSET
return (bool) $this->redis->mset($keysAndValues);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
$exists = $this->redis->exists($id);
if (is_bool($exists)) {
return $exists;
}
return $exists > 0;
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
if ($lifeTime > 0) {
return $this->redis->setex($id, $lifeTime, $data);
}
return $this->redis->set($id, $data);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return $this->redis->del($id) >= 0;
}
/**
* {@inheritdoc}
*/
protected function doDeleteMultiple(array $keys)
{
return $this->redis->del($keys) >= 0;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return $this->redis->flushDB();
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$info = $this->redis->info();
return [
Cache::STATS_HITS => $info['keyspace_hits'],
Cache::STATS_MISSES => $info['keyspace_misses'],
Cache::STATS_UPTIME => $info['uptime_in_seconds'],
Cache::STATS_MEMORY_USAGE => $info['used_memory'],
Cache::STATS_MEMORY_AVAILABLE => false,
];
}
/**
* Returns the serializer constant to use. If Redis is compiled with
* igbinary support, that is used. Otherwise the default PHP serializer is
* used.
*
* @return int One of the Redis::SERIALIZER_* constants
*/
protected function getSerializerValue()
{
if (defined('Redis::SERIALIZER_IGBINARY') && extension_loaded('igbinary')) {
return Redis::SERIALIZER_IGBINARY;
}
return Redis::SERIALIZER_PHP;
}
}

View File

@ -0,0 +1,206 @@
<?php
namespace Doctrine\Common\Cache;
use SQLite3;
use SQLite3Result;
use const SQLITE3_ASSOC;
use const SQLITE3_BLOB;
use const SQLITE3_TEXT;
use function array_search;
use function implode;
use function serialize;
use function sprintf;
use function time;
use function unserialize;
/**
* SQLite3 cache provider.
*/
class SQLite3Cache extends CacheProvider
{
/**
* The ID field will store the cache key.
*/
public const ID_FIELD = 'k';
/**
* The data field will store the serialized PHP value.
*/
public const DATA_FIELD = 'd';
/**
* The expiration field will store a date value indicating when the
* cache entry should expire.
*/
public const EXPIRATION_FIELD = 'e';
/** @var SQLite3 */
private $sqlite;
/** @var string */
private $table;
/**
* Calling the constructor will ensure that the database file and table
* exist and will create both if they don't.
*
* @param string $table
*/
public function __construct(SQLite3 $sqlite, $table)
{
$this->sqlite = $sqlite;
$this->table = (string) $table;
$this->ensureTableExists();
}
private function ensureTableExists() : void
{
$this->sqlite->exec(
sprintf(
'CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY NOT NULL, %s BLOB, %s INTEGER)',
$this->table,
static::ID_FIELD,
static::DATA_FIELD,
static::EXPIRATION_FIELD
)
);
}
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
$item = $this->findById($id);
if (! $item) {
return false;
}
return unserialize($item[self::DATA_FIELD]);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return $this->findById($id, false) !== null;
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
$statement = $this->sqlite->prepare(sprintf(
'INSERT OR REPLACE INTO %s (%s) VALUES (:id, :data, :expire)',
$this->table,
implode(',', $this->getFields())
));
$statement->bindValue(':id', $id);
$statement->bindValue(':data', serialize($data), SQLITE3_BLOB);
$statement->bindValue(':expire', $lifeTime > 0 ? time() + $lifeTime : null);
return $statement->execute() instanceof SQLite3Result;
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
[$idField] = $this->getFields();
$statement = $this->sqlite->prepare(sprintf(
'DELETE FROM %s WHERE %s = :id',
$this->table,
$idField
));
$statement->bindValue(':id', $id);
return $statement->execute() instanceof SQLite3Result;
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return $this->sqlite->exec(sprintf('DELETE FROM %s', $this->table));
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
// no-op.
}
/**
* Find a single row by ID.
*
* @param mixed $id
*
* @return array|null
*/
private function findById($id, bool $includeData = true) : ?array
{
[$idField] = $fields = $this->getFields();
if (! $includeData) {
$key = array_search(static::DATA_FIELD, $fields);
unset($fields[$key]);
}
$statement = $this->sqlite->prepare(sprintf(
'SELECT %s FROM %s WHERE %s = :id LIMIT 1',
implode(',', $fields),
$this->table,
$idField
));
$statement->bindValue(':id', $id, SQLITE3_TEXT);
$item = $statement->execute()->fetchArray(SQLITE3_ASSOC);
if ($item === false) {
return null;
}
if ($this->isExpired($item)) {
$this->doDelete($id);
return null;
}
return $item;
}
/**
* Gets an array of the fields in our table.
*
* @return array
*/
private function getFields() : array
{
return [static::ID_FIELD, static::DATA_FIELD, static::EXPIRATION_FIELD];
}
/**
* Check if the item is expired.
*
* @param array $item
*/
private function isExpired(array $item) : bool
{
return isset($item[static::EXPIRATION_FIELD]) &&
$item[self::EXPIRATION_FIELD] !== null &&
$item[self::EXPIRATION_FIELD] < time();
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Doctrine\Common\Cache;
class Version
{
public const VERSION = '1.9.0-DEV';
}

View File

@ -0,0 +1,59 @@
<?php
namespace Doctrine\Common\Cache;
/**
* Void cache driver. The cache could be of use in tests where you don`t need to cache anything.
*
* @link www.doctrine-project.org
*/
class VoidCache extends CacheProvider
{
/**
* {@inheritDoc}
*/
protected function doFetch($id)
{
return false;
}
/**
* {@inheritDoc}
*/
protected function doContains($id)
{
return false;
}
/**
* {@inheritDoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
return true;
}
/**
* {@inheritDoc}
*/
protected function doDelete($id)
{
return true;
}
/**
* {@inheritDoc}
*/
protected function doFlush()
{
return true;
}
/**
* {@inheritDoc}
*/
protected function doGetStats()
{
return;
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace Doctrine\Common\Cache;
use function count;
use function is_array;
use function wincache_ucache_clear;
use function wincache_ucache_delete;
use function wincache_ucache_exists;
use function wincache_ucache_get;
use function wincache_ucache_info;
use function wincache_ucache_meminfo;
use function wincache_ucache_set;
/**
* WinCache cache provider.
*
* @link www.doctrine-project.org
*/
class WinCacheCache extends CacheProvider
{
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return wincache_ucache_get($id);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return wincache_ucache_exists($id);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
return wincache_ucache_set($id, $data, $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return wincache_ucache_delete($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
return wincache_ucache_clear();
}
/**
* {@inheritdoc}
*/
protected function doFetchMultiple(array $keys)
{
return wincache_ucache_get($keys);
}
/**
* {@inheritdoc}
*/
protected function doSaveMultiple(array $keysAndValues, $lifetime = 0)
{
$result = wincache_ucache_set($keysAndValues, null, $lifetime);
return empty($result);
}
/**
* {@inheritdoc}
*/
protected function doDeleteMultiple(array $keys)
{
$result = wincache_ucache_delete($keys);
return is_array($result) && count($result) !== count($keys);
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$info = wincache_ucache_info();
$meminfo = wincache_ucache_meminfo();
return [
Cache::STATS_HITS => $info['total_hit_count'],
Cache::STATS_MISSES => $info['total_miss_count'],
Cache::STATS_UPTIME => $info['total_cache_uptime'],
Cache::STATS_MEMORY_USAGE => $meminfo['memory_total'],
Cache::STATS_MEMORY_AVAILABLE => $meminfo['memory_free'],
];
}
}

View File

@ -0,0 +1,104 @@
<?php
namespace Doctrine\Common\Cache;
use BadMethodCallException;
use const XC_TYPE_VAR;
use function ini_get;
use function serialize;
use function unserialize;
use function xcache_clear_cache;
use function xcache_get;
use function xcache_info;
use function xcache_isset;
use function xcache_set;
use function xcache_unset;
/**
* Xcache cache driver.
*
* @deprecated
*
* @link www.doctrine-project.org
*/
class XcacheCache extends CacheProvider
{
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return $this->doContains($id) ? unserialize(xcache_get($id)) : false;
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return xcache_isset($id);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
return xcache_set($id, serialize($data), (int) $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return xcache_unset($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
$this->checkAuthorization();
xcache_clear_cache(XC_TYPE_VAR);
return true;
}
/**
* Checks that xcache.admin.enable_auth is Off.
*
* @return void
*
* @throws BadMethodCallException When xcache.admin.enable_auth is On.
*/
protected function checkAuthorization()
{
if (ini_get('xcache.admin.enable_auth')) {
throw new BadMethodCallException(
'To use all features of \Doctrine\Common\Cache\XcacheCache, '
. 'you must set "xcache.admin.enable_auth" to "Off" in your php.ini.'
);
}
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
$this->checkAuthorization();
$info = xcache_info(XC_TYPE_VAR, 0);
return [
Cache::STATS_HITS => $info['hits'],
Cache::STATS_MISSES => $info['misses'],
Cache::STATS_UPTIME => null,
Cache::STATS_MEMORY_USAGE => $info['size'],
Cache::STATS_MEMORY_AVAILABLE => $info['avail'],
];
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace Doctrine\Common\Cache;
use function zend_shm_cache_clear;
use function zend_shm_cache_delete;
use function zend_shm_cache_fetch;
use function zend_shm_cache_store;
/**
* Zend Data Cache cache driver.
*
* @link www.doctrine-project.org
*/
class ZendDataCache extends CacheProvider
{
/**
* {@inheritdoc}
*/
protected function doFetch($id)
{
return zend_shm_cache_fetch($id);
}
/**
* {@inheritdoc}
*/
protected function doContains($id)
{
return zend_shm_cache_fetch($id) !== false;
}
/**
* {@inheritdoc}
*/
protected function doSave($id, $data, $lifeTime = 0)
{
return zend_shm_cache_store($id, $data, $lifeTime);
}
/**
* {@inheritdoc}
*/
protected function doDelete($id)
{
return zend_shm_cache_delete($id);
}
/**
* {@inheritdoc}
*/
protected function doFlush()
{
$namespace = $this->getNamespace();
if (empty($namespace)) {
return zend_shm_cache_clear();
}
return zend_shm_cache_clear($namespace);
}
/**
* {@inheritdoc}
*/
protected function doGetStats()
{
return null;
}
}