Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Adapter\Cache;
/**
* Handles Apc Cache.
*/
class ApcCache extends OpCodeCache
{
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Adapter\Cache;
use Sonata\Cache\CacheAdapterInterface;
use Sonata\Cache\CacheElement;
use Sonata\Cache\CacheElementInterface;
abstract class BaseCacheHandler implements CacheAdapterInterface
{
/**
* @param array $keys
* @param mixed $data
*
* @return CacheElementInterface
*/
protected function handleGet(array $keys, $data = null): CacheElementInterface
{
if ($data instanceof CacheElementInterface) {
return $data;
}
return new CacheElement($keys, null, -1000);
}
}

View File

@@ -0,0 +1,120 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Adapter\Cache;
use Sonata\Cache\CacheElement;
use Sonata\Cache\CacheElementInterface;
class MemcachedCache extends BaseCacheHandler
{
protected $servers;
protected $prefix;
protected $collection;
/**
* @param $prefix
* @param array $servers
*/
public function __construct(string $prefix, array $servers)
{
$this->prefix = $prefix;
$this->servers = $servers;
}
/**
* {@inheritdoc}
*/
public function flushAll(): bool
{
return $this->getCollection()->flush();
}
/**
* {@inheritdoc}
*/
public function flush(array $keys = []): bool
{
return $this->getCollection()->delete($this->computeCacheKeys($keys));
}
/**
* {@inheritdoc}
*/
public function has(array $keys): bool
{
return false !== $this->getCollection()->get($this->computeCacheKeys($keys));
}
/**
* {@inheritdoc}
*/
public function set(array $keys, $data, int $ttl = CacheElement::DAY, array $contextualKeys = []): CacheElementInterface
{
$cacheElement = new CacheElement($keys, $data, $ttl);
$this->getCollection()->set(
$this->computeCacheKeys($keys),
$cacheElement,
/*
* The driver does not seems to behave as documented, so we provide a timestamp if the ttl > 30d
* http://code.google.com/p/memcached/wiki/NewProgramming#Cache_Invalidation
*/
$cacheElement->getTtl() + ($cacheElement->getTtl() > 2592000 ? time() : 0)
);
return $cacheElement;
}
/**
* {@inheritdoc}
*/
public function get(array $keys): CacheElementInterface
{
return $this->handleGet($keys, $this->getCollection()->get($this->computeCacheKeys($keys)));
}
/**
* {@inheritdoc}
*/
public function isContextual(): bool
{
return false;
}
/**
* {@inheritdoc}
*/
private function getCollection(): \Memcached
{
if (!$this->collection) {
$this->collection = new \Memcached();
foreach ($this->servers as $server) {
$this->collection->addServer($server['host'], $server['port'], $server['weight']);
}
}
return $this->collection;
}
/**
* {@inheritdoc}
*/
private function computeCacheKeys(array $keys): string
{
ksort($keys);
return md5($this->prefix.serialize($keys));
}
}

View File

@@ -0,0 +1,154 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Adapter\Cache;
use Sonata\Cache\CacheElement;
use Sonata\Cache\CacheElementInterface;
class MongoCache extends BaseCacheHandler
{
protected $collection;
private $servers;
private $databaseName;
private $collectionName;
/**
* @param array $servers
* @param $database
* @param $collection
*/
public function __construct(array $servers, string $database, string $collection)
{
$this->servers = $servers;
$this->databaseName = $database;
$this->collectionName = $collection;
}
/**
* {@inheritdoc}
*/
public function flushAll(): bool
{
return $this->flush([]);
}
/**
* {@inheritdoc}
*/
public function flush(array $keys = []): bool
{
$result = $this->getCollection()->remove($keys, [
'w' => 1,
]);
return 1 == $result['ok'] && null === $result['err'];
}
/**
* {@inheritdoc}
*/
public function has(array $keys): bool
{
$keys['_timeout'] = ['$gt' => time()];
return $this->getCollection()->count($keys) > 0;
}
/**
* Returns the valid Mongo class client for the current php driver.
*
* @return string
*/
public static function getMongoClass()
{
if (class_exists('\MongoClient')) {
return '\MongoClient';
}
return '\Mongo';
}
/**
* {@inheritdoc}
*/
public function set(array $keys, $data, int $ttl = CacheElement::DAY, array $contextualKeys = []): CacheElementInterface
{
$time = time();
$cacheElement = new CacheElement($keys, $data, $ttl, $contextualKeys);
$keys = $cacheElement->getContextualKeys() + $cacheElement->getKeys();
$keys['_value'] = new \MongoBinData(serialize($cacheElement), \MongoBinData::BYTE_ARRAY);
$keys['_updated_at'] = $time;
$keys['_timeout'] = $time + $cacheElement->getTtl();
$this->getCollection()->save($keys);
return $cacheElement;
}
/**
* {@inheritdoc}
*/
public function get(array $keys): CacheElementInterface
{
$record = $this->getRecord($keys);
return $this->handleGet($keys, $record ? unserialize($record['_value']->bin) : null);
}
/**
* {@inheritdoc}
*/
public function isContextual(): bool
{
return true;
}
/**
* @return \MongoCollection
*/
private function getCollection(): \MongoCollection
{
if (!$this->collection) {
$class = self::getMongoClass();
$mongo = new $class(sprintf('mongodb://%s', implode(',', $this->servers)));
$this->collection = $mongo
->selectDB($this->databaseName)
->selectCollection($this->collectionName);
}
return $this->collection;
}
/**
* @param array $keys
*
* @return array|null
*/
private function getRecord(array $keys)
{
$keys['_timeout'] = ['$gt' => time()];
$results = $this->getCollection()->find($keys);
if ($results->hasNext()) {
return $results->getNext();
}
return;
}
}

View File

@@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Adapter\Cache;
use Sonata\Cache\CacheElement;
use Sonata\Cache\CacheElementInterface;
use Sonata\Cache\Exception\UnsupportedException;
class NoopCache extends BaseCacheHandler
{
/**
* {@inheritdoc}
*/
public function flushAll(): bool
{
return true;
}
/**
* {@inheritdoc}
*/
public function flush(array $keys = []): bool
{
return true;
}
/**
* {@inheritdoc}
*/
public function has(array $keys): bool
{
return false;
}
/**
* {@inheritdoc}
*/
public function set(array $keys, $data, int $ttl = CacheElement::DAY, array $contextualKeys = []): CacheElementInterface
{
return new CacheElement($keys, $data, $ttl, $contextualKeys);
}
/**
* {@inheritdoc}
*/
public function get(array $keys): CacheElementInterface
{
throw new UnsupportedException('The NoopCache::get() cannot called');
}
/**
* {@inheritdoc}
*/
public function isContextual(): bool
{
return false;
}
}

View File

@@ -0,0 +1,239 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Adapter\Cache;
use Sonata\Cache\CacheElement;
use Sonata\Cache\CacheElementInterface;
use Sonata\Cache\Exception\UnsupportedException;
/**
* Handles OpCode cache.
*
* For user cache this Adapter use this extensions:
* - Apc extension for PHP version < 5.5.0 (see http://php.net/manual/fr/book.apc.php)
* - Apcu extension for PHP version >= 5.5.0 (see https://github.com/krakjoe/apcu)
* And for opcode cache use the Apc extension for PHP version < 5.5.0 and opcache instead (see http://php.net/manual/fr/book.opcache.php)
*
* @author Amine Zaghdoudi <amine.zaghdoudi@ekino.com>
*/
class OpCodeCache extends BaseCacheHandler
{
/**
* @var string
*/
protected $url;
/**
* @var string
*/
protected $prefix;
/**
* @var array
*/
protected $servers;
/**
* @var bool
*/
protected $currentOnly;
/**
* @var array
*/
protected $timeout = [];
/**
* Constructor.
*
* @param string $url A router instance
* @param string $prefix A prefix to avoid clash between instances
* @param array $servers An array of servers
* @param array $timeout An array of timeout options
*/
public function __construct(string $url, string $prefix, array $servers, array $timeout = [])
{
$this->url = $url;
$this->prefix = $prefix;
$this->servers = $servers;
$defaultTimeout = [
'sec' => 5,
'usec' => 0,
];
$this->timeout['RCV'] = isset($timeout['RCV']) ? array_merge($defaultTimeout, $timeout['RCV']) : $defaultTimeout;
$this->timeout['SND'] = isset($timeout['SND']) ? array_merge($defaultTimeout, $timeout['SND']) : $defaultTimeout;
}
/**
* @param bool $bool
*/
public function setCurrentOnly($bool): void
{
$this->currentOnly = $bool;
}
/**
* {@inheritdoc}
*/
public function flushAll(): bool
{
if ($this->currentOnly) {
if (version_compare(PHP_VERSION, '5.5.0', '>=') && function_exists('opcache_reset')) {
opcache_reset();
}
if (function_exists('apc_clear_cache')) {
apc_clear_cache('user') && apc_clear_cache();
}
return true;
}
$result = true;
foreach ($this->servers as $server) {
if (4 == count(explode('.', $server['ip']))) {
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
} else {
$socket = socket_create(AF_INET6, SOCK_STREAM, SOL_TCP);
}
// generate the raw http request
$command = sprintf("GET %s HTTP/1.1\r\n", $this->getUrl());
$command .= sprintf("Host: %s\r\n", $server['domain']);
if ($server['basic']) {
$command .= sprintf("Authorization: Basic %s\r\n", $server['basic']);
}
$command .= "Connection: Close\r\n\r\n";
// setup the default timeout (avoid max execution time)
socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $this->timeout['SND']);
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $this->timeout['RCV']);
socket_connect($socket, $server['ip'], $server['port']);
socket_write($socket, $command);
$content = '';
do {
$buffer = socket_read($socket, 1024);
$content .= $buffer;
} while (!empty($buffer));
if ($result) {
$result = 'ok' == substr($content, -2);
}
}
return $result;
}
/**
* {@inheritdoc}
*/
public function flush(array $keys = []): bool
{
if ($this->currentOnly) {
$this->checkApc();
return apc_delete($this->computeCacheKeys($keys));
}
return $this->flushAll();
}
/**
* {@inheritdoc}
*/
public function has(array $keys): bool
{
$this->checkApc();
return apc_exists($this->computeCacheKeys($keys));
}
/**
* {@inheritdoc}
*/
public function set(array $keys, $data, int $ttl = CacheElement::DAY, array $contextualKeys = []): CacheElementInterface
{
$this->checkApc();
$cacheElement = new CacheElement($keys, $data, $ttl);
apc_store(
$this->computeCacheKeys($keys),
$cacheElement,
$cacheElement->getTtl()
);
return $cacheElement;
}
/**
* {@inheritdoc}
*/
public function get(array $keys): CacheElementInterface
{
$this->checkApc();
return $this->handleGet($keys, apc_fetch($this->computeCacheKeys($keys)));
}
/**
* {@inheritdoc}
*/
public function isContextual(): bool
{
return false;
}
/**
* @return string
*/
protected function getUrl()
{
return $this->url;
}
/**
* Check that Apc is enabled.
*
* @return bool
*
* @throws UnsupportedException
*/
protected function checkApc()
{
if (!extension_loaded('apc') || !ini_get('apc.enabled')) {
throw new UnsupportedException(__CLASS__.' does not support data caching. you should install APC or APCu to use it');
}
}
/**
* Computes the given cache keys.
*
* @param array $keys
*
* @return string
*/
protected function computeCacheKeys($keys)
{
ksort($keys);
return md5($this->prefix.serialize($keys));
}
}

