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

21
vendor/sonata-project/cache/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2010-2016 Thomas Rabaix
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

23
vendor/sonata-project/cache/Makefile vendored Normal file
View File

@@ -0,0 +1,23 @@
# DO NOT EDIT THIS FILE!
#
# It's auto-generated by sonata-project/dev-kit package.
.PHONY: test docs
all:
@echo "Please choose a task."
lint:
composer validate
find . -name '*.yml' -not -path './vendor/*' -not -path './Resources/public/vendor/*' | xargs yaml-lint
find . \( -name '*.xml' -or -name '*.xliff' \) \
-not -path './vendor/*' -not -path './Resources/public/vendor/*' \
| xargs -I'{}' xmllint --encode UTF-8 --output '{}' --format '{}'
php-cs-fixer fix --verbose
git diff --exit-code
test:
phpunit -c phpunit.xml.dist --coverage-clover build/logs/clover.xml
docs:
cd docs && sphinx-build -W -b html -d _build/doctrees . _build/html

View File

@@ -0,0 +1,57 @@
{
"name": "sonata-project/cache",
"description": "Cache library",
"keywords": ["cache", "redis", "mongodb", "memcached"],
"homepage": "https://github.com/sonata-project/cache",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Thomas Rabaix",
"email": "thomas.rabaix@gmail.com",
"homepage": "https://sonata-project.org/"
},
{
"name": "Sonata Community",
"homepage": "https://github.com/sonata-project/cache/contributors"
}
],
"require": {
"php": "^7.1",
"psr/log": "^1.0"
},
"require-dev": {
"doctrine/orm": "^2.5",
"doctrine/phpcr-odm": "^1.4",
"jackalope/jackalope-doctrine-dbal": "^1.2",
"predis/predis": "^1.1",
"sllh/php-cs-fixer-styleci-bridge": "^2.1",
"symfony/phpunit-bridge": "^3.3"
},
"suggest": {
"doctrine/orm": "ORM support",
"doctrine/phpcr-odm": "PHPCR ODM support",
"ext-apc": "Caching with ext/apc",
"ext-memcached": "Caching with ext/memcached",
"predis/predis": "Install redis php"
},
"autoload": {
"psr-4": { "Sonata\\Cache\\": "src/" }
},
"autoload-dev": {
"psr-4": { "Sonata\\Cache\\Tests\\": "tests/" }
},
"conflicts": {
"doctrine/orm": "<2.5",
"doctrine/phpcr-odm": "<1.4",
"predis/predis": "<1.1"
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
}
}

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="Cache Test Suite">
<directory suffix="Test.php">./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./test/</directory>
</exclude>
</whitelist>
</filter>
<php>
<ini name="precision" value="8"/>
</php>
</phpunit>

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;
}
}