[Cache] serialize objects using native arrays when possible

This commit is contained in:
Nicolas Grekas 2018-06-07 16:35:59 +02:00
parent 3ccbec3497
commit 866420e2eb
37 changed files with 1390 additions and 88 deletions

View File

@ -20,6 +20,7 @@ return PhpCsFixer\Config::create()
->append(array(__FILE__))
->exclude(array(
// directories containing files with content that is autogenerated by `var_export`, which breaks CS in output code
'Symfony/Component/Cache/Tests/Marshaller/Fixtures',
'Symfony/Component/DependencyInjection/Tests/Fixtures',
'Symfony/Component/Routing/Tests/Fixtures/dumper',
// fixture templates

View File

@ -41,6 +41,7 @@
<argument /> <!-- namespace -->
<argument>0</argument> <!-- default lifetime -->
<argument>%kernel.cache_dir%/pools</argument>
<argument>true</argument>
<call method="setLogger">
<argument type="service" id="logger" on-invalid="ignore" />
</call>

View File

@ -87,16 +87,21 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
if (null === $this->values) {
$this->initialize();
}
if (null === $value = $this->values[$key] ?? null) {
if (!isset($this->keys[$key])) {
if ($this->pool instanceof CacheInterface) {
return $this->pool->get($key, $callback, $beta);
}
return $this->doGet($this->pool, $key, $callback, $beta ?? 1.0);
}
$value = $this->values[$this->keys[$key]];
if ('N;' === $value) {
return null;
}
if ($value instanceof \Closure) {
return $value();
}
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
return unserialize($value);
}
@ -115,15 +120,22 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
if (null === $this->values) {
$this->initialize();
}
if (!isset($this->values[$key])) {
if (!isset($this->keys[$key])) {
return $this->pool->getItem($key);
}
$value = $this->values[$key];
$value = $this->values[$this->keys[$key]];
$isHit = true;
if ('N;' === $value) {
$value = null;
} elseif ($value instanceof \Closure) {
try {
$value = $value();
} catch (\Throwable $e) {
$value = null;
$isHit = false;
}
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
$value = unserialize($value);
@ -167,7 +179,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
$this->initialize();
}
return isset($this->values[$key]) || $this->pool->hasItem($key);
return isset($this->keys[$key]) || $this->pool->hasItem($key);
}
/**
@ -182,7 +194,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
$this->initialize();
}
return !isset($this->values[$key]) && $this->pool->deleteItem($key);
return !isset($this->keys[$key]) && $this->pool->deleteItem($key);
}
/**
@ -198,7 +210,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key)));
}
if (isset($this->values[$key])) {
if (isset($this->keys[$key])) {
$deleted = false;
} else {
$fallbackKeys[] = $key;
@ -224,7 +236,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
$this->initialize();
}
return !isset($this->values[$item->getKey()]) && $this->pool->save($item);
return !isset($this->keys[$item->getKey()]) && $this->pool->save($item);
}
/**
@ -236,7 +248,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
$this->initialize();
}
return !isset($this->values[$item->getKey()]) && $this->pool->saveDeferred($item);
return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item);
}
/**
@ -253,11 +265,17 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
$fallbackKeys = array();
foreach ($keys as $key) {
if (isset($this->values[$key])) {
$value = $this->values[$key];
if (isset($this->keys[$key])) {
$value = $this->values[$this->keys[$key]];
if ('N;' === $value) {
yield $key => $f($key, null, true);
} elseif ($value instanceof \Closure) {
try {
yield $key => $f($key, $value(), true);
} catch (\Throwable $e) {
yield $key => $f($key, null, false);
}
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
yield $key => $f($key, unserialize($value), true);

View File

@ -20,10 +20,14 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
use PhpFilesTrait;
/**
* @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire.
* Doing so is encouraged because it fits perfectly OPcache's memory model.
*
* @throws CacheException if OPcache is not enabled
*/
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null)
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false)
{
$this->appendOnly = $appendOnly;
self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);

View File