View File

@@ -0,0 +1,153 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Adapter\Cache;
use Predis\Client;
use Predis\Connection\PredisCluster;
use Sonata\Cache\CacheElement;
use Sonata\Cache\CacheElementInterface;
class PRedisCache extends BaseCacheHandler
{
protected $parameters;
protected $options;
/**
* @var Client
*/
protected $client;
/**
* @param array $parameters
* @param array $options
*/
public function __construct(array $parameters = [], array $options = [])
{
$this->parameters = $parameters;
$this->options = $options;
}
/**
* {@inheritdoc}
*/
public function flushAll(): bool
{
$command = $this->getClient()->createCommand('flushdb');
$connection = $this->getClient()->getConnection();
if ($connection instanceof PredisCluster) {
foreach ($connection->executeCommandOnNodes($command) as $status) {
if (!$status) {
return false;
}
}
return true;
}
return $this->getClient()->executeCommand($command);
}
/**
* {@inheritdoc}
*/
public function flush(array $keys = []): bool
{
$this->getClient()->del($this->computeCacheKeys($keys));
// http://redis.io/commands/del
// it is not possible to know is the command succeed as the del command returns
// the number of row deleted.
// we can flush an non existant row
return true;
}
/**
* {@inheritdoc}
*/
public function has(array $keys): bool
{
return $this->getClient()->exists($this->computeCacheKeys($keys));
}
/**
* {@inheritdoc}
*/
public function set(array $keys, $data, int $ttl = CacheElement::DAY, array $contextualKeys = []): CacheElementInterface
{
$cacheElement = new CacheElement($keys, $data, $ttl);
$key = $this->computeCacheKeys($keys);
$this->getClient()->hset($key, 'sonata__data', serialize($cacheElement));
foreach ($contextualKeys as $name => $value) {
if (!is_scalar($value)) {
$value = serialize($value);
}
$this->getClient()->hset($key, $name, $value);
}
foreach ($keys as $name => $value) {
if (!is_scalar($value)) {
$value = serialize($value);
}
$this->getClient()->hset($key, $name, $value);
}
$this->getClient()->expire($key, $cacheElement->getTtl());
return $cacheElement;
}
/**
* {@inheritdoc}
*/
public function get(array $keys): CacheElementInterface
{
return $this->handleGet($keys, unserialize($this->getClient()->hget($this->computeCacheKeys($keys), 'sonata__data')));
}
/**
* {@inheritdoc}
*/
public function isContextual(): bool
{
return false;
}
/**
* @return Client
*/
protected function getClient(): Client
{
if (!$this->client) {
$this->client = new Client($this->parameters, $this->options);
}
return $this->client;
}
/**
* {@inheritdoc}
*/
private function computeCacheKeys(array $keys): string
{
ksort($keys);
return md5(serialize($keys));
}
}

