Table of Contents

PHP RFC: Allow __sleep() to return null or no value to delegate to default serialization

Introduction

The __sleep() and __wakeup() magic methods have been part of PHP’s object serialization model since early versions. However, they have long served multiple unrelated responsibilities — from filtering properties and forbidding serialization to preparing data before serialization.

As PHP evolves toward a more explicit and attribute-driven model of serialization (see #[NoSerialize] and #[NotSerializable] RFCs), this RFC proposes to make __sleep() more specialized — responsible only for pre-serialization preparation — by allowing it to return null or no value (void). This signals the engine to perform default serialization automatically after executing __sleep().

This effectively separates “serialization rules” (handled via attributes) from “serialization lifecycle hooks” (handled via __sleep()), simplifying class design and improving readability.

Motivation

In the ongoing effort to modernize and simplify PHP’s object serialization model, there is a clear intention to gradually reduce the overlapping responsibilities of __sleep().

Historically, __sleep() has served two unrelated purposes:

This RFC explicitly limits __sleep() to the first purpose only — executing preparation or cleanup logic before serialization — and removes its responsibility for determining which data members are included in serialization.

The second responsibility (controlling which properties or classes may be serialized) is intended to be handled by future declarative mechanisms — namely the proposed attributes #[NoSerialize] and #[NotSerializable] (currently RFCs under discussion).

By allowing __sleep() to return null or no value, this proposal ensures that __sleep() remains a valid and useful lifecycle hook, focused solely on preparation rather than on serialization control. This narrows its semantics, aligns it with modern PHP design goals, and provides a clear migration path for future versions of the language.

Use Cases and Modern Equivalents

This RFC formalizes a clear division of responsibility:

1. Avoiding class serialization

Previously:

class A {
    function __sleep() {}
}

Can be replaced with:

#[NoSerialize]
class A {}

(see https://wiki.php.net/rfc/no_serialize_attribute)

2. Forbidding serialization

Previously:

class A {
    function __sleep() {
        throw new Exception('Unable to serialize');
    }
}

Can be replaced with:

#[NotSerializable]
class A {}

(see https://wiki.php.net/rfc/not_serializable_attribute)

3. Excluding specific properties from serialization

Previously, developers had to manually list serializable properties:

class A {
    public $a;
    public $b;
    private $p;
 
    function __sleep() {
        return ['a'];
    }
}
 
class B extends A {
    public $c;
    function __sleep() {
        return array_merge(parent::__sleep(), ['c']);
    }
}

This approach introduces structural fragility, as private properties declared in parent classes cannot be directly accessed or included in the serialization process by a child class’s implementation of __sleep().

It can be expressed declaratively:

class A {
    public $a;
    #[NoSerialize]
    public $b;
    private $p;
}
 
class B extends A {
    public $c;
}

The #[NoSerialize] attribute clearly defines serialization behavior without the need for manual arrays or merges.

4. Preparing data before serialization (the main use case of this RFC)

Previously:

class A {
    public $a;
    function __sleep() {
        $this->cleanup();
        return ['a'];
    }
}

With this RFC:

class A {
    public $a;
    function __sleep(): void {
        $this->cleanup();
        // no return → engine performs default serialization
    }
}

This is the intended specialization: __sleep() acts purely as a hook for pre-serialization preparation.

Proposal

This RFC updates __sleep() semantics as follows:

This ensures that invalid return types do not interrupt serialization, while still providing diagnostic feedback to the developer.

Default serialization means:

Examples

Example 1: Default serialization after side effects

class UserSession {
    public string $id;
    public Cache $cache;
 
    public function __sleep(): void {
        $this->cache->flush();
        // Returning nothing → default serialization
    }
}

Example 2: Using with #[NoSerialize]

class Example {
    public string $name;
    #[NoSerialize]
    private Logger $log;
 
    public function __sleep(): ?array {
        $this->prepare();
        return null; // equivalent to returning nothing
    }
}

Example 3: For classes that should not be serialized

Classes marked with #[NotSerializable] completely forbid serialization — no data processing, cleanup, or side effects from __sleep() will occur.

This attribute acts as a strict, declarative replacement for the old pattern of throwing an exception inside __sleep().

Previously:

class A {
    function __sleep() {
        // This code never reaches serialization but still runs before throwing.
        $this->cleanup();
        throw new Exception('Unable to serialize');
    }
}

With the attribute-based approach:

#[NotSerializable]
class A {
    private Socket $connection;
 
    function __sleep(): void {
        // This method will never be called.
        // The engine blocks serialization before executing user code.
    }
}

Implementation Details

When __sleep() is invoked during serialize():

1. The engine executes the method.

2. If the return value is:

The updated warning message:

"%s::__sleep() must return an array of property names, or return null/void to delegate to default serialization"

This replaces the older message:

"%s::__sleep() should return an array only containing the names of instance-variables to serialize"

Backward Compatibility

Low impact.

Previously:

With this RFC:

To preserve the old behavior (where objects effectively serialized as NULL), developers should explicitly use the #[NoSerialize] attribute (proposed separately).

This change reduces silent data loss and makes __sleep() behavior more consistent without meaningfully breaking existing code.

Rationale

Proposed PHP Version(s)

Next minor or major version (PHP 8.6 / PHP 9.0)

RFC Impact

On the Engine

Minor changes in serialization path.

On Tools and IDEs

Add support for nullable array return types from __sleep().

On Extensions and SAPIs

None.

Future Scope

Voting Choices

Allow __sleep() to return null or no value (void) to delegate to default serialization?
Real name Yes No Abstain
Final result: 0 0 0
This poll has been closed.

Patches and Tests

https://github.com/php/php-src/pull/20252

References

Changelog

-