Skip to content

Commit

Permalink
feature #20694 [Cache] Implement PSR-16 SimpleCache v1.0 (nicolas-gre…
Browse files Browse the repository at this point in the history
…kas)

This PR was squashed before being merged into the 3.3-dev branch (closes #20694).

Discussion
----------

[Cache] Implement PSR-16 SimpleCache v1.0

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | not yet
| Fixed tickets | -
| License       | MIT
| Doc PR        | symfony/symfony-docs#7409

Second iteration on the topic after #20636 raised some issues.

Please don't squash while merging.

Commits
-------

6219dd6 [Cache] Create PSR-16 variants of all PSR-6 adapters
99ae9d6 [Cache] Move adapter implementations to traits
848a33e [Cache] Implement PSR-16 SimpleCache v1.0
  • Loading branch information
fabpot committed Jan 23, 2017
2 parents 3f8aa7b + 6219dd6 commit 9d8c6c6
Show file tree
Hide file tree
Showing 67 changed files with 4,641 additions and 1,607 deletions.
6 changes: 4 additions & 2 deletions composer.json
Expand Up @@ -21,6 +21,7 @@
"twig/twig": "~1.28|~2.0",
"psr/cache": "~1.0",
"psr/log": "~1.0",
"psr/simple-cache": "^1.0",
"symfony/polyfill-intl-icu": "~1.0",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php56": "~1.0",
Expand Down Expand Up @@ -79,7 +80,7 @@
"symfony/yaml": "self.version"
},
"require-dev": {
"cache/integration-tests": "dev-master",
"cache/integration-tests": "^0.15.0",
"doctrine/cache": "~1.6",
"doctrine/data-fixtures": "1.0.*",
"doctrine/dbal": "~2.4",
Expand All @@ -99,7 +100,8 @@
"phpdocumentor/type-resolver": "<0.2.0"
},
"provide": {
"psr/cache-implementation": "1.0"
"psr/cache-implementation": "1.0",
"psr/simple-cache-implementation": "1.0"
},
"autoload": {
"psr-4": {
Expand Down
2 changes: 2 additions & 0 deletions phpunit.xml.dist
Expand Up @@ -62,6 +62,8 @@
<array>
<element><string>Cache\IntegrationTests</string></element>
<element><string>Doctrine\Common\Cache</string></element>
<element><string>Symfony\Component\Cache</string></element>
<element><string>Symfony\Component\Cache\Traits</string></element>
<element><string>Symfony\Component\Console</string></element>
<element><string>Symfony\Component\HttpFoundation</string></element>
</array>
Expand Down
187 changes: 2 additions & 185 deletions src/Symfony/Component/Cache/Adapter/AbstractAdapter.php
Expand Up @@ -13,31 +13,24 @@

use Psr\Cache\CacheItemInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Traits\AbstractTrait;

/**
* @author Nicolas Grekas <p@tchwork.com>
*/
abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface
{
use LoggerAwareTrait;
use AbstractTrait;

private static $apcuSupported;
private static $phpFilesSupported;

private $namespace;
private $deferred = array();
private $createCacheItem;
private $mergeByLifetime;

/**
* @var int|null The maximum length to enforce for identifiers or null when no limit applies
*/
protected $maxIdLength;

protected function __construct($namespace = '', $defaultLifetime = 0)
{
$this->namespace = '' === $namespace ? '' : $this->getId($namespace).':';
Expand Down Expand Up @@ -130,52 +123,6 @@ public static function createConnection($dsn, array $options = array())
throw new InvalidArgumentException(sprintf('Unsupported DSN: %s.', $dsn));
}

/**
* Fetches several cache items.
*
* @param array $ids The cache identifiers to fetch
*
* @return array|\Traversable The corresponding values found in the cache
*/
abstract protected function doFetch(array $ids);

/**
* Confirms if the cache contains specified cache item.
*
* @param string $id The identifier for which to check existence
*
* @return bool True if item exists in the cache, false otherwise
*/
abstract protected function doHave($id);

/**
* Deletes all items in the pool.
*
* @param string The prefix used for all identifiers managed by this pool
*
* @return bool True if the pool was successfully cleared, false otherwise
*/
abstract protected function doClear($namespace);

/**
* Removes multiple items from the pool.
*
* @param array $ids An array of identifiers that should be removed from the pool
*
* @return bool True if the items were successfully removed, false otherwise
*/
abstract protected function doDelete(array $ids);

/**
* Persists several cache items immediately.
*
* @param array $values The values to cache, indexed by their cache identifier
* @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning
*
* @return array|bool The identifiers that failed to be cached or a boolean stating if caching succeeded or not
*/
abstract protected function doSave(array $values, $lifetime);

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -225,87 +172,6 @@ public function getItems(array $keys = array())
return $this->generateItems($items, $ids);
}

/**
* {@inheritdoc}
*/
public function hasItem($key)
{
$id = $this->getId($key);

if (isset($this->deferred[$key])) {
$this->commit();
}

try {
return $this->doHave($id);
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached', array('key' => $key, 'exception' => $e));

return false;
}
}

/**
* {@inheritdoc}
*/
public function clear()
{
$this->deferred = array();

try {
return $this->doClear($this->namespace);
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to clear the cache', array('exception' => $e));

return false;
}
}

/**
* {@inheritdoc}
*/
public function deleteItem($key)
{
return $this->deleteItems(array($key));
}

/**
* {@inheritdoc}
*/
public function deleteItems(array $keys)
{
$ids = array();

foreach ($keys as $key) {
$ids[$key] = $this->getId($key);
unset($this->deferred[$key]);
}

try {
if ($this->doDelete($ids)) {
return true;
}
} catch (\Exception $e) {
}

$ok = true;

// When bulk-delete failed, retry each item individually
foreach ($ids as $key => $id) {
try {
$e = null;
if ($this->doDelete(array($id))) {
continue;
}
} catch (\Exception $e) {
}
CacheItem::log($this->logger, 'Failed to delete key "{key}"', array('key' => $key, 'exception' => $e));
$ok = false;
}

return $ok;
}

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -394,47 +260,6 @@ public function __destruct()
}
}

