8000 [Cache] automatically use igbinary when available, with graceful erro… · symfony/symfony@c62ca03 · GitHub
[go: up one dir, main page]

Skip to content

Commit c62ca03

Browse files
[Cache] automatically use igbinary when available, with graceful error handling
1 parent 6064cfe commit c62ca03

22 files changed

+271
-60
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ before_install:
151151
tfold ext.libsodium tpecl libsodium sodium.so $INI
152152
tfold ext.mongodb tpecl mongodb-1.4.0RC1 mongodb.so $INI
153153
tfold ext.amqp tpecl amqp-1.9.3 amqp.so $INI
154+
tfold ext.igbinary tpecl igbinary-2.0.6 igbinary.so $INI
154155
fi
155156
156157
- |

src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ class FilesystemAdapter extends AbstractAdapter implements PruneableInterface
1818
{
1919
use FilesystemTrait;
2020

21-
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null)
21+
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $allowIgbinary = true)
2222
{
23+
$this->allowIgbinary = $allowIgbinary;
2324
parent::__construct('', $defaultLifetime);
2425
$this->init($namespace, $directory);
2526
}

src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ class MemcachedAdapter extends AbstractAdapter
2929
*
3030
* Using a MemcachedAdapter as a pure items store is fine.
3131
*/
32-
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0)
32+
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, bool $allowIgbinary = true)
3333
{
34+
$this->allowIgbinary = $allowIgbinary;
3435
$this->init($client, $namespace, $defaultLifetime);
3536
}
3637
}

src/Symfony/Component/Cache/Adapter/PdoAdapter.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface
4343
* @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
4444
* @throws InvalidArgumentException When namespace contains invalid characters
4545
*/
46-
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = array())
46+
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = array(), bool $allowIgbinary = true)
4747
{
48+
$this->allowIgbinary = $allowIgbinary;
4849
$this->init($connOrDsn, $namespace, $defaultLifetime, $options);
4950
}
5051
}

src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public function get(string $key, callable $callback, float $beta = null)
8888
$this->initialize();
8989
}
9090
if (!isset($this->keys[$key])) {
91+
get_from_pool:
9192
if ($this->pool instanceof CacheInterface) {
9293
return $this->pool->get($key, $callback, $beta);
9394
}
@@ -99,11 +100,16 @@ public function get(string $key, callable $callback, float $beta = null)
99100
if ('N;' === $value) {
100101
return null;
101102
}
102-
if ($value instanceof \Closure) {
103-
return $value();
104-
}
105-
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
106-
return unserialize($value);
103+
try {
104+
if ($value instanceof \Closure) {
105+
return $value();
106+
}
107+
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
108+
return $this->unserializeValue($value);
109+
}
110+
} catch (\Throwable $e) {
111+
unset($this->keys[$key]);
112+
goto get_from_pool;
107113
}
108114

109115
return $value;
@@ -278,7 +284,7 @@ private function generateItems(array $keys): \Generator
278284
}
279285
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
280286
try {
281-
yield $key => $f($key, unserialize($value), true);
287+
yield $key => $f($key, $this->unserializeValue($value), true);
282288
} catch (\Throwable $e) {
283289
yield $key => $f($key, null, false);
284290
}

src/Symfony/Component/Cache/Adapter/RedisAdapter.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ class RedisAdapter extends AbstractAdapter
2222
* @param string $namespace The default namespace
2323
* @param int $defaultLifetime The default lifetime
2424
*/
25-
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0)
25+
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, bool $allowIgbinary = true)
2626
{
27+
$this->allowIgbinary = $allowIgbinary;
2728
$this->init($redisClient, $namespace, $defaultLifetime);
2829
}
2930
}

src/Symfony/Component/Cache/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ CHANGELOG
44
4.2.0
55
-----
66

