Problem/Motivation

PHP has passed an RFC to soft deprecate the use of __sleep() and __wakeup() in favor of __serialize() and __unserialize(): https://wiki.php.net/rfc/soft-deprecate-sleep-wakeup

This is after initially an RFC to deprecate them in PHP 8.5 had passed, but was reconsidered because the full scope was underestimated:

Symfony has already addressed it.
But core has wider usage overriding seriazed keys, especially in container serialization and missed transition to use the Serialzable interface.

New approach (since PHP 7.4) is using https://wiki.php.net/rfc/custom_object_serialization and since PHP 8.1 using the Serializable interface is deprecated without magic methods

In addition, __wakeup() does not support setting readonly parameters, see examples:
https://3v4l.org/OPTLj
https://3v4l.org/3mViB

(Note that judging from core CI runs, this might have been fixed in PHP 8.5.6: See https://git.drupalcode.org/project/drupal/-/jobs/9745198 on 8.5.6 (successful Drupal\Tests\node\Functional\NodeAccessRebuildNodeGrantsTest) vs this browser output artifact Drupal\Tests\node\Functional\NodeAccessRebuildNodeGrantsTest on PHP 8.3 (11.x)

Setting readonly properties is supported in __unserialize()
https://3v4l.org/G0WCa

Steps to reproduce

(Outdated since deprecation reverted)
See core's unit tests using PHP 8.5 in latest pipelines

Deprecated: The __wakeup() serialization magic method has been deprecated. Implement __unserialize() instead (or in addition, if support for old PHP versions is necessary) in /builds/issue/drupal-3548957/core/lib/Drupal/Core/DependencyInjection/DependencySerializationTrait.php on line 12
Deprecated: The __sleep() serialization magic method has been deprecated. Implement __serialize() instead (or in addition, if support for old PHP versions is necessary) in /builds/issue/drupal-3548957/core/lib/Drupal/Core/Database/Connection.php on line 29
Deprecated: The __sleep() serialization magic method has been deprecated. Implement __serialize() instead (or in addition, if support for old PHP versions is necessary) in /builds/issue/drupal-3548957/core/lib/Drupal/Core/Cache/MemoryBackend.php on line 23

Proposed resolution

- replace methods as suggested
- provide BC
- write CR

Remaining tasks

MR: https://git.drupalcode.org/project/drupal/-/merge_requests/15448
This provides BC for subclasses that implementing __sleep and __wakeup

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

Issue fork drupal-3548971

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

andypost created an issue. See original summary.

andypost’s picture

andypost’s picture

Title: Deal with PHP 8.5 depration of __sleep()/__wakeup() » Deal with PHP 8.5 deprecation of __sleep()/__wakeup()
godotislate’s picture

Magic methods don't seem to be addressed specifically in the BC policy, but they seem to fall under "Underscore-prefixed functions and methods" and "Public methods not specified in an interface" as internal code.

That being said, it seems reasonable there may be contrib or custom classes that use DependencySerializationTrait and rely on overriding the trait's __sleep() and __wakeup() so that they can be safely cached or otherwise stored to the database.

Maybe in DependencySerializationTrait, __serialize() and __unserialize() can be implemented to call __sleep() and __wakeup(), so that any subclass implementing the latter methods will still have those invoked. It might also be possible to add a boolean flag property in the trait, and that flag is set to TRUE when __sleep() and __wakeup() are called by __serialize() and __unserialize(), and then otherwise a deprecation triggered.

So roughly something like this:


trait DependencySerializationTrait {

  protected bool $nonDeprecatedCall = FALSE;

  public function __serialize(): array {
    $this->nonDeprecatedCall = TRUE;
    $keys = $this->__sleep();
    $this->nonDeprecatedCall = FALSE;
    ...
  }

  public function __sleep(): array {
    if (!$this->nonDeprecatedCall) {
      @trigger_error(".."}
    }
    ...
  }

  // Do the same for __unserialize() and __wakeup().

Any class that overrides __sleep() or __wakeup() would need to call the trait's method in order to trigger deprecation, though.

This could also apply to similar situations other than use of DependencySerializationTrait.

andypost’s picture

PHP reverted deprecation for 8.5 so making this optional but as clean-up it still makes sense and for futureproof

Ref https://github.com/php/php-src/commit/fc353966f3976f733a2d038ce9b29016c4...

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

andypost’s picture

I still think it should change serialization at least for none-trait places

andypost’s picture

Status: Active » Needs work

I bet it needs split on issues so I started to backport changes https://git.drupalcode.org/project/drupal/-/merge_requests/12877/diffs?c...

Also it may need a sniffer or phpstan rule to catch remains

godotislate’s picture

Coming from #3424344: Allow DependencySerializationTrait to work with readonly properties: readonly properties can't be set in __wakeup(), but they can in __unserialize(), which might be a good reason to include DependencySerializationTrait in scope here. Or if not, to create an issue for to deprecate __sleep()/__wakeup() in the trait and use __serialize()/__unserialize().

godotislate’s picture

andypost’s picture

Thanks, the issue was supposed to be a plan/meta as every case here needs own issue it was delayed as 8.5 serialization been relaxed but now 8.6 is coming)

So let's fix the trait first and then decide about split

godotislate’s picture

I copied over a lot of MR 15051 to MR 15448, then did a separate approach for DependencySerializationTrait, because one problem to address is this:

  • A class uses DependencySerializationTrait
  • The class overrides __sleep() and __wakeup() to add its own special handling
  • Because DependencySerializationTrait now has __serialize() and __unserialize(), __sleep() and __wakeup() don't run

The MR tries check whether the class itself implements __sleep or __wakeup, and to run them from __serialize or __unserialize. A bunch of tests are failing though. Will need more investigation.

Other concern:
Can objects that have been serialized and stored in the DB before the switch to __serialize and __unserialize still be able to be unserialized?

godotislate’s picture

Took another shot at this. I restarted https://git.drupalcode.org/project/drupal/-/merge_requests/15448 from scratch to address test failures one by one. After it's green, I'll compare with https://git.drupalcode.org/project/drupal/-/merge_requests/15051 to see if it's missing anything, but I'm still working through test failures.

Can objects that have been serialized and stored in the DB before the switch to __serialize and __unserialize still be able to be unserialized?

This is no on one of the tests at least, but I may be able to rewrite the relevant unserialize/serialize method to more closely resemble what sleep/wakeup did before.

godotislate’s picture

OK, finally got https://git.drupalcode.org/project/drupal/-/merge_requests/15448 green.

Still need to review https://git.drupalcode.org/project/drupal/-/merge_requests/15051 to see if MR 15448 is missing anything from it, though the normalization method was very usefuly to pass tests.

Along with that, probably need to clean up a few things.

The general approach is this:
Introduced two new traits that provide __serialize and __sleep, as well as __unserialize and __wakeup.
For classes that use the traits, on serialization, __serialize will check if there's an implementation of __sleep in the class that overrides the implementation of __sleep in the trait. If so, it will call __sleep.
Both __serialize and __sleep in the trait call an abstract function doSerialize(), which should be implemented in the class, and basically performs everything that typicall would be done in __serialize. This way __serialize and __sleep should work the same (with __sleep just returing the array keys), and this is to provide BC in case either the subclass's __sleep method calls the parent, or if some external code calls __sleep.
There is similar functionality for __unserialize and __wakeup with an abstract doUnserialize method.
Still outstanding is adding test coverage to show that the BC layer actually works.

andypost’s picture

nice idea with traits! looks this way it can give real BC

godotislate’s picture

Issue summary: View changes
Status: Needs work » Needs review

OK, MR https://git.drupalcode.org/project/drupal/-/merge_requests/15448 is ready.

I added a test class from https://git.drupalcode.org/project/drupal/-/merge_requests/15051 and cleaned some things up.
I also added tests for the BC layer.
I reviewed the CR and it seems fine. I made small edit to add that __wakeup can not set readonly properties, while __unserialize can.

I'm hiding https://git.drupalcode.org/project/drupal/-/merge_requests/15051. Hope that's OK, let me know if there are any issues.

godotislate changed the visibility of the branch 3548971-deal-with-sleep to hidden.

godotislate’s picture

Interestingly, the PHP RFC to "soft" deprecate __sleep and __wakeup talks about the same challenges we've had to address here with BC.

godotislate’s picture

Title: Deal with PHP 8.5 deprecation of __sleep()/__wakeup() » Replace PHP soft-deprecated __sleep()/__wakeup() with __serialize()/__unserialize b
Issue summary: View changes

Updated the IS for latest status.

godotislate’s picture

Title: Replace PHP soft-deprecated __sleep()/__wakeup() with __serialize()/__unserialize b » Replace PHP soft-deprecated __sleep()/__wakeup() with __serialize()/__unserialize()