@ -0,0 +1,184 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Marshaller;
use Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator;
use Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference;
use Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* PhpMarshaller allows serializing PHP data structures using var_export()
* while preserving all the semantics associated to serialize().
*
* By leveraging OPcache, the generated PHP code is faster than doing the same with unserialize().
*
* @internal
*/
class PhpMarshaller
{
public static function marshall($value, int &$objectsCount)
{
if (!\is_object($value) && !\is_array($value)) {
return $value;
}
$objectsPool = new \SplObjectStorage();
$value = array($value);
$objectsCount = self::doMarshall($value, $objectsPool);
$classes = array();
$values = array();
$wakeups = array();
foreach ($objectsPool as $i => $v) {
list(, $classes[], $values[], $wakeup) = $objectsPool[$v];
if ($wakeup) {
$wakeups[$wakeup] = $i;
}
}
ksort($wakeups);
$properties = array();
foreach ($values as $i => $vars) {
foreach ($vars as $class => $values) {
foreach ($values as $name => $v) {
$properties[$class][$name][$i] = $v;
}
}
}
if (!$classes) {
return $value[0];
}
return new Configurator(new Registry($classes), $properties, $value[0], $wakeups);
}
public static function optimize(string $exportedValue)
{
return preg_replace(sprintf("{%s::__set_state\(array\(\s++'0' => (\d+),\s++\)\)}", preg_quote(Reference::class)), Registry::class.'::$objects[$1]', $exportedValue);
}
private static function doMarshall(array &$array, \SplObjectStorage $objectsPool): int
{
$objectsCount = 0;
foreach ($array as &$value) {
if (\is_array($value) && $value) {
$objectsCount += self::doMarshall($value, $objectsPool);
}
if (!\is_object($value)) {
continue;
}
if (isset($objectsPool[$value])) {
++$objectsCount;
$value = new Reference($objectsPool[$value][0]);
continue;
}
$class = \get_class($value);
$properties = array();
$sleep = null;
$arrayValue = (array) $value;
$proto = (Registry::$reflectors[$class] ?? Registry::getClassReflector($class))->newInstanceWithoutConstructor();
if ($value instanceof \ArrayIterator || $value instanceof \ArrayObject) {
// ArrayIterator and ArrayObject need special care because their "flags"
// option changes the behavior of the (array) casting operator.
$reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject';
$reflector = Registry::$reflectors[$reflector] ?? Registry::getClassReflector($reflector);
$properties = array(
$arrayValue,
$reflector->getMethod('getFlags')->invoke($value),
$value instanceof \ArrayObject ? $reflector->getMethod('getIteratorClass')->invoke($value) : 'ArrayIterator',
);
$reflector = $reflector->getMethod('setFlags');
$reflector->invoke($proto, \ArrayObject::STD_PROP_LIST);
if ($properties[1] & \ArrayObject::STD_PROP_LIST) {
$reflector->invoke($value, 0);
$properties[0] = (array) $value;
} else {
$reflector->invoke($value, \ArrayObject::STD_PROP_LIST);
$arrayValue = (array) $value;
}
$reflector->invoke($value, $properties[1]);
if (array(array(), 0, 'ArrayIterator') === $properties) {
$properties = array();
} else {
if ('ArrayIterator' === $properties[2]) {
unset($properties[2]);
}
$properties = array($reflector->class => array("\0" => $properties));
}
} elseif ($value instanceof \SplObjectStorage) {
foreach (clone $value as $v) {
$properties[] = $v;
$properties[] = $value[$v];
}
$properties = array('SplObjectStorage' => array("\0" => $properties));
} elseif ($value instanceof \Serializable) {
++$objectsCount;
$objectsPool[$value] = array($id = \count($objectsPool), serialize($value), array(), 0);
$value = new Reference($id);
continue;
}
if (\method_exists($class, '__sleep')) {
if (!\is_array($sleep = $value->__sleep())) {
trigger_error('serialize(): __sleep should return an array only containing the names of instance-variables to serialize', E_USER_NOTICE);
$value = null;
continue;
}
$sleep = array_flip($sleep);
}
$proto = (array) $proto;
foreach ($arrayValue as $name => $v) {
$k = (string) $name;
if ('' === $k || "\0" !== $k[0]) {
$c = $class;
} elseif ('*' === $k[1]) {
$c = $class;
$k = substr($k, 3);
} else {
$i = strpos($k, "\0", 2);
$c = substr($k, 1, $i - 1);
$k = substr($k, 1 + $i);
}
if (null === $sleep) {
$properties[$c][$k] = $v;
} elseif (isset($sleep[$k]) && $c === $class) {
$properties[$c][$k] = $v;
unset($sleep[$k]);
}
if (\array_key_exists($name, $proto) && $proto[$name] === $v) {
unset($properties[$c][$k]);
}
}
if ($sleep) {
foreach ($sleep as $k => $v) {
trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $k), E_USER_NOTICE);
}
}
$objectsPool[$value] = array($id = \count($objectsPool));
$objectsCount += 1 + self::doMarshall($properties, $objectsPool);
$objectsPool[$value] = array($id, $class, $properties, \method_exists($class, '__wakeup') ? $objectsCount : 0);
$value = new Reference($id);
}
return $objectsCount;
}
}

View File

