From 9b6ef5f622b4b29cf98bdc144f2a370c3a40d4df Mon Sep 17 00:00:00 2001 From: Sorin Sarca Date: Fri, 10 Jan 2025 22:42:24 +0200 Subject: [PATCH] Reference bug (#155) * Fixed #154 - reference bug * Updated changelog --- CHANGELOG.md | 4 ++++ src/DeserializationHandler.php | 9 ++++++--- src/ReflectionClass.php | 12 ++++++++++-- src/SerializationHandler.php | 14 ++++++++++++-- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8eb8e9..1246cd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ CHANGELOG --------- +### v4.3.1, 2025.01.10 + +- Fixed reference bug [#154](https://github.com/opis/closure/issues/154) + ### v4.3.0, 2025.01.08 - Proper serialization of private properties diff --git a/src/DeserializationHandler.php b/src/DeserializationHandler.php index d2c1c38..d910a28 100644 --- a/src/DeserializationHandler.php +++ b/src/DeserializationHandler.php @@ -2,7 +2,7 @@ namespace Opis\Closure; -use stdClass, WeakMap, Closure; +use stdClass, WeakMap, Closure, SplObjectStorage; use function unserialize; /** @@ -15,6 +15,8 @@ class DeserializationHandler private ?array $visitedArrays = null; private array $options; + private ?SplObjectStorage $refKeepAlive; + public function __construct(?array $options = null) { $this->options = $options ?? []; @@ -25,6 +27,7 @@ public function unserialize(string $serialized): mixed $this->unboxed = new WeakMap(); $this->refs = new WeakMap(); $this->visitedArrays = []; + $this->refKeepAlive = new SplObjectStorage(); if (Serializer::$v3Compatible) { $this->v3_unboxed = []; @@ -46,7 +49,7 @@ public function unserialize(string $serialized): mixed return $data; } finally { - $this->unboxed = $this->refs = $this->visitedArrays = null; + $this->unboxed = $this->refs = $this->visitedArrays = $this->refKeepAlive = null; $this->v3_unboxed = $this->v3_refs = null; } } @@ -74,7 +77,7 @@ private function handleIterable(array|object &$iterable): void private function handleArray(array &$array): void { - $id = ReflectionClass::getRefId($array); + $id = ReflectionClass::getRefId($array, $this->refKeepAlive); if (!isset($this->visitedArrays[$id])) { $this->visitedArrays[$id] = true; $this->handleIterable($array); diff --git a/src/ReflectionClass.php b/src/ReflectionClass.php index 52f8d45..a069f16 100644 --- a/src/ReflectionClass.php +++ b/src/ReflectionClass.php @@ -107,9 +107,17 @@ public static function objectIsEnum(object $value): bool return self::$enumExists && ($value instanceof \UnitEnum); } - public static function getRefId(mixed &$reference): ?string + public static function getRefId(mixed &$reference, ?\SplObjectStorage $keepAlive = null): ?string { - return \ReflectionReference::fromArrayElement([&$reference], 0)?->getId(); + $ref = \ReflectionReference::fromArrayElement([&$reference], 0); + if (!$ref) { + return null; + } + + // we save this so the ref ids cannot be reused while serializing/deserializing + $keepAlive?->attach($ref); + + return $ref->getId(); } public static function isAnonymousClassName(string $class): bool diff --git a/src/SerializationHandler.php b/src/SerializationHandler.php index ef1df66..d5b0c67 100644 --- a/src/SerializationHandler.php +++ b/src/SerializationHandler.php @@ -19,6 +19,8 @@ class SerializationHandler private bool $hasClosures; + private ?SplObjectStorage $refKeepAlive; + public function serialize(mixed $data): string { $this->arrayMap = []; @@ -27,6 +29,7 @@ public function serialize(mixed $data): string $this->shouldBox = new WeakMap(); $this->info = []; $this->hasClosures = false; + $this->refKeepAlive = new SplObjectStorage(); try { // get boxed structure @@ -37,7 +40,12 @@ public function serialize(mixed $data): string } return serialize($data); } finally { - $this->arrayMap = $this->objectMap = $this->priority = $this->shouldBox = $this->info = null; + $this->arrayMap = + $this->objectMap = + $this->priority = + $this->refKeepAlive = + $this->shouldBox = + $this->info = null; } } @@ -155,12 +163,14 @@ private function handleObject(object $data): object return $box; } + private SplObjectStorage $keep; + private function &handleArray(array &$data, bool $skipRefId = false): array { if ($skipRefId) { $box = []; } else { - $id = ReflectionClass::getRefId($data); + $id = ReflectionClass::getRefId($data, $this->refKeepAlive); if (array_key_exists($id, $this->arrayMap)) { return $this->arrayMap[$id]; }