Skip to content

Zend: Deprecate __sleep() and __wakeup() #19435

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Girgias
Copy link
Member

@Girgias Girgias commented Aug 9, 2025

@@ -110,6 +110,10 @@ $db = MySQLPDOTest::factory();
$db->exec('DROP TABLE IF EXISTS test_stmt_fetchserialize_fetch_class');
?>
--EXPECTF--
Deprecated: The __sleep() serialization hook has been deprecated. Implement __serialize() instead (or in addition, if support for old PHP versions is necessary) in %s on line %d
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these tests not be rewritten to use the new methods?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because those tests are for the deprecated PDO::FETCH_SERIALIZE option which relies on the deprecated Serialize interface methods.

@bukka
Copy link
Member

bukka commented Aug 10, 2025

That's a lot of tests to be updated.

I have been looking into this more and checking the actual conversion in real code. This might actually suck for users a bit because in this case we basically tell them to add more code for no benefit. For example compare those two:

class User {
    public function __construct(
        public readonly string $id,
        public readonly string $email,
        public readonly DateTime $createdAt
    ) {}
    
    public function __sleep(): array {
        return ['id', 'email', 'createdAt'];
    }
    
    public function __wakeup(): void {
        if (!filter_var($this->email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Invalid email format');
        }
        if (empty($this->id)) {
            throw new InvalidArgumentException('ID cannot be empty');
        }
    }
}

The conversion would be like this:

class User {
    public function __construct(
        public readonly string $id,
        public readonly string $email,
        public readonly DateTime $createdAt
    ) {}
    
    public function __serialize(): array {
        return [
            'id' => $this->id,
            'email' => $this->email, 
            'createdAt' => $this->createdAt,
        ];
    }
    
    public function __unserialize(array $data): void {
        if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Invalid email format');
        }
        if (empty($data['id'])) {
            throw new InvalidArgumentException('ID cannot be empty');
        }
        $this->id = $data['id'];
        $this->email = $data['email'];
        $this->createdAt = $data['createdAt'];
    }
}

Now imagine you have class with 50 properties. Why do we need to force users to add this extra code?

On top of that this is also slower in terms of perf. I did a little bit of testing and just with 3 props, wake up was most of the time faster so with 50+ props, it might be even visible but probably not something major to worry about.

The way how I see it is that __unserialize is not a direct replacement for __wakeup but more a method that can be used if different mapping should be used. But it doesn't seem like the use case that everyone has so for many users it might just mean that

@Girgias Have you actually considered all of this before proposing the deprecation? It passed by closest possible margin (1vote) and I guess some of the voters might have not been aware what's required (honestly I haven't realised it until now and I have actually worked with it in past - I voted no because I thought it does not harm to keep it but in reality it is actually a burden to do the migration and it results in worse code). I think if the RFC showed some examples of needed migration and also mentioned potential perf impact, it wouldn't pass.

@bukka
Copy link
Member

bukka commented Aug 10, 2025

Btw the examples are a bit stripped off as above wouldn't need to use __sleep and __serialze (there should be some extra props added but it's just to show a minimal conversion without extra props in the class.

@perkie01

This comment was marked as off-topic.

@TimWolla
Copy link
Member

This might actually suck for users a bit because in this case we basically tell them to add more code for no benefit.

I expect the majority of implementations to be just throwing an exception to prevent serialization.

Why do we need to force users to add this extra code?

One good reason against __sleep() is that it's impossible for it to correctly handle inheritance, since private properties cannot be serialized with it. This is also documented:

It is not possible for __sleep() to return names of private properties in parent classes. Doing this will result in an E_NOTICE level error. Use __serialize() instead.

As for __serialize(), the minimal implementation that preserves behavior can just be:

	public function __serialize() {
		return get_mangled_object_vars($this);
	}

Possibly combined with unset() to drop individual properties. __unserialize() indeed is a little more complicated when inheritance gets involved (due to private properties), but that is also something that can be dealt with in a generic fashion, possibly using something like this:

	public function __serialize() {
		return [parent::__serialize(), [
			'my' => 'stuff',
		]];
	}
	public function __unserialize(array $data) {
		[$parentData, $myData] = $data;
		parent::__unserialize($parentData);
		foreach ($myData as $key => $val) {
			$this->$key = $val;
		}
	}

But how exactly it will need to looks greatly depends on whether the parent class has a serialization hook.

Copy link
Member

@TimWolla TimWolla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation LGTM.

@@ -37,6 +37,7 @@ $obj = $reflector->newLazyProxy(function ($obj) {
test('Proxy', $obj);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preexisting issue: This test is missing the closing PHP tag (or rather: All of the lazy object tests appear to be). /cc @arnaud-lb

@TimWolla TimWolla requested a review from a team August 12, 2025 17:10
@TimWolla
Copy link
Member

But how exactly it will need to looks greatly depends on whether the parent class has a serialization hook.

It could make sense to automatically implement a __serialize() and __unserialize() stub on all classes, which would allow to unconditionally call parent::__serialize() and parent::__unserialize() in all cases, which would make dealing with inheritance easier. I was also thinking about doing something like this for __destruct() (the correct handling of which is a mess currently).

@bukka
Copy link
Member

bukka commented Aug 12, 2025

My real problem with this is that the RFC just made it sound like it's a stright forward replacement but it's not. It didn't even give any example of it. I guess those are valid points but none of it was in the RFC because there were tons of other changes and no space for discussion. So people just made decision based on couple of notes that made it sound like a no brainer and even then it passed by the closest possible margin. That says everything. I'm pretty sure if all those points were there, it wouldn't pass. But it's what it is. Feel free to merge it.

@bukka
Copy link
Member

bukka commented Aug 12, 2025

That said it's sort of my fault as I didn't raise those points earlier though. It's too late now unfortunately.

@TimWolla
Copy link
Member

For reference, there is a discussion on the mailing list, so please keep it on the list going forward: https://news-web.php.net/php.internals/128456

@nicolas-grekas
Copy link
Contributor

Given the discussion on internals, the PR should likely be put on hold.
I suggest updating the documentation instead, so that sleep/wakeup aren't displayed first anymore on https://www.php.net/language.oop5.magic and we display notices about this mechanism being soft-deprecated?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants