From 467e17272f9190bd782b616cfac4441cdcce03b9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 14 Aug 2025 14:47:58 +0200 Subject: [PATCH] [String] Deprecate implementing `__sleep/wakeup()` on string implementations --- UPGRADE-7.4.md | 5 ++ .../DataCollector/DataCollector.php | 15 ++++-- src/Symfony/Component/HttpKernel/Kernel.php | 35 ++++++++++++-- .../Serializer/Mapping/AttributeMetadata.php | 2 +- .../Serializer/Mapping/ClassMetadata.php | 2 +- .../Component/String/AbstractString.php | 29 ++++++++++++ src/Symfony/Component/String/CHANGELOG.md | 5 ++ src/Symfony/Component/String/LazyString.php | 29 ++++++++++++ .../Component/String/UnicodeString.php | 46 +++++++++++++++++++ src/Symfony/Component/String/composer.json | 1 + 10 files changed, 159 insertions(+), 10 deletions(-) diff --git a/UPGRADE-7.4.md b/UPGRADE-7.4.md index bd785a4ec6d88..459a43dd247cb 100644 --- a/UPGRADE-7.4.md +++ b/UPGRADE-7.4.md @@ -71,6 +71,11 @@ Serializer * Make `AttributeMetadata` and `ClassMetadata` final +String +------ + + * Deprecate implementing `__sleep/wakeup()` on string implementations + Translation ----------- diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php index b640d47f727dc..0f863452eff10 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php @@ -108,24 +108,30 @@ public function __serialize(): array public function __unserialize(array $data): void { - if (self::class !== (new \ReflectionMethod($this, '__wakeup'))->class) { + if ($wakeup = self::class !== (new \ReflectionMethod($this, '__wakeup'))->class) { trigger_deprecation('symfony/http-kernel', '7.4', 'Implementing "%s::__wakeup()" is deprecated, use "__unserialize()" instead.', get_debug_type($this)); } if (\in_array(array_keys($data), [['data'], ["\0*\0data"]], true)) { $this->data = $data['data'] ?? $data["\0*\0data"]; + if ($wakeup) { + $this->__wakeup(); + } + return; } trigger_deprecation('symfony/http-kernel', '7.4', 'Passing more than just key "data" to "%s::__unserialize()" is deprecated, populate properties in "%s::__unserialize()" instead.', self::class, get_debug_type($this)); - \Closure::bind(function ($data) { + \Closure::bind(function ($data) use ($wakeup) { foreach ($data as $key => $value) { $this->{("\0" === $key[0] ?? '') ? substr($key, 1 + strrpos($key, "\0")) : $key} = $value; } - $this->__wakeup(); + if ($wakeup) { + $this->__wakeup(); + } }, $this, static::class)($data); } @@ -136,6 +142,8 @@ public function __unserialize(array $data): void */ public function __sleep(): array { + trigger_deprecation('symfony/http-kernel', '7.4', 'Calling "%s::__sleep()" is deprecated, use "__serialize()" instead.', get_debug_type($this)); + return ['data']; } @@ -146,6 +154,7 @@ public function __sleep(): array */ public function __wakeup(): void { + trigger_deprecation('symfony/http-kernel', '7.4', 'Calling "%s::__wakeup()" is deprecated, use "__unserialize()" instead.', get_debug_type($this)); } /** diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 763242dcde979..d193637c7615a 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -802,25 +802,46 @@ public function __serialize(): array public function __unserialize(array $data): void { - if (self::class !== (new \ReflectionMethod($this, '__wakeup'))->class) { + if ($wakeup = self::class !== (new \ReflectionMethod($this, '__wakeup'))->class) { trigger_deprecation('symfony/http-kernel', '7.4', 'Implementing "%s::__wakeup()" is deprecated, use "__unserialize()" instead.', get_debug_type($this)); } if (\in_array(array_keys($data), [['environment', 'debug'], ["\0*\0environment", "\0*\0debug"]], true)) { - $this->environment = $data['environment'] ?? $data["\0*\0environment"]; - $this->debug = $data['debug'] ?? $data["\0*\0debug"]; + $environment = $data['environment'] ?? $data["\0*\0environment"]; + $debug = $data['debug'] ?? $data["\0*\0debug"]; + + if (\is_object($environment) || \is_object($debug)) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + $this->environment = $environment; + $this->debug = $debug; + + if ($wakeup) { + $this->__wakeup(); + } else { + $this->__construct($environment, $debug); + } return; } trigger_deprecation('symfony/http-kernel', '7.4', 'Passing more than just key "environment" and "debug" to "%s::__unserialize()" is deprecated, populate properties in "%s::__unserialize()" instead.', self::class, get_debug_type($this)); - \Closure::bind(function ($data) { + \Closure::bind(function ($data) use ($wakeup) { foreach ($data as $key => $value) { $this->{("\0" === $key[0] ?? '') ? substr($key, 1 + strrpos($key, "\0")) : $key} = $value; } - $this->__wakeup(); + if ($wakeup) { + $this->__wakeup(); + } else { + if (\is_object($this->environment) || \is_object($this->debug)) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + $this->__construct($this->environment, $this->debug); + } }, $this, static::class)($data); } @@ -831,6 +852,8 @@ public function __unserialize(array $data): void */ public function __sleep(): array { + trigger_deprecation('symfony/http-kernel', '7.4', 'Calling "%s::__sleep()" is deprecated, use "__serialize()" instead.', get_debug_type($this)); + return ['environment', 'debug']; } @@ -841,6 +864,8 @@ public function __sleep(): array */ public function __wakeup(): void { + trigger_deprecation('symfony/http-kernel', '7.4', 'Calling "%s::__wakeup()" is deprecated, use "__unserialize()" instead.', get_debug_type($this)); + if (\is_object($this->environment) || \is_object($this->debug)) { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); } diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index 5159af35df4c8..f7cff256413df 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -219,7 +219,7 @@ public function merge(AttributeMetadataInterface $attributeMetadata): void /** * @internal since Symfony 7.4, will be replaced by `__serialize()` in 8.0 * - * @final since Symfony 7.4 + * @final since Symfony 7.4, will be replaced by `__serialize()` in 8.0 */ public function __sleep(): array { diff --git a/src/Symfony/Component/Serializer/Mapping/ClassMetadata.php b/src/Symfony/Component/Serializer/Mapping/ClassMetadata.php index d352bfb4b77be..988516e42a0ab 100644 --- a/src/Symfony/Component/Serializer/Mapping/ClassMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/ClassMetadata.php @@ -96,7 +96,7 @@ public function setClassDiscriminatorMapping(?ClassDiscriminatorMapping $mapping /** * @internal since Symfony 7.4, will be replaced by `__serialize()` in 8.0 * - * @final since Symfony 7.4 + * @final since Symfony 7.4, will be replaced by `__serialize()` in 8.0 */ public function __sleep(): array { diff --git a/src/Symfony/Component/String/AbstractString.php b/src/Symfony/Component/String/AbstractString.php index fc60f8f24b211..69f6ba5e1950c 100644 --- a/src/Symfony/Component/String/AbstractString.php +++ b/src/Symfony/Component/String/AbstractString.php @@ -706,8 +706,37 @@ public function wordwrap(int $width = 75, string $break = "\n", bool $cut = fals return $str; } + public function __serialize(): array + { + if (self::class === (new \ReflectionMethod($this, '__sleep'))->class) { + return ['string' => $this->string]; + } + + trigger_deprecation('symfony/string', '7.4', 'Implementing "%s::__sleep()" is deprecated, use "__serialize()" instead.', get_debug_type($this)); + + $data = []; + foreach ($this->__sleep() as $key) { + try { + if (($r = new \ReflectionProperty($this, $key))->isInitialized($this)) { + $data[$key] = $r->getValue($this); + } + } catch (\ReflectionException) { + $data[$key] = $this->$key; + } + } + + return $data; + } + + /** + * @internal since Symfony 7.4, will be removed in 8.0 + * + * @final since Symfony 7.4, will be removed in 8.0 + */ public function __sleep(): array { + trigger_deprecation('symfony/string', '7.4', 'Calling "%s::__sleep()" is deprecated, use "__serialize()" instead.', get_debug_type($this)); + return ['string']; } diff --git a/src/Symfony/Component/String/CHANGELOG.md b/src/Symfony/Component/String/CHANGELOG.md index 141036ce8c916..d5569bea23839 100644 --- a/src/Symfony/Component/String/CHANGELOG.md +++ b/src/Symfony/Component/String/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.4 +--- + + * Deprecate implementing `__sleep/wakeup()` on string implementations + 7.3 --- diff --git a/src/Symfony/Component/String/LazyString.php b/src/Symfony/Component/String/LazyString.php index b86d7337e208c..d250c288fc55e 100644 --- a/src/Symfony/Component/String/LazyString.php +++ b/src/Symfony/Component/String/LazyString.php @@ -101,6 +101,35 @@ public function __toString(): string } } + public function __serialize(): array + { + if (self::class === (new \ReflectionMethod($this, '__sleep'))->class) { + $this->__toString(); + + return ['value' => $this->value]; + } + + trigger_deprecation('symfony/string', '7.4', 'Implementing "%s::__sleep()" is deprecated, use "__serialize()" instead.', get_debug_type($this)); + + $data = []; + foreach ($this->__sleep() as $key) { + try { + if (($r = new \ReflectionProperty($this, $key))->isInitialized($this)) { + $data[$key] = $r->getValue($this); + } + } catch (\ReflectionException) { + $data[$key] = $this->$key; + } + } + + return $data; + } + + /** + * @internal since Symfony 7.4, will be removed in 8.0 + * + * @final since Symfony 7.4, will be removed in 8.0 + */ public function __sleep(): array { $this->__toString(); diff --git a/src/Symfony/Component/String/UnicodeString.php b/src/Symfony/Component/String/UnicodeString.php index 811ae0285ac00..4da506aeb0847 100644 --- a/src/Symfony/Component/String/UnicodeString.php +++ b/src/Symfony/Component/String/UnicodeString.php @@ -366,8 +366,54 @@ public function startsWith(string|iterable|AbstractString $prefix): bool return $prefix === $grapheme; } + public function __unserialize(array $data): void + { + if ($wakeup = self::class !== (new \ReflectionMethod($this, '__wakeup'))->class) { + trigger_deprecation('symfony/string', '7.4', 'Implementing "%s::__wakeup()" is deprecated, use "__unserialize()" instead.', get_debug_type($this)); + } + + try { + if (\in_array(array_keys($data), [['string'], ["\0*\0string"]], true)) { + $this->string = $data['string'] ?? $data["\0*\0string"]; + + if ($wakeup) { + $this->__wakeup(); + } + + return; + } + + trigger_deprecation('symfony/string', '7.4', 'Passing more than just key "string" to "%s::__unserialize()" is deprecated, populate properties in "%s::__unserialize()" instead.', self::class, get_debug_type($this)); + + \Closure::bind(function ($data) use ($wakeup) { + foreach ($data as $key => $value) { + $this->{("\0" === $key[0] ?? '') ? substr($key, 1 + strrpos($key, "\0")) : $key} = $value; + } + + if ($wakeup) { + $this->__wakeup(); + } + }, $this, static::class)($data); + } finally { + if (!$wakeup) { + if (!\is_string($this->string)) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string); + } + } + } + + /** + * @internal since Symfony 7.4, will be removed in 8.0 + * + * @final since Symfony 7.4, will be removed in 8.0 + */ public function __wakeup(): void { + trigger_deprecation('symfony/string', '7.4', 'Calling "%s::__wakeup()" is deprecated, use "__unserialize()" instead.', get_debug_type($this)); + if (!\is_string($this->string)) { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); } diff --git a/src/Symfony/Component/String/composer.json b/src/Symfony/Component/String/composer.json index a7ae848dc392d..887ea86a6c0da 100644 --- a/src/Symfony/Component/String/composer.json +++ b/src/Symfony/Component/String/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.33", "symfony/polyfill-intl-normalizer": "~1.0",