7+
* automatically use igbinary when available, with graceful error handling
78
* added `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache
89
* added sub-second expiry accuracy for backends that support it
910
* added support for phpredis 4 `compression` and `tcp_keepalive` options
1011
* throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool
1112
* deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead
1213
* deprecated the `AbstractAdapter::createSystemCache()` method
14+
* deprecated the `AbstractAdapter::unserialize()` and `AbstractCache::unserialize()` methods
1315

1416
3.4.0
1517
-----

src/Symfony/Component/Cache/Simple/FilesystemCache.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ class FilesystemCache extends AbstractCache implements PruneableInterface
1818
{
1919
use FilesystemTrait;
2020

21-
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null)
21+
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $allowIgbinary = true)
2222
{
23+
$this->allowIgbinary = $allowIgbinary;
2324
parent::__construct('', $defaultLifetime);
2425
$this->init($namespace, $directory);
2526
}

src/Symfony/Component/Cache/Simple/MemcachedCache.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ class MemcachedCache extends AbstractCache
1919

2020
protected $maxIdLength = 250;
2121

22-
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0)
22+
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, bool $allowIgbinary = true)
2323
{
24+
$this->allowIgbinary = $allowIgbinary;
2425
$this->init($client, $namespace, $defaultLifetime);
2526
}
2627
}

src/Symfony/Component/Cache/Simple/PdoCache.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ class PdoCache extends AbstractCache implements PruneableInterface
4141
* @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
4242
* @throws InvalidArgumentException When namespace contains invalid characters
4343
*/
44-
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = array())
44+
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = array(), bool $allowIgbinary = true)
4545
{
46+
$this->allowIgbinary = $allowIgbinary;
4647
$this->init($connOrDsn, $namespace, $defaultLifetime, $options);
4748
}
4849
}

src/Symfony/Component/Cache/Simple/PhpArrayCache.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public function get($key, $default = null)
8383
}
8484
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
8585
try {
86-
return unserialize($value);
86+
return $this->unserializeValue($value);
8787
} catch (\Throwable $e) {
8888
return $default;
8989
}

src/Symfony/Component/Cache/Simple/RedisCache.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ class RedisCache extends AbstractCache
2222
* @param string $namespace
2323
* @param int $defaultLifetime
2424
*/
25-
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0)
25+
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, bool $allowIgbinary = true)
2626
{
27+
$this->allowIgbinary = $allowIgbinary;
2728
$this->init($redisClient, $namespace, $defaultLifetime);
2829
}
2930
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\Tests\Traits;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Cache\Traits\SerializingTrait;
16+
17+
class SerializingTraitTest extends TestCase
18+
{
19+
use SerializingTrait;
20+
21+
public function testSerialize()
22+
{
23+
$values = array(
24+
'a' => 123,
25+
'b' => function () {},
26+
);
27+
28+
$expected = array('a' => \extension_loaded('igbinary') ? igbinary_serialize(123) : serialize(123));
29+
$this->assertSame($expected, $this->serializeValues($values, $failed));
30+
$this->assertSame(array('b'), $failed);
31+
}
32+
33+
public function testNativeUnserialize()
34+
{
35+
$this->assertNull($this->unserializeValue(serialize(null)));
36+
$this->assertFalse($this->unserializeValue(serialize(false)));
37+
$this->assertSame('', $this->unserializeValue(serialize('')));
38+
$this->assertSame(0, $this->unserializeValue(serialize(0)));
39+
}
40+
41+
/**
42+
* @requires extension igbinary
43+
*/
44+
public function testIgbinaryUnserialize()
45+
{
46+
$this->assertNull($this->unserializeValue(igbinary_serialize(null)));
47+
$this->assertFalse($this->unserializeValue(igbinary_serialize(false)));
48+
$this->assertSame('', $this->unserializeValue(igbinary_serialize('')));
49+
$this->assertSame(0, $this->unserializeValue(igbinary_serialize(0)));
50+
}
51+
52+
/**
53+
* @expectedException \DomainException
54+
* @expectedExceptionMessage Class not found: NotExistingClass
55+
*/
56+
public function testNativeUnserializeNotFoundClass()
57+
{
58+
$this->unserializeValue('O:16:"NotExistingClass":0:{}');
59+
}
60+
61+
/**
62+
* @requires extension igbinary
63+
* @expectedException \DomainException
64+
* @expectedExceptionMessage Class not found: NotExistingClass
65+
*/
66+
public function testIgbinaryUnserializeNotFoundClass()
67+
{
68+
$this->unserializeValue(rawurldecode('%00%00%00%02%17%10NotExistingClass%14%00'));
69+
}
70+
71+
/**
72+
* @expectedException \DomainException
73+
* @expectedExceptionMessage unserialize(): Error at offset 0 of 3 bytes
74+
*/
75+
public function testNativeUnserializeInvalid()
76+
{
77+
set_error_handler(function () { return false; });
78+
try {
79+
@$this->unserializeValue(':::');
80+
} finally {
81+
restore_error_handler();
82+
}
83+
}
84+
85+
/**
86+
* @requires extension igbinary
87+
* @expectedException \DomainException
88+
* @expectedExceptionMessage igbinary_unserialize_zval: unknown type '61', position 5
89+
*/
90+
public function testIgbinaryUnserializeInvalid()
91+
{
92+
set_error_handler(function () { return false; });
93+
try {
94+
@$this->unserializeValue(rawurldecode('%00%00%00%02abc'));
95+
} finally {
96+
restore_error_handler();
97+
}
98+
}
99+
}

src/Symfony/Component/Cache/Traits/AbstractTrait.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,14 @@ public function clear()
109109
if ($cleared = $this->versioningIsEnabled) {
110110
$namespaceVersion = 2;
111111
try {
112-
foreach ($this->doFetch(array('@'.$this->namespace)) as $v) {
112+
foreach ($this->doFetch(array('/'.$this->namespace)) as $v) {
113113
$namespaceVersion = 1 + (int) $v;
114114
}
115115
} catch (\Exception $e) {
116116
}
117-
$namespaceVersion .= ':';
117+
$namespaceVersion .= '/';
118118
try {
119-
$cleared = $this->doSave(array('@'.$this->namespace => $namespaceVersion), 0);
119+
$cleared = $this->doSave(array('/'.$this->namespace => $namespaceVersion), 0);
120120
} catch (\Exception $e) {
121121
$cleared = false;
122122
}
@@ -220,9 +220,13 @@ public function reset()
220220
* @return mixed
221221
*
222222
* @throws \Exception
223+
*
224+
* @deprecated since Symfony 4.2, use SerializingTrait instead.
223225
*/
224226
protected static function unserialize($value)
225227
{
228+
@trigger_error(sprintf('The "%s::unserialize()" method is deprecated since Symfony 4.2, use SerializingTrait instead.', __CLASS__), E_USER_DEPRECATED);
229+
226230
if ('b:0;' === $value) {
227231
return false;
228232
}
@@ -242,9 +246,9 @@ protected static function unserialize($value)
242246
private function getId($key)
243247
{
244248
if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
245-
$this->namespaceVersion = '1:';
249+
$this->namespaceVersion = '1/';
246250
try {
247-
foreach ($this->doFetch(array('@'.$this->namespace)) as $v) {
251+
foreach ($this->doFetch(array('/'.$this->namespace)) as $v) {
248252
$this->namespaceVersion = $v;
249253
}
250254
} catch (\Exception $e) {

src/Symfony/Component/Cache/Traits/ApcuTrait.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ private function init($namespace, $defaultLifetime, $version)
5151
*/
5252
protected function doFetch(array $ids)
5353
{
54+
$unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
5455
try {
5556
$values = array();
5657
foreach (apcu_fetch($ids, $ok) ?: array() as $k => $v) {
@@ -62,6 +63,8 @@ protected function doFetch(array $ids)
6263
return $values;
6364
} catch (\Error $e) {
6465
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
66+
} finally {
67+
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
6568
}
6669
}
6770

src/Symfony/Component/Cache/Traits/FilesystemTrait.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
trait FilesystemTrait
2323
{
2424
use FilesystemCommonTrait;
25+
use SerializingTrait;
2526

2627
/**
2728
* @return bool
@@ -68,7 +69,7 @@ protected function doFetch(array $ids)
6869
$value = stream_get_contents($h);
6970
fclose($h);
7071
if ($i === $id) {
71-
$values[$id] = parent::unserialize($value);
72+
$values[$id] = $this->unserializeValue($value);
7273
}
7374
}
7475
}
@@ -91,17 +92,19 @@ protected function doHave($id)
9192
*/
9293
protected function doSave(array $values, $lifetime)
9394
{
94-
$ok = true;
9595
$expiresAt = $lifetime ? (time() + $lifetime) : 0;
96+
$values = $this->serializeValues($values, $failed);
9697

9798
foreach ($values as $id => $value) {
98-
$ok = $this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".serialize($value), $expiresAt) && $ok;
99+
if (!$this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".$value, $expiresAt)) {
100+
$failed[] = $id;
101+
}
99102
}
100103

101-
if (!$ok && !is_writable($this->directory)) {
104+
if ($failed && !is_writable($this->directory)) {
102105
throw new CacheException(sprintf('Cache directory is not writable (%s)', $this->directory));
103106
}
104107

105-
return $ok;
108+
return $failed;
106109
}
107110
}

0 commit comments

Comments
 (0)
0