Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7951ea1

Browse filesBrowse files
committed
feature #30286 Drop more usages of Serializable (nicolas-grekas)
This PR was merged into the 4.3-dev branch. Discussion ---------- Drop more usages of Serializable | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - When the serialized payload of some class is used for ephemeral needs, my proposal here is to drop implementing `Serializable` and provide a deprecation layer based on `__sleep` to still call the `serialize`/`unserialize` methods. This means that at the behavior level, we provide BC, and at the payload level, we don't. That leaves us with only two "not ephemeral" use cases: - `Route` and `CompilerRoute` instances are serialized by Drupal. We'd better not break such existing payloads without being sure it's ok for Drupal (ping @alexpott). My proposal in this PR is to schedule the BC break for Symfony 5, and ask Drupal (and others) to add a check in their unserialization logic so that they are able to recompile the cached list of routes if unserialize fails due to this change. They could alternatively implement `Serializable` on their own, without calling `parent::(un)serialize()`. - `TokenInterface` from `Security` - for this one, I have no idea yet, except either plan for breaking BC at the payload level (which would mean invalidating all sessions when moving to SF5) - or create a new interface if we think breaking sessions is not reasonable. For now, I've kept using `Serializable`, which could be also fine keeping if we mark the corresponding method `final` to force WDYT? Commits ------- 05d6475 Drop more usages of Serializable
2 parents ec8033f + 05d6475 commit 7951ea1
Copy full SHA for 7951ea1

File tree

Expand file treeCollapse file tree

27 files changed

+216
-114
lines changed
Filter options
Expand file treeCollapse file tree

27 files changed

+216
-114
lines changed

‎UPGRADE-4.3.md

Copy file name to clipboardExpand all lines: UPGRADE-4.3.md
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ Routing
5555

5656
* The `generator_base_class`, `generator_cache_class`, `matcher_base_class`, and `matcher_cache_class` router
5757
options have been deprecated.
58+
* Implementing `Serializable` for `Route` and `CompiledRoute` is deprecated; if you serialize them, please
59+
ensure your unserialization logic can recover from a failure related to an updated serialization format
5860

5961
Security
6062
--------

‎UPGRADE-5.0.md

Copy file name to clipboardExpand all lines: UPGRADE-5.0.md
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ Routing
234234

235235
* The `generator_base_class`, `generator_cache_class`, `matcher_base_class`, and `matcher_cache_class` router
236236
options have been removed.
237+
* `Route` and `CompiledRoute` don't implement `Serializable` anymore; if you serialize them, please
238+
ensure your unserialization logic can recover from a failure related to an updated serialization format
237239

238240
Security
239241
--------

‎src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/AppKernel.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/AppKernel.php
+4-5Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,14 @@ protected function build(ContainerBuilder $container)
7979
$container->register('logger', NullLogger::class);
8080
}
8181

82-
public function serialize()
82+
public function __sleep()
8383
{
84-
return serialize([$this->varDir, $this->testCase, $this->rootConfig, $this->getEnvironment(), $this->isDebug()]);
84+
return ['varDir', 'testCase', 'rootConfig', 'environment', 'debug'];
8585
}
8686

87-
public function unserialize($str)
87+
public function __wakeup()
8888
{
89-
$a = unserialize($str);
90-
$this->__construct($a[0], $a[1], $a[2], $a[3], $a[4]);
89+
$this->__construct($this->varDir, $this->testCase, $this->rootConfig, $this->environment, $this->debug);
9190
}
9291

9392
protected function getKernelParameters()

‎src/Symfony/Bundle/FrameworkBundle/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/composer.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"symfony/dependency-injection": "^4.3",
2525
"symfony/event-dispatcher": "^4.1",
2626
"symfony/http-foundation": "^4.3",
27-
"symfony/http-kernel": "^4.2",
27+
"symfony/http-kernel": "^4.3",
2828
"symfony/polyfill-mbstring": "~1.0",
2929
"symfony/filesystem": "~3.4|~4.0",
3030
"symfony/finder": "~3.4|~4.0",

‎src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php
+2-7Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -215,14 +215,9 @@ public function testPrune()
215215
}
216216
}
217217