View File

@@ -0,0 +1,77 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Adapter\Counter;
use Sonata\Cache\Counter;
/**
* Handles APC cache.
*/
class ApcCounter extends BaseCounter
{
/**
* @var string
*/
protected $prefix;
/**
* Constructor.
*
* @param string $prefix A prefix to avoid clash between instances
*/
public function __construct(string $prefix)
{
$this->prefix = $prefix;
}
/**
* {@inheritdoc}
*/
public function increment(Counter $counter, int $number = 1): Counter
{
$counter = $this->transform($counter);
$value = apc_inc($this->prefix.'/'.$counter->getName(), $number);
return $this->handleIncrement($value, $counter, $number);
}
/**
* {@inheritdoc}
*/
public function decrement(Counter $counter, int $number = 1): Counter
{
$counter = $this->transform($counter);
$value = apc_dec($this->prefix.'/'.$counter->getName(), $number);
return $this->handleDecrement($value, $counter, $number);
}
/**
* {@inheritdoc}
*/
public function set(Counter $counter): Counter
{
apc_store($this->prefix.'/'.$counter->getName(), $counter->getValue());
return $counter;
}
/**
* {@inheritdoc}
*/
public function get(string $name): Counter
{
return Counter::create($name, (int) apc_fetch($this->prefix.'/'.$name));
}
}