@ -0,0 +1,119 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Marshaller\PhpMarshaller;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class Configurator
{
public static $configurators = array();
public function __construct(Registry $registry, array $properties, $value, array $wakeups)
{
$this->{0} = $registry;
$this->{1} = $properties;
$this->{2} = $value;
$this->{3} = $wakeups;
}
public static function __set_state($state)
{
$objects = Registry::$objects;
Registry::$objects = \array_pop(Registry::$stack);
list(, $properties, $value, $wakeups) = $state;
foreach ($properties as $class => $vars) {
(self::$configurators[$class] ?? self::getConfigurator($class))($vars, $objects);
}
foreach ($wakeups as $i) {
$objects[$i]->__wakeup();
}
return $value;
}
public static function getConfigurator($class)
{
$classReflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class);
if (!$classReflector->isInternal()) {
return self::$configurators[$class] = \Closure::bind(function ($properties, $objects) {
foreach ($properties as $name => $values) {
foreach ($values as $i => $v) {
$objects[$i]->$name = $v;
}
}
}, null, $class);
}
switch ($class) {
case 'ArrayIterator':
case 'ArrayObject':
$constructor = $classReflector->getConstructor();
return self::$configurators[$class] = static function ($properties, $objects) use ($constructor) {
foreach ($properties as $name => $values) {
if ("\0" !== $name) {
foreach ($values as $i => $v) {
$objects[$i]->$name = $v;
}
}
}
foreach ($properties["\0"] as $i => $v) {
$constructor->invokeArgs($objects[$i], $v);
}
};
case 'SplObjectStorage':
return self::$configurators[$class] = static function ($properties, $objects) {
foreach ($properties as $name => $values) {
if ("\0" === $name) {
foreach ($values as $i => $v) {
for ($j = 0; $j < \count($v); ++$j) {
$objects[$i]->attach($v[$j], $v[++$j]);
}
}
continue;
}
foreach ($values as $i => $v) {
$objects[$i]->$name = $v;
}
}
};
}
$propertyReflectors = array();
foreach ($classReflector->getProperties(\ReflectionProperty::IS_PROTECTED | \ReflectionProperty::IS_PRIVATE) as $propertyReflector) {
if (!$propertyReflector->isStatic()) {
$propertyReflector->setAccessible(true);
$propertyReflectors[$propertyReflector->name] = $propertyReflector;
}
}
return self::$configurators[$class] = static function ($properties, $objects) use ($propertyReflectors) {
foreach ($properties as $name => $values) {
if (isset($propertyReflectors[$name])) {
foreach ($values as $i => $v) {
$propertyReflectors[$name]->setValue($objects[$i], $v);
}
} else {
foreach ($values as $i => $v) {
$objects[$i]->$name = $v;
}
}
}
};
}
}

View File

@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Marshaller\PhpMarshaller;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class Reference
{
public function __construct(int $id)
{
$this->{0} = $id;
}
public static function __set_state($state)
{
return Registry::$objects[$state[0]];
}
}

View File

@ -0,0 +1,58 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Marshaller\PhpMarshaller;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class Registry
{
public static $stack = array();
public static $objects = array();
public static $reflectors = array();
public static $prototypes = array();
public function __construct(array $classes)
{
foreach ($classes as $i => $class) {
$this->$i = $class;
}
}
public static function __set_state($classes)
{
self::$stack[] = self::$objects;
self::$objects = $classes;
foreach (self::$objects as &$class) {
if (isset(self::$prototypes[$class])) {
$class = clone self::$prototypes[$class];
} elseif (':' === ($class[1] ?? null)) {
$class = \unserialize($class);
} else {
$class = (self::$reflectors[$class] ?? self::getClassReflector($class))->newInstanceWithoutConstructor();
}
}
}
public static function getClassReflector($class)
{
$reflector = new \ReflectionClass($class);
if (!$reflector->hasMethod('__clone')) {
self::$prototypes[$class] = $reflector->newInstanceWithoutConstructor();
}
return self::$reflectors[$class] = $reflector;
}
}

View File