218-
class NotUnserializable implements \Serializable
218+
class NotUnserializable
219219
{
220-
public function serialize()
221-
{
222-
return serialize(123);
223-
}
224-
225-
public function unserialize($ser)
220+
public function __wakeup()
226221
{
227222
throw new \Exception(__CLASS__);
228223
}

‎src/Symfony/Component/Cache/Tests/Psr16CacheTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Cache/Tests/Psr16CacheTest.php
+2-7Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,14 +159,9 @@ protected function isPruned($cache, $name)
159159
}
160160
}
161161

162-
class NotUnserializable implements \Serializable
162+
class NotUnserializable
163163
{
164-
public function serialize()
165-
{
166-
return serialize(123);
167-
}
168-
169-
public function unserialize($ser)
164+
public function __wakeup()
170165
{
171166
throw new \Exception(__CLASS__);
172167
}

‎src/Symfony/Component/Cache/Tests/Simple/CacheTestCase.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Cache/Tests/Simple/CacheTestCase.php
+2-7Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,9 @@ public function testPrune()
132132
}
133133
}
134134

135-
class NotUnserializable implements \Serializable
135+
class NotUnserializable
136136
{
137-
public function serialize()
138-
{
139-
return serialize(123);
140-
}
141-
142-
public function unserialize($ser)
137+
public function __wakeup()
143138
{
144139
throw new \Exception(__CLASS__);
145140
}

‎src/Symfony/Component/Debug/Tests/phpt/fatal_with_nested_handlers.phpt

Copy file name to clipboardExpand all lines: src/Symfony/Component/Debug/Tests/phpt/fatal_with_nested_handlers.phpt
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ var_dump([
2424
$eHandler[0]->setExceptionHandler('print_r');
2525

2626
if (true) {
27-
class Broken implements \Serializable
27+
class Broken implements \JsonSerializable
2828
{
2929
}
3030
}
@@ -37,6 +37,6 @@ array(1) {
3737
}
3838
object(Symfony\Component\Debug\Exception\FatalErrorException)#%d (%d) {
3939
["message":protected]=>
40-
string(199) "Error: Class Symfony\Component\Debug\Broken contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize)"
40+
string(179) "Error: Class Symfony\Component\Debug\Broken contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (JsonSerializable::jsonSerialize)"
4141
%a
4242
}

‎src/Symfony/Component/Form/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ CHANGELOG
1515
* added `block_prefix` option to `BaseType`.
1616
* added `help_html` option to display the `help` text as HTML.
1717
* `FormError` doesn't implement `Serializable` anymore
18+
* `FormDataCollector` has been marked as `final`
1819
* added `label_translation_parameters`, `attr_translation_parameters`, `help_translation_parameters` options
1920
to `FormType` to pass translation parameters to form labels, attributes (`placeholder` and `title`) and help text respectively.
2021
The passed parameters will replace placeholders in translation messages.

‎src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php
+9-2Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
*
2828
* @author Robert Schönthal <robert.schoenthal@gmail.com>
2929
* @author Bernhard Schussek <bschussek@gmail.com>
30+
*
31+
* @final since Symfony 4.3
3032
*/
3133
class FormDataCollector extends DataCollector implements FormDataCollectorInterface
3234
{
@@ -229,15 +231,20 @@ public function getData()
229231
return $this->data;
230232
}
231233

232-
public function serialize()
234+
/**
235+
* @internal
236+
*/
237+
public function __sleep()
233238
{
234239
foreach ($this->data['forms_by_hash'] as &$form) {
235240
if (isset($form['type_class']) && !$form['type_class'] instanceof ClassStub) {
236241
$form['type_class'] = new ClassStub($form['type_class']);
237242
}
238243
}
239244

240-
return serialize($this->cloneVar($this->data));
245+
$this->data = $this->cloneVar($this->data);
246+
247+
return parent::__sleep();
241248
}
242249

243250
/**

‎src/Symfony/Component/Form/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/composer.json
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"symfony/config": "~3.4|~4.0",
3232
"symfony/console": "~3.4|~4.0",
3333
"symfony/http-foundation": "~3.4|~4.0",
34-
"symfony/http-kernel": "~3.4|~4.0",
34+
"symfony/http-kernel": "~4.3",
3535
"symfony/security-csrf": "~3.4|~4.0",
3636
"symfony/translation": "~4.2",
3737
"symfony/var-dumper": "~3.4|~4.0"
@@ -41,7 +41,7 @@
4141
"symfony/dependency-injection": "<3.4",
4242
"symfony/doctrine-bridge": "<3.4",
4343
"symfony/framework-bundle": "<3.4",
44-
"symfony/http-kernel": "<3.4",
44+
"symfony/http-kernel": "<4.3",
4545
"symfony/translation": "<4.2",
4646
"symfony/twig-bridge": "<3.4.5|<4.0.5,>=4.0"
4747
},

‎src/Symfony/Component/HttpKernel/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/CHANGELOG.md
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@ CHANGELOG
44
4.3.0
55
-----
66

7+
* `KernelInterface` doesn't extend `Serializable` anymore
8+
* deprecated the `Kernel::serialize()` and `unserialize()` methods
79
* increased the priority of `Symfony\Component\HttpKernel\EventListener\AddRequestFormatsListener`
810
* made `Symfony\Component\HttpKernel\EventListenerLocaleListener` set the default locale early
911
* made `FileLinkFormatter` final and not implement `Serializable` anymore
12+
* the base `DataCollector` doesn't implement `Serializable` anymore, you should
13+
store all the serialized state in the data property instead
14+
* `DumpDataCollector` has been marked as `final`
1015

1116
4.2.0
1217
-----

‎src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php
+29-1Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* @author Fabien Potencier <fabien@symfony.com>
2727
* @author Bernhard Schussek <bschussek@symfony.com>
2828
*/
29-
abstract class DataCollector implements DataCollectorInterface, \Serializable
29+
abstract class DataCollector implements DataCollectorInterface
3030
{
3131
protected $data = [];
3232

@@ -35,16 +35,26 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable
3535
*/
3636
private $cloner;
3737

38+
/**
39+
* @deprecated since Symfony 4.3, store all the serialized state in the data property instead
40+
*/
3841
public function serialize()
3942
{
43+
@trigger_error(sprintf('The "%s" method is deprecated since Symfony 4.3, store all the serialized state in the data property instead.', __METHOD__), E_USER_DEPRECATED);
44+
4045
$trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
4146
$isCalledFromOverridingMethod = isset($trace[1]['function'], $trace[1]['object']) && 'serialize' === $trace[1]['function'] && $this === $trace[1]['object'];
4247

4348
return $isCalledFromOverridingMethod ? $this->data : serialize($this->data);
4449
}
4550

51+
/**
52+
* @deprecated since Symfony 4.3, store all the serialized state in the data property instead
53+
*/
4654
public function unserialize($data)
4755
{
56+
@trigger_error(sprintf('The "%s" method is deprecated since Symfony 4.3, store all the serialized state in the data property instead.', __METHOD__), E_USER_DEPRECATED);
57+
4858
$this->data = \is_array($data) ? $data : unserialize($data);
4959
}
5060

@@ -100,4 +110,22 @@ protected function getCasters()
100110

101111
return $casters;
102112
}
113+
114+
public function __sleep()
115+
{
116+
if (__CLASS__ !== $c = (new \ReflectionMethod($this, 'serialize'))->getDeclaringClass()->name) {
117+
@trigger_error(sprintf('Implementing the "%s::serialize()" method is deprecated since Symfony 4.3, store all the serialized state in the "data" property instead.', $c), E_USER_DEPRECATED);
118+
$this->data = $this->serialize();
119+
}
120+
121+
return ['data'];
122+
}
123+
124+
public function __wakeup()
125+
{
126+
if (__CLASS__ !== $c = (new \ReflectionMethod($this, 'unserialize'))->getDeclaringClass()->name) {
127+
@trigger_error(sprintf('Implementing the "%s::unserialize()" method is deprecated since Symfony 4.3, store all the serialized state in the "data" property instead.', $c), E_USER_DEPRECATED);
128+
$this->unserialize($this->data);
129+
}
130+
}
103131
}

‎src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php
+31-8Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
/**
2727
* @author Nicolas Grekas <p@tchwork.com>
28+
*
29+
* @final since Symfony 4.3
2830
*/
2931
class DumpDataCollector extends DataCollector implements DataDumperInterface
3032
{
@@ -85,6 +87,9 @@ public function dump(Data $data)
8587
$this->isCollected = false;
8688
}
8789

90+
if (!$this->dataCount) {
91+
$this->data = [];
92+
}
8893
$this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt');
8994
++$this->dataCount;
9095

@@ -95,6 +100,10 @@ public function dump(Data $data)
95100

96101
public function collect(Request $request, Response $response, \Exception $exception = null)
97102
{
103+
if (!$this->dataCount) {
104+
$this->data = [];
105+
}
106+
98107
// Sub-requests and programmatic calls stay in the collected profile.
99108
if ($this->dumper || ($this->requestStack && $this->requestStack->getMasterRequest() !== $request) || $request->isXmlHttpRequest() || $request->headers->has('Origin')) {
100109
return;
@@ -136,28 +145,38 @@ public function reset()
136145
$this->clonesIndex = 0;
137146
}
138147

139-
public function serialize()
148+
/**
149+
* @internal
150+
*/
151+
public function __sleep()
140152
{
153+
if (!$this->dataCount) {
154+
$this->data = [];
155+
}
156+
141157
if ($this->clonesCount !== $this->clonesIndex) {
142-
return 'a:0:{}';
158+
return [];
143159
}
144160

145161
$this->data[] = $this->fileLinkFormat;
146162
$this->data[] = $this->charset;
147-
$ser = serialize($this->data);
148-
$this->data = [];
149163
$this->dataCount = 0;
150164
$this->isCollected = true;
151165

152-
return $ser;
166+
return parent::__sleep();
153167
}
154168

155-
public function unserialize($data)
169+
/**
170+
* @internal
171+
*/
172+
public function __wakeup()
156173
{
157-
$this->data = unserialize($data);
174+
parent::__wakeup();
175+
158176
$charset = array_pop($this->data);
159177
$fileLinkFormat = array_pop($this->data);
160178
$this->dataCount = \count($this->data);
179+
161180
self::__construct($this->stopwatch, $fileLinkFormat, $charset);
162181
}
163182

@@ -178,6 +197,10 @@ public function getDumps($format, $maxDepthLimit = -1, $maxItemsPerDepth = -1)
178197
}
179198
$dumps = [];
180199

200+
if (!$this->dataCount) {
201+
return $this->data = [];
202+
}
203+
181204
foreach ($this->data as $dump) {
182205
$dumper->dump($dump['data']->withMaxDepth($maxDepthLimit)->withMaxItemsPerDepth($maxItemsPerDepth));
183206
$dump['data'] = stream_get_contents($data, -1, 0);
@@ -196,7 +219,7 @@ public function getName()
196219

197220
public function __destruct()
198221
{
199-
if (0 === $this->clonesCount-- && !$this->isCollected && $this->data) {
222+
if (0 === $this->clonesCount-- && !$this->isCollected && $this->dataCount) {
200223
$this->clonesCount = 0;
201224
$this->isCollected = true;
202225

‎src/Symfony/Component/HttpKernel/Kernel.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/Kernel.php
+33Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,15 +833,48 @@ public static function stripComments($source)
833833
return $output;
834834
}
835835

836+
/**
837+
* @deprecated since Symfony 4.3
838+
*/
836839
public function serialize()
837840
{
841+
@trigger_error(sprintf('The "%s" method is deprecated since Symfony 4.3.', __METHOD__), E_USER_DEPRECATED);
842+
838843
return serialize([$this->environment, $this->debug]);
839844
}
840845

846+
/**
847+
* @deprecated since Symfony 4.3
848+
*/
841849
public function unserialize($data)
842850
{
851+
@trigger_error(sprintf('The "%s" method is deprecated since Symfony 4.3.', __METHOD__), E_USER_DEPRECATED);
843852
list($environment, $debug) = unserialize($data, ['allowed_classes' => false]);
844853

845854
$this->__construct($environment, $debug);
846855
}
856+
857+
public function __sleep()
858+
{
859+
if (__CLASS__ !== $c = (new \ReflectionMethod($this, 'serialize'))->getDeclaringClass()->name) {
860+
@trigger_error(sprintf('Implementing the "%s::serialize()" method is deprecated since Symfony 4.3.', $c), E_USER_DEPRECATED);
861+
$this->serialized = $this->serialize();
862+
863+
return ['serialized'];
864+
}
865+
866+
return ['environment', 'debug'];
867+
}
868+
869+
public function __wakeup()
870+
{
871+
if (__CLASS__ !== $c = (new \ReflectionMethod($this, 'serialize'))->getDeclaringClass()->name) {
872+
@trigger_error(sprintf('Implementing the "%s::serialize()" method is deprecated since Symfony 4.3.', $c), E_USER_DEPRECATED);
873+
$this->unserialize($this->serialized);
874+
unset($this->serialized);
875+
876+
return;
877+
}
878+
$this->__construct($this->environment, $this->debug);
879+
}
847880
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.