View File

@@ -0,0 +1,68 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Adapter\Counter;
use Sonata\Cache\Counter;
use Sonata\Cache\CounterAdapterInterface;
abstract class BaseCounter implements CounterAdapterInterface
{
/**
* @param $counter
*
* @return Counter
*/
protected function transform($counter): Counter
{
if ($counter instanceof Counter) {
return $counter;
}
return Counter::create($counter);
}
/**
* @param mixed $value
* @param Counter $counter
* @param int $number
*
* @return Counter
*/
protected function handleIncrement($value, Counter $counter, int $number): Counter
{
if (false === $value) {
$counter = $this->set(Counter::create($counter->getName(), $counter->getValue() + $number));
} else {
$counter = Counter::create($counter->getName(), $value);
}
return $counter;
}
/**
* @param mixed $value
* @param Counter $counter
* @param int $number
*
* @return Counter
*/
protected function handleDecrement($value, Counter $counter, int $number): Counter
{
if (false === $value) {
$counter = $this->set(Counter::create($counter->getName(), $counter->getValue() + (-1 * $number)));
} else {
$counter = Counter::create($counter->getName(), $value);
}
return $counter;
}
}

View File

@@ -0,0 +1,91 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Adapter\Counter;
use Sonata\Cache\Counter;
class MemcachedCounter extends BaseCounter
{
protected $servers;
protected $prefix;
protected $collection;
/**
* @param $prefix
* @param array $servers
*/
public function __construct(string $prefix, array $servers)
{
$this->prefix = $prefix;
$this->servers = $servers;
}
/**
* {@inheritdoc}
*/
public function increment(Counter $counter, int $number = 1): Counter
{
$counter = $this->transform($counter);
$value = $this->getCollection()->increment($this->prefix.'.'.$counter->getName(), $number);
return $this->handleIncrement(0 !== $this->getCollection()->getResultCode() ? false : $value, $counter, $number);
}
/**
* {@inheritdoc}
*/
public function decrement(Counter $counter, int $number = 1): Counter
{
$counter = $this->transform($counter);
$value = $this->getCollection()->decrement($this->prefix.'.'.$counter->getName(), $number);
return $this->handleDecrement(0 !== $this->getCollection()->getResultCode() ? false : $value, $counter, $number);
}
/**
* {@inheritdoc}
*/
public function set(Counter $counter): Counter
{
$this->getCollection()->add($this->prefix.'.'.$counter->getName(), $counter->getValue());
return $counter;
}
/**
* {@inheritdoc}
*/
public function get(string $name): Counter
{
return Counter::create($name, (int) $this->getCollection()->get($this->prefix.'.'.$name));
}
/**
* {@inheritdoc}
*/
private function getCollection(): \Memcached
{
if (!$this->collection) {
$this->collection = new \Memcached();
foreach ($this->servers as $server) {
$this->collection->addServer($server['host'], $server['port'], $server['weight']);
}
}
return $this->collection;
}
}