@ -66,15 +66,21 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
if (null === $this->values) {
$this->initialize();
}
if (!isset($this->values[$key])) {
if (!isset($this->keys[$key])) {
return $this->pool->get($key, $default);
}
$value = $this->values[$key];
$value = $this->values[$this->keys[$key]];
if ('N;' === $value) {
return null;
}
if ($value instanceof \Closure) {
try {
return $value();
} catch (\Throwable $e) {
return $default;
}
}
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
return unserialize($value);
@ -120,7 +126,7 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
$this->initialize();
}
return isset($this->values[$key]) || $this->pool->has($key);
return isset($this->keys[$key]) || $this->pool->has($key);
}
/**
@ -135,7 +141,7 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
$this->initialize();
}
return !isset($this->values[$key]) && $this->pool->delete($key);
return !isset($this->keys[$key]) && $this->pool->delete($key);
}
/**
@ -155,7 +161,7 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key)));
}
if (isset($this->values[$key])) {
if (isset($this->keys[$key])) {
$deleted = false;
} else {
$fallbackKeys[] = $key;
@ -184,7 +190,7 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
$this->initialize();
}
return !isset($this->values[$key]) && $this->pool->set($key, $value, $ttl);
return !isset($this->keys[$key]) && $this->pool->set($key, $value, $ttl);
}
/**
@ -204,7 +210,7 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', is_object($key) ? get_class($key) : gettype($key)));
}
if (isset($this->values[$key])) {
if (isset($this->keys[$key])) {
$saved = false;
} else {
$fallbackValues[$key] = $value;
@ -223,11 +229,17 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
$fallbackKeys = array();
foreach ($keys as $key) {
if (isset($this->values[$key])) {
$value = $this->values[$key];
if (isset($this->keys[$key])) {
$value = $this->values[$this->keys[$key]];
if ('N;' === $value) {
yield $key => null;
} elseif ($value instanceof \Closure) {
try {
yield $key => $value();
} catch (\Throwable $e) {
yield $key => $default;
}
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
yield $key => unserialize($value);

View File

@ -20,10 +20,14 @@ class PhpFilesCache extends AbstractCache implements PruneableInterface
use PhpFilesTrait;
/**
* @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire.
* Doing so is encouraged because it fits perfectly OPcache's memory model.
*
* @throws CacheException if OPcache is not enabled
*/
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null)
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false)
{
$this->appendOnly = $appendOnly;
self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);

View File

@ -107,16 +107,32 @@ class PhpArrayAdapterTest extends AdapterTestCase
public function testStoredFile()
{
$expected = array(
$data = array(
'integer' => 42,
'float' => 42.42,
'boolean' => true,
'array_simple' => array('foo', 'bar'),
'array_associative' => array('foo' => 'bar', 'foo2' => 'bar2'),
);
$expected = array(
array(
'integer' => 0,
'float' => 1,
'boolean' => 2,
'array_simple' => 3,
'array_associative' => 4,
),
array(
0 => 42,
1 => 42.42,
2 => true,
3 => array('foo', 'bar'),
4 => array('foo' => 'bar', 'foo2' => 'bar2'),
),
);
$adapter = $this->createCachePool();
$adapter->warmUp($expected);
$adapter->warmUp($data);
$values = eval(substr(file_get_contents(self::$file), 6));
@ -126,12 +142,16 @@ class PhpArrayAdapterTest extends AdapterTestCase
class PhpArrayAdapterWrapper extends PhpArrayAdapter
{
protected $data = array();
public function save(CacheItemInterface $item)
{
call_user_func(\Closure::bind(function () use ($item) {
$this->values[$item->getKey()] = $item->get();
$this->warmUp($this->values);
$this->values = eval(substr(file_get_contents($this->file), 6));
$key = $item->getKey();
$this->keys[$key] = $id = \count($this->values);
$this->data[$key] = $this->values[$id] = $item->get();
$this->warmUp($this->data);
list($this->keys, $this->values) = eval(substr(file_get_contents($this->file), 6));
}, $this, PhpArrayAdapter::class));
return true;

View File

@ -0,0 +1,28 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'ArrayIterator',
)),
'1' =>
array (
'ArrayIterator' =>
array (
'' . "\0" . '' =>
array (
0 =>
array (
0 =>
array (
0 => 123,
),
1 => 1,
),
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'3' =>
array (
),
));

View File

@ -0,0 +1,30 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'ArrayIterator',
)),
'1' =>
array (
'ArrayIterator' =>
array (
'' . "\0" . '' =>
array (
0 =>
array (
0 =>
array (
0 => 123,
),
1 => 1,
),
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' =>
array (
),
));

View File

@ -0,0 +1,28 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyArrayObject',
)),
'1' =>
array (
'ArrayObject' =>
array (
'' . "\0" . '' =>
array (
0 =>
array (
0 =>
array (
0 => 234,
),
1 => 1,
),
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'3' =>
array (
),
));

View File

@ -0,0 +1,30 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyArrayObject',
)),
'1' =>
array (
'ArrayObject' =>
array (
'' . "\0" . '' =>
array (
0 =>
array (
0 =>
array (
0 => 234,
),
1 => 1,
),
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' =>
array (
),
));

View File

@ -0,0 +1,36 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'ArrayObject',
'1' => 'ArrayObject',
)),
'1' =>
array (
'ArrayObject' =>
array (
'' . "\0" . '' =>
array (
0 =>
array (
0 =>
array (
0 => 1,
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
),
1 => 0,
),
),
'foo' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'3' =>
array (
),
));

View File

@ -0,0 +1,42 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'ArrayObject',
'1' => 'ArrayObject',
)),
'1' =>
array (
'ArrayObject' =>
array (
'' . "\0" . '' =>
array (
0 =>
array (
0 =>
array (
0 => 1,
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
),
1 => 0,
),
),
'foo' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 1,
)),
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' =>
array (
),
));

