Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f286074

Browse files
[Cache] serialize objects using native arrays when possible
1 parent 50c4384 commit f286074

File tree

5 files changed

+206
-36
lines changed

5 files changed

+206
-36
lines changed

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symfony\Component\Cache\ResettableInterface;
2121
use Symfony\Component\Cache\Traits\GetTrait;
2222
use Symfony\Component\Cache\Traits\PhpArrayTrait;
23+
use Symfony\Component\Cache\Traits\PhpMarshaller;
2324

2425
/**
2526
* Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0.
@@ -101,6 +102,9 @@ public function get(string $key, callable $callback)
101102
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
102103
return unserialize($value);
103104
}
105+
if (\is_array($value) && $value) {
106+
return PhpMarshaller::unmarshall($value);
107+
}
104108

105109
return $value;
106110
}
@@ -127,15 +131,13 @@ public function getItem($key)
127131
$value = null;
128132
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
129133
try {
130-
$e = null;
131134
$value = unserialize($value);
132-
} catch (\Error $e) {
133-
} catch (\Exception $e) {
134-
}
135-
if (null !== $e) {
135+
} catch (\Throwable $e) {
136136
$value = null;
137137
$isHit = false;
138138
}
139+
} elseif (\is_array($value) && $value) {
140+
$value = PhpMarshaller::unmarshall($value);
139141
}
140142

141143
$f = $this->createCacheItem;
@@ -266,11 +268,11 @@ private function generateItems(array $keys): \Generator
266268
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
267269
try {
268270
yield $key => $f($key, unserialize($value), true);
269-
} catch (\Error $e) {
270-
yield $key => $f($key, null, false);
271-
} catch (\Exception $e) {
271+
} catch (\Throwable $e) {
272272
yield $key => $f($key, null, false);
273273
}
274+
} elseif (\is_array($value) && $value) {
275+
yield $key => $f($key, PhpMarshaller::unmarshall($value), true);
274276
} else {
275277
yield $key => $f($key, $value, true);
276278
}

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Psr\SimpleCache\CacheInterface;
1515
use Symfony\Component\Cache\Exception\InvalidArgumentException;
1616
use Symfony\Component\Cache\Traits\PhpArrayTrait;
17+
use Symfony\Component\Cache\Traits\PhpMarshaller;
1718
use Symfony\Component\Cache\PruneableInterface;
1819
use Symfony\Component\Cache\ResettableInterface;
1920

@@ -77,14 +78,12 @@ public function get($key, $default = null)
7778
$value = null;
7879
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
7980
try {
80-
$e = null;
81-
$value = unserialize($value);
82-
} catch (\Error $e) {
83-
} catch (\Exception $e) {
84-
}
85-
if (null !== $e) {
81+
return unserialize($value);
82+
} catch (\Throwable $e) {
8683
return $default;
8784
}
85+
} elseif (\is_array($value) && $value) {
86+
return PhpMarshaller::unmarshall($value);
8887
}
8988

9089
return $value;
@@ -235,11 +234,11 @@ private function generateItems(array $keys, $default)
235234
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
236235
try {
237236
yield $key => unserialize($value);
238-
} catch (\Error $e) {
239-
yield $key => $default;
240-
} catch (\Exception $e) {
237+
} catch (\Throwable $e) {
241238
yield $key => $default;
242239
}
240+
} elseif (\is_array($value) && $value) {
241+
yield $key => PhpMarshaller::unmarshall($value);
243242
} else {
244243
yield $key => $value;
245244
}

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

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,19 @@ public function warmUp(array $values)
6868
foreach ($values as $key => $value) {
6969
CacheItem::validateKey(\is_int($key) ? (string) $key : $key);
7070

71-
if (null === $value || \is_object($value)) {
72-
try {
73-
$value = serialize($value);
74-
} catch (\Exception $e) {
75-
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, get_class($value)), 0, $e);
76-
}
77-
} elseif (\is_array($value)) {
71+
if (null === $value) {
72+
$value = 'N;';
73+
} elseif (\is_object($value) || \is_array($value)) {
7874
try {
7975
$serialized = serialize($value);
80-
$unserialized = unserialize($serialized);
8176
} catch (\Exception $e) {
82-
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable array value.', $key), 0, $e);
77+
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? get_class($value) : 'array'), 0, $e);
8378
}
84-
// Store arrays serialized if they contain any objects or references
85-
if ($unserialized !== $value || (false !== strpos($serialized, ';R:') && preg_match('/;R:[1-9]/', $serialized))) {
79+
// Keep value serialized if it contains any "Serializable" objects or any internal references
80+
if (0 === strpos($serialized, 'C:') || preg_match('/;[CRr]:[1-9]/', $serialized)) {
8681
$value = $serialized;
82+
} else {
83+
$value = PhpMarshaller::marshall($value);
8784
}
8885
} elseif (\is_string($value)) {
8986
// Serialize strings if they could be confused with serialized objects or arrays
@@ -104,7 +101,7 @@ public function warmUp(array $values)
104101

105102
file_put_contents($tmpFile, $dump);
106103
@chmod($tmpFile, 0666 & ~umask());
107-
unset($serialized, $unserialized, $value, $dump);
104+
unset($serialized, $value, $dump);
108105

109106
@rename($tmpFile, $this->file);
110107

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ protected function doFetch(array $ids)
9898
$values[$id] = null;
9999
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
100100
$values[$id] = parent::unserialize($value);
101+
} elseif (\is_array($value) && $value) {
102+
$values[$id] = PhpMarshaller::unmarshall($value);
101103
}
102104
}
103105

@@ -122,14 +124,19 @@ protected function doSave(array $values, $lifetime)
122124
$allowCompile = 'cli' !== PHP_SAPI || ini_get('opcache.enable_cli');
123125

124126
foreach ($values as $key => $value) {
125-
if (null === $value || \is_object($value)) {
126-
$value = serialize($value);
127-
} elseif (\is_array($value)) {
128-
$serialized = serialize($value);
129-
$unserialized = parent::unserialize($serialized);
130-
// Store arrays serialized if they contain any objects or references
131-
if ($unserialized !== $value || (false !== strpos($serialized, ';R:') && preg_match('/;R:[1-9]/', $serialized))) {
127+
if (null === $value) {
128+
$value = 'N;';
129+
} elseif (\is_object($value) || \is_array($value)) {
130+
try {
131+
$serialized = serialize($value);
132+
} catch (\Exception $e) {
133+
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? get_class($value) : 'array'), 0, $e);
134+
}
135+
// Keep value serialized if it contains any "Serializable" objects or any internal references
136+
if (0 === strpos($serialized, 'C:') || preg_match('/;[CRr]:[1-9]/', $serialized)) {
132137
$value = $serialized;
138+
} else {
139+
$value = PhpMarshaller::marshall($value);
133140
}
134141
} elseif (\is_string($value)) {
135142
// Serialize strings if they could be confused with serialized objects or arrays
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Traits;
13+
14+
/**
15+
* @author Nicolas Grekas <[email protected]>
16+
*
17+
* @internal
18+
*/
19+
class PhpMarshaller
20+
{
21+
private const COOKIE = "\xCB\xD7\xA53\x26\xA0\x92P\x13\xA64\x9A\xA5\xB6\xA1\x23";
22+
23+
private static $reflectors = array();
24+
private static $constructors = array();
25+
private static $prototypes = array();
26+
private static $sleep = array();
27+
private static $wakeup = array();
28+
29+
public static function marshall($value)
30+
{
31+
if (!\is_object($value) && !\is_array($value)) {
32+
return $value;
33+
}
34+
$data = array($value);
35+
if (!self::doMarshall($data)) {
36+
return $value;
37+
}
38+
$data[self::COOKIE] = true;
39+
40+
return $data;
41+
}
42+
43+
public static function unmarshall($value)
44+
{
45+
if (!\is_array($value) || !isset($value[self::COOKIE])) {
46+
return $value;
47+
}
48+
self::doUnmarshall($value);
49+
50+
return $value[0];
51+
}
52+
53+
private static function doMarshall(array &$array): bool
54+
{
55+
$containsObject = false;
56+
57+
foreach ($array as &$value) {
58+
if (\is_object($value)) {
59+
$containsObject = true;
60+
$class = \get_class($value);
61+
$data = array(self::COOKIE => $class);
62+
63+
if (self::$sleep[$class] ?? self::$sleep[$class] = \method_exists($class, '__sleep')) {
64+
$properties = array_flip($value->__sleep());
65+
} else {
66+
$properties = null;
67+
}
68+
69+
foreach ((array) $value as $k => $v) {
70+
$k = (string) $k;
71+
if ('' === $k || "\0" !== $k[0]) {
72+
$c = $class;
73+
} elseif ('*' === $k[1]) {
74+
$c = $class;
75+
$k = substr($k, 3);
76+
} else {
77+
$i = strpos($k, "\0", 2);
78+
$c = substr($k, 1, $i - 1);
79+
$k = substr($k, 1 + $i);
80+
}
81+
if (null !== $properties && !isset($properties[$k])) {
82+
continue;
83+
}
84+
$data[$c][$k] = $v;
85+
}
86+
87+
self::doMarshall($data);
88+
$value = $data;
89+
} elseif (\is_array($value) && self::doMarshall($value)) {
90+
$containsObject = $value[self::COOKIE] = true;
91+
}
92+
}
93+
94+
return $containsObject;
95+
}
96+
97+
private static function doUnmarshall(array &$array)
98+
{
99+
foreach ($array as &$value) {
100+
if (!\is_array($value) || !isset($value[self::COOKIE])) {
101+
continue;
102+
}
103+
$class = $value[self::COOKIE];
104+
unset($value[self::COOKIE]);
105+
self::doUnmarshall($value);
106+
107+
if (true === $class) {
108+
continue;
109+
}
110+
$classReflector = self::$reflectors[$class] ?? self::getClassReflector($class);
111+
$instance = isset(self::$prototypes[$class]) ? clone self::$prototypes[$class] : $classReflector->newInstanceWithoutConstructor();
112+
113+
foreach ($value as $class => $properties) {
114+
(self::$constructors[$class] ?? self::getConstructor($class))($instance, $properties);
115+
}
116+
117+
if (self::$wakeup[$class] ?? self::$wakeup[$class] = \method_exists($class, '__wakeup')) {
118+
$instance->__wakeup();
119+
}
120+
121+
$value = $instance;
122+
}
123+
}
124+
125+
private static function getClassReflector($class)
126+
{
127+
$reflector = new \ReflectionClass($class);
128+
129+
if (!\method_exists($class, '__clone')) {
130+
self::$prototypes[$class] = $reflector->newInstanceWithoutConstructor();
131+
}
132+
133+
return self::$reflectors[$class] = $reflector;
134+
}
135+
136+
private static function getConstructor($class)
137+
{
138+
$classReflector = self::$reflectors[$class] ?? self::getClassReflector($class);
139+
140+
if ($classReflector->isInternal()) {
141+
$propertyReflectors = array();
142+
foreach ($classReflector->getProperties() as $propertyReflector) {
143+
$propertyReflector->setAccessible(true);
144+
$propertyReflectors[$propertyReflector->name] = $propertyReflector;
145+
}
146+
$constructor = function ($instance, array $properties) use ($propertyReflectors) {
147+
foreach ($properties as $k => $v) {
148+
if (isset($propertyReflectors[$k])) {
149+
$propertyReflectors[$k]->setValue($instance, $v);
150+
} else {
151+
$instance->$k = $v;
152+
}
153+
}
154+
};
155+
} else {
156+
$constructor = \Closure::bind(function ($instance, array $properties) {
157+
foreach ($properties as $k => $v) {
158+
$instance->$k = $v;
159+
}
160+
}, null, $class);
161+
}
162+
163+
return self::$constructors[$class] = $constructor;
164+
}
165+
}

0 commit comments

Comments
 (0)