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

Skip to content

Commit 9a5c2ff

Browse files
[Cache] automatically use igbinary when available, with graceful error handling
1 parent 1abfb2c commit 9a5c2ff

14 files changed

+255
-52
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/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/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ 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
* throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool
1011
* deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead
1112
* deprecated the `AbstractAdapter::createSystemCache()` method
13+
* deprecated the `AbstractAdapter::unserialize()` and `AbstractCache::unserialize()` methods
1214

1315
3.4.0
1416
-----

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
}
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' => \function_exists('igbinary_serialize') ? 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
@@ -110,13 +110,13 @@ public function clear()
110110
if ($cleared = $this->versioningIsEnabled) {
111111
$namespaceVersion = 2;
112112
try {
113-
foreach ($this->doFetch(array('@'.$this->namespace)) as $v) {
113+
foreach ($this->doFetch(array('/'.$this->namespace)) as $v) {
114114
$namespaceVersion = 1 + (int) $v;
115115
}
116116
} catch (\Exception $e) {
117117
}
118-
$namespaceVersion .= ':';
119-
$cleared = $this->doSave(array('@'.$this->namespace => $namespaceVersion), 0);
118+
$namespaceVersion .= '/';
119+
$cleared = $this->doSave(array('/'.$this->namespace => $namespaceVersion), 0);
120120
if ($cleared = true === $cleared || array() === $cleared) {
121121
$this->namespaceVersion = $namespaceVersion;
122122
}
@@ -216,9 +216,13 @@ public function reset()
216216
* @return mixed
217217
*
218218
* @throws \Exception
219+
*
220+
* @deprecated since Symfony 4.2, use SerializingTrait instead.
219221
*/
220222
protected static function unserialize($value)
221223
{
224+
@trigger_error(sprintf('The "%s::unserialize()" method is deprecated since Symfony 4.2, use SerializingTrait instead.', __CLASS__), E_USER_DEPRECATED);
225+
222226
if ('b:0;' === $value) {
223227
return false;
224228
}
@@ -238,9 +242,9 @@ protected static function unserialize($value)
238242
private function getId($key)
239243
{
240244
if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
241-
$this->namespaceVersion = '1:';
245+
$this->namespaceVersion = '1/';
242246
try {
243-
foreach ($this->doFetch(array('@'.$this->namespace)) as $v) {
247+
foreach ($this->doFetch(array('/'.$this->namespace)) as $v) {
244248
$this->namespaceVersion = $v;
245249
}
246250
} 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
}

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
*/
2323
trait MemcachedTrait
2424
{
25+
use SerializingTrait;
26+
2527
private static $defaultClientOptions = array(
2628
'persistent_id' => null,
2729
'username' => null,
@@ -194,6 +196,10 @@ public static function createConnection($servers, array $options = array())
194196
*/
195197
protected function doSave(array $values, $lifetime)
196198
{
199+
if (!$values = $this->serializeValues($values, $failed)) {
200+
return $failed;
201+
}
202+
197203
if ($lifetime && $lifetime > 30 * 86400) {
198204
$lifetime += time();
199205
}
@@ -203,30 +209,27 @@ protected function doSave(array $values, $lifetime)
203209
$encodedValues[rawurlencode($key)] = $value;
204210
}
205211

206-
return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime));
212+
return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime)) ? $failed : false;
207213
}
208214

209215
/**
210216
* {@inheritdoc}
211217
*/
212218
protected function doFetch(array $ids)
213219
{
214-
$unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
215220
try {
216221
$encodedIds = array_map('rawurlencode', $ids);
217222

218223
$encodedResult = $this->checkResultCode($this->getClient()->getMulti($encodedIds));
219224

220225
$result = array();
221226
foreach ($encodedResult as $key => $value) {
222-
$result[rawurldecode($key)] = $value;
227+
$result[rawurldecode($key)] = $this->unserializeValue($value);
223228
}
224229

225230
return $result;
226231
} catch (\Error $e) {
227232
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
228-
} finally {
229-
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
230233
}
231234
}
232235

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

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
*/
2323
trait PdoTrait
2424
{
25+
use SerializingTrait;
26+
2527
private $conn;
2628
private $dsn;
2729
private $driver;
@@ -181,7 +183,7 @@ protected function doFetch(array $ids)
181183
if (null === $row[1]) {
182184
$expired[] = $row[0];
183185
} else {
184-
yield $row[0] => parent::unserialize(is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]);
186+
yield $row[0] => $this->unserializeValue(is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]);
185187
}
186188
}
187189

@@ -252,18 +254,7 @@ protected function doDelete(array $ids)
252254
*/
253255
protected function doSave(array $values, $lifetime)
254256
{
255-
$serialized = array();
256-
$failed = array();
257-
258-
foreach ($values as $id => $value) {
259-
try {
260-
$serialized[$id] = serialize($value);
261-
} catch (\Exception $e) {
262-
$failed[] = $id;
263-
}
264-
}
265-
266-
if (!$serialized) {
257+
if (!$values = $this->serializeValues($values, $failed)) {
267258
return $failed;
268259
}
269260

@@ -328,7 +319,7 @@ protected function doSave(array $values, $lifetime)
328319
$insertStmt->bindValue(':time', $now, \PDO::PARAM_INT);
329320
}
330321

331-
foreach ($serialized as $id => $data) {
322+
foreach ($values as $id => $data) {
332323
$stmt->execute();
333324

334325
if (null === $driver && !$stmt->rowCount()) {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
trait PhpArrayTrait
2525
{
2626
use ProxyTrait;
27+
use SerializingTrait {
28+
serializeValues as private;
29+
unserializeValue as private;
30+
}
2731

2832
private $file;
2933
private $keys;

0 commit comments

Comments
 (0)
0