View File

@ -0,0 +1 @@
<?php return true;

View File

@ -0,0 +1,20 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyCloneable',
'1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyNotCloneable',
)),
'1' =>
array (
),
'2' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
),
'3' =>
array (
),
));

View File

@ -0,0 +1,24 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyCloneable',
'1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyNotCloneable',
)),
'1' =>
array (
),
'2' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 1,
)),
),
'3' =>
array (
),
));

View File

@ -0,0 +1,30 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'DateTime',
)),
'1' =>
array (
'DateTime' =>
array (
'date' =>
array (
0 => '1970-01-01 00:00:00.000000',
),
'timezone_type' =>
array (
0 => 1,
),
'timezone' =>
array (
0 => '+00:00',
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'3' =>
array (
1 => 0,
),
));

View File

@ -0,0 +1,32 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'DateTime',
)),
'1' =>
array (
'DateTime' =>
array (
'date' =>
array (
0 => '1970-01-01 00:00:00.000000',
),
'timezone_type' =>
array (
0 => 1,
),
'timezone' =>
array (
0 => '+00:00',
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' =>
array (
1 => 0,
),
));

View File

@ -0,0 +1,39 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateValue',
'1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateChildValue',
)),
'1' =>
array (
'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateValue' =>
array (
'prot' =>
array (
0 => 123,
),
'priv' =>
array (
0 => 234,
1 => 234,
),
),
'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateChildValue' =>
array (
'prot' =>
array (
1 => 123,
),
),
),
'2' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
),
'3' =>
array (
),
));

View File

@ -0,0 +1,43 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateValue',
'1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateChildValue',
)),
'1' =>
array (
'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateValue' =>
array (
'prot' =>
array (
0 => 123,
),
'priv' =>
array (
0 => 234,
1 => 234,
),
),
'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateChildValue' =>
array (
'prot' =>
array (
1 => 123,
),
),
),
'2' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 1,
)),
),
'3' =>
array (
),
));

View File

@ -0,0 +1,19 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'C:55:"Symfony\\Component\\Cache\\Tests\\Marshaller\\MySerializable":3:{123}',
)),
'1' =>
array (
),
'2' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
),
'3' =>
array (
),
));

View File

@ -0,0 +1,23 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'C:55:"Symfony\\Component\\Cache\\Tests\\Marshaller\\MySerializable":3:{123}',
)),
'1' =>
array (
),
'2' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
),
'3' =>
array (
),
));

View File

@ -0,0 +1,7 @@
<?php return array (
0 => 123,
1 =>
array (
0 => 'abc',
),
);

View File

@ -0,0 +1,27 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'SplObjectStorage',
'1' => 'stdClass',
)),
'1' =>
array (
'SplObjectStorage' =>
array (
'' . "\0" . '' =>
array (
0 =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
1 => 345,
),
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'3' =>
array (
),
));

View File

@ -0,0 +1,31 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'SplObjectStorage',
'1' => 'stdClass',
)),
'1' =>
array (
'SplObjectStorage' =>
array (
'' . "\0" . '' =>
array (
0 =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 1,
)),
1 => 345,
),
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' =>
array (
),
));

View File

@ -0,0 +1,30 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup',
'1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup',
)),
'1' =>
array (
'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup' =>
array (
'sub' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
1 => 123,
),
'baz' =>
array (
1 => 123,
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'3' =>
array (
1 => 1,
2 => 0,
),
));

View File

@ -0,0 +1,34 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup',
'1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup',
)),
'1' =>
array (
'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup' =>
array (
'sub' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 1,
)),
1 => 123,
),
'baz' =>
array (
1 => 123,
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' =>
array (
1 => 1,
2 => 0,
),
));

View File