View File

@@ -0,0 +1,114 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Adapter\Counter;
use Sonata\Cache\Adapter\Cache\MongoCache;
use Sonata\Cache\Counter;
class MongoCounter extends BaseCounter
{
protected $collection;
private $servers;
private $databaseName;
private $collectionName;
/**
* @param array $servers
* @param $database
* @param $collection
*/
public function __construct(array $servers, string $database, string $collection)
{
$this->servers = $servers;
$this->databaseName = $database;
$this->collectionName = $collection;
}
/**
* {@inheritdoc}
*/
public function increment(Counter $counter, int $number = 1): Counter
{
$counter = $this->transform($counter);
$result = $this->getCollection()->findAndModify(
['counter' => $counter->getName()],
['$inc' => ['value' => $number]],
[],
['new' => true]
);
return $this->handleIncrement(0 === count($result) ? false : $result['value'], $counter, $number);
}
/**
* {@inheritdoc}
*/
public function decrement(Counter $counter, int $number = 1): Counter
{
$counter = $this->transform($counter);
$result = $this->getCollection()->findAndModify(
['counter' => $counter->getName()],
['$inc' => ['value' => -1 * $number]],
[],
['new' => true]
);
return $this->handleDecrement(0 === count($result) ? false : $result['value'], $counter, $number);
}
/**
* {@inheritdoc}
*/
public function set(Counter $counter): Counter
{
$result = $this->getCollection()->findAndModify(
['counter' => $counter->getName()],
['$setOnInsert' => ['value' => $counter->getValue()]],
[],
['upsert' => true, 'new' => true]
);
return Counter::create($counter->getName(), $result['value']);
}
/**
* {@inheritdoc}
*/
public function get(string $name): Counter
{
$result = $this->getCollection()->findOne(['counter' => $name]);
return Counter::create($name, $result ? (int) $result['value'] : 0);
}
/**
* @return \MongoCollection
*/
private function getCollection(): \MongoCollection
{
if (!$this->collection) {
$class = MongoCache::getMongoClass();
$mongo = new $class(sprintf('mongodb://%s', implode(',', $this->servers)));
$this->collection = $mongo
->selectDB($this->databaseName)
->selectCollection($this->collectionName);
}
return $this->collection;
}
}

View File

@@ -0,0 +1,96 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Adapter\Counter;
use Predis\Client;
use Sonata\Cache\Counter;
class PRedisCounter extends BaseCounter
{
protected $options;
protected $parameters;
protected $client;
/**
* @param array $parameters
* @param array $options
*/
public function __construct(array $parameters = [], array $options = [])
{
$this->parameters = $parameters;
$this->options = $options;
}
/**
* {@inheritdoc}
*/
public function increment(Counter $counter, int $number = 1): Counter
{
$counter = $this->transform($counter);
if (null === $this->getClient()->get($counter->getName())) {
$this->getClient()->set($counter->getName(), $value = $counter->getValue() + $number);
} else {
$value = $this->getClient()->incrby($counter->getName(), $number);
}
return Counter::create($counter->getName(), $value);
}
/**
* {@inheritdoc}
*/
public function decrement(Counter $counter, int $number = 1): Counter
{
$counter = $this->transform($counter);
if (null === $this->getClient()->get($counter->getName())) {
$this->getClient()->set($counter->getName(), $value = $counter->getValue() - $number);
} else {
$value = $this->getClient()->decrby($counter->getName(), $number);
}
return Counter::create($counter->getName(), $value);
}
/**
* {@inheritdoc}
*/
public function set(Counter $counter): Counter
{
$this->getClient()->set($counter->getName(), $counter->getValue());
return $counter;
}
/**
* {@inheritdoc}
*/
public function get(string $name): Counter
{
return Counter::create($name, (int) $this->getClient()->get($name));
}
/**
* @return Client
*/
private function getClient(): Client
{
if (!$this->client) {
$this->client = new Client($this->parameters, $this->options);
}
return $this->client;
}
}

View File

@@ -0,0 +1,68 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache;
interface CacheAdapterInterface
{
/**
* Gets data from cache.
*
* @param array $keys
*
* @return CacheElementInterface
*/
public function get(array $keys): CacheElementInterface;
/**
* Returns TRUE whether cache contains data identified by keys.
*
* @param array $keys
*
* @return bool
*/
public function has(array $keys): bool;
/**
* Sets value in cache.
*
* @param array $keys An array of keys
* @param mixed $value Value to store
* @param int $ttl A time to live, default 86400 seconds (CacheElement::DAY)
* @param array $contextualKeys An array of contextual keys
*
* @return CacheElementInterface
*/
public function set(array $keys, $value, int $ttl = CacheElement::DAY, array $contextualKeys = []): CacheElementInterface;
/**
* Flushes data from cache identified by keys.
*
* @param array $keys
*
* @return bool
*/
public function flush(array $keys = []): bool;
/**
* Flushes all data from cache.
*
* @return bool
*/
public function flushAll(): bool;
/**
* Returns TRUE whether cache is contextual.
*
* @return bool
*/
public function isContextual(): bool;
}

