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

Skip to content

Commit 1890567

Browse files
[Cache] serialize objects using native arrays when possible
1 parent 1b2bd8f commit 1890567

File tree

5 files changed

+209
-19
lines changed

5 files changed

+209
-19
lines changed

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

Lines changed: 8 additions & 0 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
}
@@ -132,6 +136,8 @@ public function getItem($key)
132136
$value = null;
133137
$isHit = false;
134138
}
139+
} elseif (\is_array($value) && $value) {
140+
$value = PhpMarshaller::unmarshall($value);
135141
}
136142

137143
$f = $this->createCacheItem;
@@ -265,6 +271,8 @@ private function generateItems(array $keys): \Generator
265271
} catch (\Throwable $e) {
266272
yield $key => $f($key, null, false);
267273
}
274+
} elseif (\is_array($value) && $value) {
275+
yield $key => $f($key, PhpMarshaller::unmarshall($value), true);
268276
} else {
269277
yield $key => $f($key, $value, true);
270278
}

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

Lines changed: 5 additions & 0 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

@@ -82,6 +83,8 @@ public function get($key, $default = null)
8283
} catch (\Throwable $e) {
8384
return $default;
8485
}
86+
} elseif (\is_array($value) && $value) {
87+
return PhpMarshaller::unmarshall($value);
8588
}
8689

8790
return $value;
@@ -235,6 +238,8 @@ private function generateItems(array $keys, $default)
235238
} catch (\Throwable $e) {
236239
yield $key => $default;
237240
}
241+
} elseif (\is_array($value) && $value) {
242+
yield $key => PhpMarshaller::unmarshall($value);
238243
} else {
239244
yield $key => $value;
240245
}

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,22 @@ 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 {
75+
$e = null;
7976
$serialized = serialize($value);
80-
$unserialized = unserialize($serialized);
8177
} catch (\Exception $e) {
82-
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable array value.', $key), 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+
if (null !== $e || false === $serialized) {
80+
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? get_class($value) : 'array'), 0, $e);
81+
}
82+
// Keep value serialized if it contains any "Serializable" objects or any internal references
83+
if ('C' === $serialized[0] || preg_match('/;[CRr]:[1-9]/', $serialized)) {
8684
$value = $serialized;
85+
} else {
86+
$value = PhpMarshaller::marshall($value);
8787
}
8888
} elseif (\is_string($value)) {
8989
// Serialize strings if they could be confused with serialized objects or arrays
@@ -104,7 +104,7 @@ public function warmUp(array $values)
104104

105105
file_put_contents($tmpFile, $dump);
106106
@chmod($tmpFile, 0666 & ~umask());
107-
unset($serialized, $unserialized, $value, $dump);
107+
unset($serialized, $value, $dump);
108108

109109
@rename($tmpFile, $this->file);
110110

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

Lines changed: 17 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,22 @@ 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+
$e = null;
132+
$serialized = serialize($value);
133+
} catch (\Exception $e) {
134+
}
135+
if (null !== $e || false === $serialized) {
136+
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? get_class($value) : 'array'), 0, $e);
137+
}
138+
// Keep value serialized if it contains any "Serializable" objects or any internal references
139+
if ('C' === $serialized[0] || preg_match('/;[CRr]:[1-9]/', $serialized)) {
132140
$value = $serialized;
141+
} else {
142+
$value = PhpMarshaller::marshall($value, true);
133143
}
134144
} elseif (\is_string($value)) {
135145
// Serialize strings if they could be confused with serialized objects or arrays
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
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 $serialize = array();
28+
private static $wakeup = array();
29+
30+
public static function marshall($value, $useSetState = false)
31+
{
32+
if (!\is_object($value) && !\is_array($value)) {
33+
return $value;
34+
}
35+
$data = array($value);
36+
if (!self::doMarshall($data, $useSetState)) {
37+
return $value;
38+
}
39+
$data[self::COOKIE] = true;
40+
41+
return $data;
42+
}
43+
44+
public static function unmarshall($value)
45+
{
46+
if (!\is_array($value) || !isset($value[self::COOKIE])) {
47+
return $value;
48+
}
49+
self::doUnmarshall($value);
50+
51+
return $value[0];
52+
}
53+
54+
private static function doMarshall(array &$array, $useSetState): bool
55+
{
56+
$containsObject = false;
57+
58+
foreach ($array as &$value) {
59+
if (\is_object($value)) {
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+
if (self::doMarshall($data, $useSetState) || !$useSetState || null !== $properties || (self::$serialize[$class] ?? self::$serialize[$class] = !\method_exists($class, '__set_state') || $value instanceof \Serializable)) {
88+
$containsObject = true;
89+
$value = $data;
90+
}
91+
} elseif (\is_array($value) && self::doMarshall($value, $useSetState)) {
92+
$containsObject = $value[self::COOKIE] = true;
93+
}
94+
}
95+
96+
return $containsObject;
97+
}
98+
99+
private static function doUnmarshall(array &$array)
100+
{
101+
foreach ($array as &$value) {
102+
if (!\is_array($value) || !isset($value[self::COOKIE])) {
103+
continue;
104+
}
105+
$class = $value[self::COOKIE];
106+
unset($value[self::COOKIE]);
107+
self::doUnmarshall($value);
108+
109+
if (true === $class) {
110+
continue;
111+
}
112+
$classReflector = self::$reflectors[$class] ?? self::getClassReflector($class);
113+
$instance = isset(self::$prototypes[$class]) ? clone self::$prototypes[$class] : $classReflector->newInstanceWithoutConstructor();
114+
115+
foreach ($value as $class => $properties) {
116+
(self::$constructors[$class] ?? self::getConstructor($class))($instance, $properties);
117+
}
118+
119+
if (self::$wakeup[$class] ?? self::$wakeup[$class] = \method_exists($class, '__wakeup')) {
120+
$instance->__wakeup();
121+
}
122+
123+
$value = $instance;
124+
}
125+
}
126+
127+
private static function getClassReflector($class)
128+
{
129+
$reflector = new \ReflectionClass($class);
130+
131+
if (!\method_exists($class, '__clone')) {
132+
self::$prototypes[$class] = $reflector->newInstanceWithoutConstructor();
133+
}
134+
135+
return self::$reflectors[$class] = $reflector;
136+
}
137+
138+
private static function getConstructor($class)
139+
{
140+
$classReflector = self::$reflectors[$class] ?? self::getClassReflector($class);
141+
142+
if ($classReflector->isInternal()) {
143+
$propertyReflectors = array();
144+
foreach ($classReflector->getProperties() as $propertyReflector) {
145+
$propertyReflector->setAccessible(true);
146+
$propertyReflectors[$propertyReflector->name] = $propertyReflector;
147+
}
148+
$constructor = function ($instance, array $properties) use ($propertyReflectors) {
149+
foreach ($properties as $k => $v) {
150+
if (isset($propertyReflectors[$k])) {
151+
$propertyReflectors[$k]->setValue($instance, $v);
152+
} else {
153+
$instance->$k = $v;
154+
}
155+
}
156+
};
157+
} else {
158+
$constructor = \Closure::bind(function ($instance, array $properties) {
159+
foreach ($properties as $k => $v) {
160+
$instance->$k = $v;
161+
}
162+
}, null, $class);
163+
}
164+
165+
return self::$constructors[$class] = $constructor;
166+
}
167+
}

0 commit comments

Comments
 (0)