@ -0,0 +1,169 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Tests\Marshaller;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Marshaller\PhpMarshaller;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
class DoctrineProviderTest extends TestCase
{
use VarDumperTestTrait;
/**
* @dataProvider provideMarshall
*/
public function testMarshall(string $testName, $value, int $expectedObjectsCount)
{
$objectsCount = 0;
$marshalledValue = PhpMarshaller::marshall($value, $objectsCount);
$this->assertSame($expectedObjectsCount, $objectsCount);
$dump = '<?php return '.var_export($marshalledValue, true).";\n";
$fixtureFile = __DIR__.'/Fixtures/'.$testName.'.php';
$this->assertStringEqualsFile($fixtureFile, $dump);
if ($objectsCount) {
$marshalledValue = include $fixtureFile;
$this->assertDumpEquals($value, $marshalledValue);
$dump = PhpMarshaller::optimize($dump);
$fixtureFile = __DIR__.'/Fixtures/'.$testName.'.optimized.php';
$this->assertStringEqualsFile($fixtureFile, $dump);
$marshalledValue = include $fixtureFile;
$this->assertDumpEquals($value, $marshalledValue);
} else {
$this->assertSame($value, $marshalledValue);
}
}
public function provideMarshall()
{
yield ['bool', true, 0];
yield ['simple-array', [123, ['abc']], 0];
yield ['datetime', \DateTime::createFromFormat('U', 0), 1];
$value = new \ArrayObject();
$value[0] = 1;
$value->foo = new \ArrayObject();
$value[1] = $value;
yield ['array-object', $value, 3];
yield array('array-iterator', new \ArrayIterator(array(123), 1), 1);
yield array('array-object-custom', new MyArrayObject(array(234)), 1);
$value = new MySerializable();
yield ['serializable', array($value, $value), 2];
$value = new MyWakeup();
$value->sub = new MyWakeup();
$value->sub->sub = 123;
$value->sub->bis = 123;
$value->sub->baz = 123;
yield ['wakeup', $value, 2];
yield ['clone', array(new MyCloneable(), new MyNotCloneable()), 2];
yield ['private', array(new MyPrivateValue(123, 234), new MyPrivateChildValue(123, 234)), 2];
$value = new \SplObjectStorage();
$value[new \stdClass()] = 345;
yield ['spl-object-storage', $value, 2];
}
}
class MySerializable implements \Serializable
{
public function serialize()
{
return '123';
}
public function unserialize($data)
{
// no-op
}
}
class MyWakeup
{
public $sub;
public $bis;
public $baz;
public $def = 234;
public function __sleep()
{
return array('sub', 'baz');
}
public function __wakeup()
{
if (123 === $this->sub) {
$this->bis = 123;
$this->baz = 123;
}
}
}
class MyCloneable
{
public function __clone()
{
throw new \Exception('__clone should never be called');
}
}
class MyNotCloneable
{
private function __clone()
{
throw new \Exception('__clone should never be called');
}
}
class MyPrivateValue
{
protected $prot;
private $priv;
public function __construct($prot, $priv)
{
$this->prot = $prot;
$this->priv = $priv;
}
}
class MyPrivateChildValue extends MyPrivateValue
{
}
class MyArrayObject extends \ArrayObject
{
private $unused = 123;
public function __construct(array $array)
{
parent::__construct($array, 1);
}
public function setFlags($flags)
{
throw new \BadMethodCallException('Calling MyArrayObject::setFlags() is forbidden');
}
}

View File