/**
* Like the native unserialize() function but throws an exception if anything goes wrong.
*
* @param string $value
*
* @return mixed
*
* @throws \Exception
*/
protected static function unserialize($value)
{
if ('b:0;' === $value) {
return false;
}
$unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
try {
if (false !== $value = unserialize($value)) {
return $value;
}
throw new \DomainException('Failed to unserialize cached value');
} catch (\Error $e) {
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
} finally {
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
}
}

private function getId($key)
{
CacheItem::validateKey($key);

if (null === $this->maxIdLength) {
return $this->namespace.$key;
}
if (strlen($id = $this->namespace.$key) > $this->maxIdLength) {
$id = $this->namespace.substr_replace(base64_encode(hash('sha256', $key, true)), ':', -22);
}

return $id;
}

private function generateItems($items, &$keys)
{
$f = $this->createCacheItem;
Expand All @@ -453,12 +278,4 @@ private function generateItems($items, &$keys)
yield $key => $f($key, null, false);
}
}

/**
* @internal
*/
public static function handleUnserializeCallback($class)
{
throw new \DomainException('Class not found: '.$class);
}
}
89 changes: 3 additions & 86 deletions src/Symfony/Component/Cache/Adapter/ApcuAdapter.php
Expand Up @@ -11,97 +11,14 @@

namespace Symfony\Component\Cache\Adapter;

use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\CacheException;
use Symfony\Component\Cache\Traits\ApcuTrait;

/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ApcuAdapter extends AbstractAdapter
{
public static function isSupported()
{
return function_exists('apcu_fetch') && ini_get('apc.enabled') && !('cli' === PHP_SAPI && !ini_get('apc.enable_cli'));
}
use ApcuTrait;

public function __construct($namespace = '', $defaultLifetime = 0, $version = null)
{
if (!static::isSupported()) {
throw new CacheException('APCu is not enabled');
}
if ('cli' === PHP_SAPI) {
ini_set('apc.use_request_time', 0);
}
parent::__construct($namespace, $defaultLifetime);

if (null !== $version) {
CacheItem::validateKey($version);

if (!apcu_exists($version.'@'.$namespace)) {
$this->clear($namespace);
apcu_add($version.'@'.$namespace, null);
}
}
}

/**
* {@inheritdoc}
*/
protected function doFetch(array $ids)
{
try {
return apcu_fetch($ids);
} catch (\Error $e) {
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
}
}

/**
* {@inheritdoc}
*/
protected function doHave($id)
{
return apcu_exists($id);
}

/**
* {@inheritdoc}
*/
protected function doClear($namespace)
{
return isset($namespace[0]) && class_exists('APCuIterator', false)
? apcu_delete(new \APCuIterator(sprintf('/^%s/', preg_quote($namespace, '/')), APC_ITER_KEY))
: apcu_clear_cache();
}

/**
* {@inheritdoc}
*/
protected function doDelete(array $ids)
{
foreach ($ids as $id) {
apcu_delete($id);
}

return true;
}

/**
* {@inheritdoc}
*/
protected function doSave(array $values, $lifetime)
{
try {
return array_keys(apcu_store($values, null, $lifetime));
} catch (\Error $e) {
} catch (\Exception $e) {
}

if (1 === count($values)) {
// Workaround https://github.com/krakjoe/apcu/issues/170
apcu_delete(key($values));
}

throw $e;
$this->init($namespace, $defaultLifetime, $version);
}
}

0 comments on commit 9d8c6c6

Please sign in to comment.