View File

@@ -0,0 +1,118 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache;
final class CacheElement implements CacheElementInterface
{
const MINUTE = 60;
const HOUR = 3600;
const DAY = 86400;
const WEEK = 604800;
const MONTH = 2.63e+6;
/**
* @var int
*/
private $ttl;
/**
* @var array
*/
private $keys = [];
/**
* @var mixed
*/
private $data;
/**
* @var \DateTime
*/
private $createdAt;
/**
* @var array
*/
private $contextualKeys = [];
/**
* Constructor.
*
* @param array $keys An array of keys
* @param mixed $data Data
* @param int $ttl A time to live, default 86400 seconds (CacheElement::DAY)
* @param array $contextualKeys An array of contextual keys
*/
public function __construct(array $keys, $data, int $ttl = self::DAY, array $contextualKeys = [])
{
$this->createdAt = new \DateTime();
$this->keys = $keys;
$this->ttl = $ttl;
$this->data = $data;
$this->contextualKeys = $contextualKeys;
}
/**
* {@inheritdoc}
*/
public function getKeys(): array
{
return $this->keys;
}
/**
* {@inheritdoc}
*/
public function getTtl(): int
{
return $this->ttl;
}
/**
* {@inheritdoc}
*/
public function getData()
{
return $this->data;
}
/**
* {@inheritdoc}
*/
public function isExpired(): bool
{
return strtotime('now') > ($this->createdAt->format('U') + $this->ttl);
}
/**
* {@inheritdoc}
*/
public function getExpirationDate(): \DateTimeInterface
{
if ($this->isExpired()) {
return new \DateTime();
}
$date = clone $this->createdAt;
$date = $date->add(new \DateInterval(sprintf('PT%sS', $this->ttl)));
return $date;
}
/**
* {@inheritdoc}
*/
public function getContextualKeys(): array
{
return $this->contextualKeys;
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache;
interface CacheElementInterface
{
/**
* Returns the keys.
*
* @return array
*/
public function getKeys(): array;
/**
* Returns the time to live.
*
* @return int
*/
public function getTtl(): int;
/**
* Returns the data.
*
* @return mixed
*/
public function getData();
/**
* Returns TRUE whether the cache is expired.
*
* @return bool
*/
public function isExpired(): bool;
/**
* @return \DateTime
*/
public function getExpirationDate(): \DateTimeInterface;
/**
* Returns the contextual keys.
*
* @return array
*/
public function getContextualKeys(): array;
}

View File

@@ -0,0 +1,105 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache;
use Sonata\Cache\Invalidation\InvalidationInterface;
use Sonata\Cache\Invalidation\Recorder;
class CacheManager implements CacheManagerInterface
{
/**
* @var InvalidationInterface
*/
protected $cacheInvalidation;
/**
* @var array
*/
protected $cacheServices = [];
/**
* @var Recorder
*/
protected $recorder;
/**
* Constructor.
*
* @param InvalidationInterface $cacheInvalidation A cache invalidation instance
* @param array $cacheServices An array of cache services
*/
public function __construct(InvalidationInterface $cacheInvalidation, array $cacheServices)
{
$this->cacheInvalidation = $cacheInvalidation;
$this->cacheServices = $cacheServices;
}
/**
* {@inheritdoc}
*/
public function addCacheService(string $name, CacheAdapterInterface $cacheManager): void
{
$this->cacheServices[$name] = $cacheManager;
}
/**
* {@inheritdoc}
*/
public function getCacheService(string $name): CacheAdapterInterface
{
if (!$this->hasCacheService($name)) {
throw new \RuntimeException(sprintf('The cache service %s does not exist.', $name));
}
return $this->cacheServices[$name];
}
/**
* {@inheritdoc}
*/
public function getCacheServices(): array
{
return $this->cacheServices;
}
/**
* {@inheritdoc}
*/
public function hasCacheService(string $id): bool
{
return isset($this->cacheServices[$id]) ? true : false;
}
/**
* {@inheritdoc}
*/
public function invalidate(array $keys): void
{
$this->cacheInvalidation->invalidate($this->getCacheServices(), $keys);
}
/**
* {@inheritdoc}
*/
public function setRecorder(Recorder $recorder): void
{
$this->recorder = $recorder;
}
/**
* {@inheritdoc}
*/
public function getRecorder(): Recorder
{
return $this->recorder;
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache;
use Sonata\Cache\Invalidation\Recorder;
interface CacheManagerInterface
{
/**
* Adds a cache service.
*
* @param string $name A cache name
* @param CacheAdapterInterface $cacheManager A cache service
*/
public function addCacheService(string $name, CacheAdapterInterface $cacheManager): void;
/**
* Gets a cache service by a given name.
*
* @param string $name A cache name
*
* @return CacheAdapterInterface
*/
public function getCacheService(string $name): CacheAdapterInterface;
/**
* Returns related cache services.
*
* @return array
*/
public function getCacheServices(): array;
/**
* Returns TRUE whether a cache service identified by id exists.
*
* @param string $id
*
* @return bool
*/
public function hasCacheService(string $id): bool;
/**
* Invalidates the cache by the given keys.
*
* @param array $keys
*/
public function invalidate(array $keys): void;
/**
* Sets the recorder.
*
* @param Recorder $recorder
*/
public function setRecorder(Recorder $recorder): void;
/**
* Gets the recorder.
*
* @return Recorder
*/
public function getRecorder(): Recorder;
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache;
final class Counter
{
private $name;
private $value;
/**
* @param string $name
* @param int $value
*/
private function __construct(string $name, int $value = 0)
{
$this->name = $name;
$this->value = $value;
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @return int
*/
public function getValue(): int
{
return $this->value;
}
/**
* @param string $name
* @param int $value
*
* @return Counter
*/
public static function create(string $name, int $value = 0): self
{
return new self($name, $value);
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache;
interface CounterAdapterInterface
{
/**
* @param Counter|string $counter
* @param int $number
*
* @return Counter
*/
public function increment(Counter $counter, int $number = 1): Counter;
/**
* @param Counter|string $counter
* @param int $number
*
* @return Counter
*/
public function decrement(Counter $counter, int $number = 1): Counter;
/**
* @param Counter $counter
*
* @return Counter
*/
public function set(Counter $counter): Counter;
/**
* @param string $name
*
* @return Counter
*/
public function get(string $name): Counter;
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Exception;
class NotSupportedException extends \RuntimeException
{
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Exception;
/**
* UnsupportedException.
*
* @author Vincent Composieux <vincent.composieux@gmail.com>
*/
class UnsupportedException extends \Exception
{
}

View File

@@ -0,0 +1,97 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Invalidation;
use Doctrine\Common\EventSubscriber;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;
use Sonata\Cache\CacheAdapterInterface;
class DoctrineORMListener implements EventSubscriber
{
protected $caches = [];
protected $collectionIdentifiers;
/**
* @param ModelCollectionIdentifiers $collectionIdentifiers
* @param array $caches
*/
public function __construct(ModelCollectionIdentifiers $collectionIdentifiers, array $caches)
{
$this->collectionIdentifiers = $collectionIdentifiers;
foreach ($caches as $cache) {
$this->addCache($cache);
}
}
/**
* {@inheritdoc}
*/
public function getSubscribedEvents()
{
return [
Events::preRemove,
Events::preUpdate,
];
}
/**
* {@inheritdoc}
*/
public function preRemove(LifecycleEventArgs $args): void
{
$this->flush($args);
}
/**
* {@inheritdoc}
*/
public function preUpdate(LifecycleEventArgs $args): void
{
$this->flush($args);
}
/**
* @param CacheAdapterInterface $cache
*/
public function addCache(CacheAdapterInterface $cache): void
{
if (!$cache->isContextual()) {
return;
}
$this->caches[] = $cache;
}
/**
* {@inheritdoc}
*/
protected function flush(LifecycleEventArgs $args): void
{
$identifier = $this->collectionIdentifiers->getIdentifier($args->getEntity());
if (false === $identifier) {
return;
}
$parameters = [
ClassUtils::getClass($args->getEntity()) => $identifier,
];
foreach ($this->caches as $cache) {
$cache->flush($parameters);
}
}
}

View File

@@ -0,0 +1,97 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Invalidation;
use Doctrine\Common\EventSubscriber;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ODM\PHPCR\Event;
use Sonata\Cache\CacheAdapterInterface;
class DoctrinePHPCRODMListener implements EventSubscriber
{
protected $caches = [];
protected $collectionIdentifiers;
/**
* @param ModelCollectionIdentifiers $collectionIdentifiers
* @param array $caches
*/
public function __construct(ModelCollectionIdentifiers $collectionIdentifiers, array $caches)
{
$this->collectionIdentifiers = $collectionIdentifiers;
foreach ($caches as $cache) {
$this->addCache($cache);
}
}
/**
* {@inheritdoc}
*/
public function getSubscribedEvents()
{
return [
Event::preRemove,
Event::preUpdate,
];
}
/**
* {@inheritdoc}
*/
public function preRemove(LifecycleEventArgs $args): void
{
$this->flush($args);
}
/**
* {@inheritdoc}
*/
public function preUpdate(LifecycleEventArgs $args): void
{
$this->flush($args);
}
/**
* {@inheritdoc}
*/
public function addCache(CacheAdapterInterface $cache): void
{
if (!$cache->isContextual()) {
return;
}
$this->caches[] = $cache;
}
/**
* {@inheritdoc}
*/
protected function flush(LifecycleEventArgs $args): void
{
$identifier = $this->collectionIdentifiers->getIdentifier($args->getObject());
if (false === $identifier) {
return;
}
$parameters = [
ClassUtils::getClass($args->getObject()) => $identifier,
];
foreach ($this->caches as $cache) {
$cache->flush($parameters);
}
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Invalidation;
interface InvalidationInterface
{
/**
* @param array $caches
* @param array $array
*/
public function invalidate(array $caches, array $array);
}

View File

@@ -0,0 +1,82 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Invalidation;
class ModelCollectionIdentifiers
{
protected $classes = [];
/**
* @param array $classes
*/
public function __construct(array $classes = [])
{
foreach ($classes as $class => $identifier) {
$this->addClass($class, $identifier);
}
}
/**
* @param string $class
* @param mixed $identifier
*/
public function addClass(string $class, $identifier): void
{
$this->classes[$class] = $identifier;
}
/**
* @param $object
*
* @return bool|mixed
*/
public function getIdentifier($object)
{
$identifier = $this->getMethod($object);
if (!$identifier) {
return false;
}
return call_user_func([$object, $identifier]);
}
/**
* @param $object
*
* @return mixed
*/
public function getMethod($object)
{
if (null === $object) {
return false;
}
foreach ($this->classes as $class => $identifier) {
if ($object instanceof $class) {
return $identifier;
}
}
$class = get_class($object);
if (method_exists($object, 'getCacheIdentifier')) {
$this->addClass($class, 'getCacheIdentifier');
} elseif (method_exists($object, 'getId')) {
$this->addClass($class, 'getId');
} else {
return false;
}
return $this->classes[$class];
}
}

View File

@@ -0,0 +1,88 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Invalidation;
class Recorder
{
protected $collectionIdentifiers;
protected $stack = [];
protected $current = 0;
/**
* @param ModelCollectionIdentifiers $collectionIdentifiers
*/
public function __construct(ModelCollectionIdentifiers $collectionIdentifiers)
{
$this->collectionIdentifiers = $collectionIdentifiers;
$this->stack[$this->current] = [];
}
/**
* @param $object
*/
public function add($object): void
{
$class = get_class($object);
$identifier = $this->collectionIdentifiers->getIdentifier($object);
if (false === $identifier) {
return;
}
if (!isset($this->stack[$this->current][$class])) {
$this->stack[$this->current][$class] = [];
}
if (!in_array($identifier, $this->stack[$this->current][$class])) {
$this->stack[$this->current][$class][] = $identifier;
}
}
/**
* Add a new elements into the stack.
*/
public function push(): void
{
$this->stack[$this->current + 1] = $this->stack[$this->current];
++$this->current;
}
/**
* Remove an element from the stack and return it.
*
* @return array
*/
public function pop()
{
$value = $this->stack[$this->current];
unset($this->stack[$this->current]);
--$this->current;
if ($this->current < 0) {
$this->reset();
}
return $value;
}
public function reset(): void
{
$this->current = 0;
$this->stack = [];
}
}

View File

@@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\Cache\Invalidation;
use Psr\Log\LoggerInterface;
use Sonata\Cache\CacheAdapterInterface;
class SimpleCacheInvalidation implements InvalidationInterface
{
/**
* @var LoggerInterface
*/
protected $logger;
/**
* @param LoggerInterface $logger
*/
public function __construct(?LoggerInterface $logger = null)
{
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public function invalidate(array $caches, array $keys)
{
foreach ($caches as $cache) {
if (!$cache instanceof CacheAdapterInterface) {
throw new \RuntimeException('The object must implements the CacheAdapterInterface interface');
}
try {
if ($this->logger) {
$this->logger->info(sprintf('[%s] flushing cache keys : %s', __CLASS__, json_encode($keys)));
}
$cache->flush($keys);
} catch (\Exception $e) {
if ($this->logger) {
$this->logger->alert(sprintf('[%s] %s', __CLASS__, $e->getMessage()));
} else {
throw new \RunTimeException(null, null, $e);
}
}
}
return true;
}
}