@ -95,16 +95,32 @@ class PhpArrayCacheTest extends CacheTestCase
public function testStoredFile()
{
$expected = array(
$data = array(
'integer' => 42,
'float' => 42.42,
'boolean' => true,
'array_simple' => array('foo', 'bar'),
'array_associative' => array('foo' => 'bar', 'foo2' => 'bar2'),
);
$expected = array(
array(
'integer' => 0,
'float' => 1,
'boolean' => 2,
'array_simple' => 3,
'array_associative' => 4,
),
array(
0 => 42,
1 => 42.42,
2 => true,
3 => array('foo', 'bar'),
4 => array('foo' => 'bar', 'foo2' => 'bar2'),
),
);
$cache = new PhpArrayCache(self::$file, new NullCache());
$cache->warmUp($expected);
$cache->warmUp($data);
$values = eval(substr(file_get_contents(self::$file), 6));
@ -114,12 +130,14 @@ class PhpArrayCacheTest extends CacheTestCase
class PhpArrayCacheWrapper extends PhpArrayCache
{
protected $data = array();
public function set($key, $value, $ttl = null)
{
call_user_func(\Closure::bind(function () use ($key, $value) {
$this->values[$key] = $value;
$this->warmUp($this->values);
$this->values = eval(substr(file_get_contents($this->file), 6));
$this->data[$key] = $value;
$this->warmUp($this->data);
list($this->keys, $this->values) = eval(substr(file_get_contents($this->file), 6));
}, $this, PhpArrayCache::class));
return true;
@ -132,10 +150,10 @@ class PhpArrayCacheWrapper extends PhpArrayCache
}
call_user_func(\Closure::bind(function () use ($values) {
foreach ($values as $key => $value) {
$this->values[$key] = $value;
$this->data[$key] = $value;
}
$this->warmUp($this->values);
$this->values = eval(substr(file_get_contents($this->file), 6));
$this->warmUp($this->data);
list($this->keys, $this->values) = eval(substr(file_get_contents($this->file), 6));
}, $this, PhpArrayCache::class));
return true;

View File

@ -52,11 +52,14 @@ trait ApcuTrait
protected function doFetch(array $ids)
{
try {
$values = array();
foreach (apcu_fetch($ids, $ok) ?: array() as $k => $v) {
if (null !== $v || $ok) {
yield $k => $v;
$values[$k] = $v;
}
}
return $values;
} catch (\Error $e) {
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
}

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Cache\Traits;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Marshaller\PhpMarshaller;
/**
* @author Titouan Galopin <galopintitouan@gmail.com>
@ -25,6 +26,7 @@ trait PhpArrayTrait
use ProxyTrait;
private $file;
private $keys;
private $values;
/**
@ -54,55 +56,66 @@ trait PhpArrayTrait
}
}
$dumpedValues = '';
$dumpedMap = array();
$dump = <<<'EOF'
<?php
// This file has been auto-generated by the Symfony Cache Component.
return array(
return array(array(
EOF;
foreach ($values as $key => $value) {
CacheItem::validateKey(\is_int($key) ? (string) $key : $key);
$objectsCount = 0;
if (null === $value || \is_object($value)) {
try {
$value = serialize($value);
} catch (\Exception $e) {
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, get_class($value)), 0, $e);
}
} elseif (\is_array($value)) {
if (null === $value) {
$value = 'N;';
} elseif (\is_object($value) || \is_array($value)) {
try {
$e = null;
$serialized = serialize($value);
$unserialized = unserialize($serialized);
} catch (\Exception $e) {
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable array value.', $key), 0, $e);
}
// Store arrays serialized if they contain any objects or references
if ($unserialized !== $value || (false !== strpos($serialized, ';R:') && preg_match('/;R:[1-9]/', $serialized))) {
$value = $serialized;
if (null !== $e || false === $serialized) {
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? get_class($value) : 'array'), 0, $e);
}
// Keep value serialized if it contains any internal references
$value = false !== strpos($serialized, ';R:') ? $serialized : PhpMarshaller::marshall($value, $objectsCount);
} elseif (\is_string($value)) {
// Serialize strings if they could be confused with serialized objects or arrays
// Wrap strings if they could be confused with serialized objects or arrays
if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
$value = serialize($value);
++$objectsCount;
}
} elseif (!\is_scalar($value)) {
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, gettype($value)));
}
$dump .= var_export($key, true).' => '.var_export($value, true).",\n";
$value = var_export($value, true);
if ($objectsCount) {
$value = PhpMarshaller::optimize($value);
$value = "static function () {\nreturn {$value};\n}";
}
$hash = hash('md5', $value);
if (null === $id = $dumpedMap[$hash] ?? null) {
$id = $dumpedMap[$hash] = \count($dumpedMap);
$dumpedValues .= "{$id} => {$value},\n";
}
$dump .= var_export($key, true)." => {$id},\n";
}
$dump .= "\n);\n";
$dump .= "\n), array(\n\n{$dumpedValues}\n));\n";
$tmpFile = uniqid($this->file, true);
file_put_contents($tmpFile, $dump);
@chmod($tmpFile, 0666 & ~umask());
unset($serialized, $unserialized, $value, $dump);
unset($serialized, $value, $dump);
@rename($tmpFile, $this->file);
@ -114,7 +127,7 @@ EOF;
*/
public function clear()
{
$this->values = array();
$this->keys = $this->values = array();
$cleared = @unlink($this->file) || !file_exists($this->file);
@ -126,6 +139,17 @@ EOF;
*/
private function initialize()
{
$this->values = file_exists($this->file) ? (include $this->file ?: array()) : array();
if (!file_exists($this->file)) {
$this->keys = $this->values = array();
return;
}
$values = (include $this->file) ?: array(array(), array());
if (2 !== \count($values) || !isset($values[0], $values[1])) {
$this->keys = $this->values = array();
} else {
list($this->keys, $this->values) = $values;
}
}
}

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Cache\Traits;
use Symfony\Component\Cache\Exception\CacheException;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Marshaller\PhpMarshaller;
/**
* @author Piotr Stankowski <git@trakos.pl>
@ -23,9 +24,15 @@ use Symfony\Component\Cache\Exception\InvalidArgumentException;
*/
trait PhpFilesTrait
{
use FilesystemCommonTrait;
use FilesystemCommonTrait {
doClear as private doCommonClear;
doDelete as private doCommonDelete;
}
private $includeHandler;
private $appendOnly;
private $values = array();
private $files = array();
private static $startTime;
@ -65,35 +72,58 @@ trait PhpFilesTrait
*/
protected function doFetch(array $ids)
{
if ($this->appendOnly) {
$now = 0;
$missingIds = array();
} else {
$now = time();
$missingIds = $ids;
$ids = array();
}
$values = array();
$now = time();
begin:
foreach ($ids as $id) {
if (null === $value = $this->values[$id] ?? null) {
$missingIds[] = $id;
} elseif ('N;' === $value) {
$values[$id] = null;
} elseif ($value instanceof \Closure) {
$values[$id] = $value();
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
$values[$id] = parent::unserialize($value);
} else {
$values[$id] = $value;
}
if (!$this->appendOnly) {
unset($this->values[$id]);
}
}
if (!$missingIds) {
return $values;
}
set_error_handler($this->includeHandler);
try {
foreach ($ids as $id) {
foreach ($missingIds as $k => $id) {
try {
$file = $this->getFile($id);
list($expiresAt, $values[$id]) = include $file;
$file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
list($expiresAt, $this->values[$id]) = include $file;
if ($now >= $expiresAt) {
unset($values[$id]);
unset($this->values[$id], $missingIds[$k]);
}
} catch (\Exception $e) {
continue;
unset($missingIds[$k]);
}
}
} finally {
restore_error_handler();
}
foreach ($values as $id => $value) {
if ('N;' === $value) {
$values[$id] = null;
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
$values[$id] = parent::unserialize($value);
}
}
return $values;
$ids = $missingIds;
$missingIds = array();
goto begin;
}
/**
@ -101,7 +131,25 @@ trait PhpFilesTrait
*/
protected function doHave($id)
{
return (bool) $this->doFetch(array($id));
if ($this->appendOnly && $this->values[$id]) {
return true;
}
set_error_handler($this->includeHandler);
try {
$file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
list($expiresAt, $value) = include $file;
} finally {
restore_error_handler();
}
if ($this->appendOnly) {
$now = 0;
$this->values[$id] = $value;
} else {
$now = time();
}
return $now < $expiresAt;
}
/**
@ -110,35 +158,47 @@ trait PhpFilesTrait
protected function doSave(array $values, $lifetime)
{
$ok = true;
$data = array($lifetime ? time() + $lifetime : PHP_INT_MAX, '');
$expiry = $lifetime ? time() + $lifetime : 'PHP_INT_MAX';
$allowCompile = self::isSupported();
foreach ($values as $key => $value) {
if (null === $value || \is_object($value)) {
$value = serialize($value);
} elseif (\is_array($value)) {
$serialized = serialize($value);
$unserialized = parent::unserialize($serialized);
// Store arrays serialized if they contain any objects or references
if ($unserialized !== $value || (false !== strpos($serialized, ';R:') && preg_match('/;R:[1-9]/', $serialized))) {
$value = $serialized;
unset($this->values[$key]);
$objectsCount = 0;
if (null === $value) {
$value = 'N;';
} elseif (\is_object($value) || \is_array($value)) {
try {
$e = null;
$serialized = serialize($value);
} catch (\Exception $e) {
}
if (null !== $e || false === $serialized) {
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? get_class($value) : 'array'), 0, $e);
}
// Keep value serialized if it contains any internal references
$value = false !== strpos($serialized, ';R:') ? $serialized : PhpMarshaller::marshall($value, $objectsCount);
} elseif (\is_string($value)) {
// Serialize strings if they could be confused with serialized objects or arrays
// Wrap strings if they could be confused with serialized objects or arrays
if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
$value = serialize($value);
++$objectsCount;
}
} elseif (!\is_scalar($value)) {
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, gettype($value)));
}
$data[1] = $value;
$file = $this->getFile($key, true);
$value = var_export($value, true);
if ($objectsCount) {
$value = PhpMarshaller::optimize($value);
$value = "static function () {\n\nreturn {$value};\n\n}";
}
$file = $this->files[$key] = $this->getFile($key, true);
// Since OPcache only compiles files older than the script execution start, set the file's mtime in the past
$ok = $this->write($file, '<?php return '.var_export($data, true).';', self::$startTime - 10) && $ok;
$ok = $this->write($file, "<?php return array({$expiry}, {$value});\n", self::$startTime - 10) && $ok;
if ($allowCompile) {
@opcache_invalidate($file, true);
@opcache_compile_file($file);
}
}
@ -149,6 +209,28 @@ trait PhpFilesTrait
return $ok;
}
/**
* {@inheritdoc}
*/
protected function doClear($namespace)
{
$this->values = array();
return $this->doCommonClear($namespace);
}
/**
* {@inheritdoc}
*/
protected function doDelete(array $ids)
{
foreach ($ids as $id) {
unset($this->values[$id]);
}
return $this->doCommonDelete($ids);
}
protected function doUnlink($file)
{
if (self::isSupported()) {

View File

@ -29,7 +29,8 @@
"cache/integration-tests": "dev-master",
"doctrine/cache": "~1.6",
"doctrine/dbal": "~2.4",
"predis/predis": "~1.0"
"predis/predis": "~1.0",
"symfony/var-dumper": "^4.1.1"
},
"conflict": {
"symfony/var-dumper": "<3.4"