In PHP 8.5, how do __serialize() and __unserialize() differ from __sleep() and __wakeup(), and which should a new class implement?
answer
- names only vs an arbitrary array
- __serialize() wins over __sleep()
- constructor never runs on unserialize
- soft-deprecated in 8.5, no warning
- Serializable deprecated alone since 8.1
basics
~20 s__sleep() returns property names to keep and __wakeup() runs after they are restored; __serialize() returns any array and __unserialize() rebuilds the object from it. New classes should use __serialize()/__unserialize(): the older pair is soft-deprecated in PHP 8.5.
solid answer
~30 s`__sleep()` can only return a **list of property names** to serialize, and `__wakeup()` runs with no arguments after PHP has restored those properties itself. `__serialize(): array` returns **any array you design**, with computed values, renamed keys or a version marker, and `__unserialize(array $data): void` receives that array and assigns state itself. When both pairs exist, PHP calls only `__serialize()`/`__unserialize()` and ignores `__sleep()`/`__wakeup()`; `__serialize()` also overrides a `Serializable::serialize()` method. PHP 8.5 **soft-deprecates** `__sleep()`/`__wakeup()` (documentation only, no diagnostic), and since 8.1 implementing `Serializable` without the new pair raises `E_DEPRECATED`. New code implements `__serialize()`/`__unserialize()` only.
code
php · 26 lines<?php
declare(strict_types=1);
final class TariffTable
{
private array $index = []; // derived lookup, never stored
public function __construct(
public readonly string $region,
private readonly array $rates, // band => rate
) {
$this->index = array_flip(array_keys($rates));
}
public function __serialize(): array
{
return ['v' => 2, 'region' => $this->region, 'rates' => $this->rates];
}
public function __unserialize(array $data): void
{
$this->region = $data['region'];
$this->rates = ($data['v'] ?? 1) >= 2 ? $data['rates'] : $data['bands'];
$this->index = array_flip(array_keys($this->rates));
}
}go deeper
Recall the two pairs and that __serialize()/__unserialize() is the one to write today; __sleep() only lists property names.
Explain precedence, that the constructor is skipped, the array contract of __serialize(), and the deprecation status of __sleep()/__wakeup() and Serializable.
Show how a version key and derived-state rebuild in __unserialize() keep cached objects readable across deploys, and how to migrate Serializable classes.
Decide whether domain objects should be serialized at all, or whether stores should hold explicit arrays mapped by factories, and set that as a codebase rule.
## Three generations of hooks PHP lets a class control how `serialize()` and `unserialize()` treat it. Three mechanisms exist, and interviewers ask which one to use and why. | Mechanism | Status in PHP 8.5 | Format tag | |---|---|---| | `__sleep()` / `__wakeup()` | **soft-deprecated** (8.5): documented, no diagnostic | `O:` | | `Serializable` interface | **deprecated** (8.1) unless the class also has the new pair | `C:` | | `__serialize()` / `__unserialize()` (added in 7.4) | the recommended mechanism | `O:` | ## __sleep() and __wakeup() - `__sleep()` runs before serialization and must return an **array of property names**. Only those properties are written. It cannot change a value, add a computed key or rename anything. - Names that do not exist produce a warning (`"x" returned as member variable from __sleep() but does not exist`), and a typed property that is still uninitialized is skipped. - `__wakeup()` runs after unserialization with no arguments; PHP has already copied the stored values into the properties, so the method can only re-establish derived state, such as reopening a connection. The weakness is that the stored shape *is* the property layout. Rename a property and old payloads no longer line up. ## The Serializable interface `Serializable` has two methods, `serialize()` returning a string and `unserialize(string $data)`. The class produces its own opaque string, written as a `C:` entry. It was hard to compose (nested objects needed their own calls) and awkward with references. Since PHP 8.1 a class that implements it **without** also defining `__serialize()`/`__unserialize()` triggers `E_DEPRECATED`: `... implements the Serializable interface, which is deprecated. Implement __serialize() and __unserialize() instead`. Keeping both is only for payloads written by very old PHP versions. ## __serialize() and __unserialize() - `public function __serialize(): array` returns **an arbitrary array** representing the object. Keys need not match property names. Returning anything but an array throws `TypeError` (`... __serialize() must return an array`). - `public function __unserialize(array $data): void` receives that array. PHP creates the object **without calling the constructor**, then calls this method to assign state. - Because the method runs in the class's own scope on a fresh object, it can initialize **readonly** properties, validate the data, and translate old payload shapes. ## Which one wins 1. `__serialize()` is used if present; `__sleep()` and `Serializable::serialize()` are then ignored. 2. `__unserialize()` is used if present; `__wakeup()` is then ignored. 3. Enum cases need none of this: they serialize as `E:` entries and come back as the same case. ## Mistakes seen in reviews - Returning an object or `null` from `__serialize()`: the method must return an array, or `TypeError` is thrown. - Putting a `PDO` handle, a closure or a generator into the returned array: those classes refuse serialization and throw `Exception`. - Assuming constructor validation protects unserialized objects: the constructor never runs, so `__unserialize()` must enforce the same invariants. - Keeping `__wakeup()` next to a new `__unserialize()` and expecting both to run: only `__unserialize()` is called. ## What to write in a new class Implement `__serialize()`/`__unserialize()` and nothing else. Useful habits: - put a **version key** in the array, so `__unserialize()` can read payloads written by an earlier release of the class; - leave out **derived state** (lookup indexes, open handles) and rebuild it in `__unserialize()`; - do not store resources, closures or connections; store what is needed to recreate them. Existing classes that still have `__sleep()`/`__wakeup()` keep working in 8.5 with no runtime notice, so the deprecation will not show up in logs. Find them with a search for the method names and migrate each class when you next touch it: 1. Move the property list from `__sleep()` into the array `__serialize()` returns, as explicit keys. 2. Move the body of `__wakeup()` to the end of `__unserialize()`, after the properties have been assigned from `$data`. 3. Keep reading old payloads: an `O:` entry written through `__sleep()` reaches `__unserialize()` as an array of stored property names, with protected and private names still NUL-mangled, so either translate those keys or version the cache so old entries are never read. 4. Delete `__sleep()`/`__wakeup()` once no stored payload depends on them.
- Does the constructor run when unserialize() rebuilds an object?No. PHP creates the object without calling `__construct()`, applies declared default values, and then either copies the stored properties in or calls `__unserialize()`. Invariants the constructor enforces must therefore be re-checked in `__unserialize()`, and derived state must be rebuilt there.
- How can __unserialize() assign a readonly property?A readonly property may be initialized once from inside the class's own scope. On unserialization the object is fresh and its readonly properties are uninitialized, and `__unserialize()` runs as a method of the class, so its first assignment is a legal initialization.
- What does soft-deprecated mean for __sleep() and __wakeup() in PHP 8.5?The manual and the 8.5 upgrade notes mark them as deprecated in favour of `__serialize()`/`__unserialize()`, but PHP emits no `E_DEPRECATED` when they are used. Existing code keeps working; the signal is to migrate, not a runtime warning.
saying these in an interview costs you the question
- __sleep() can return computed values to store in place of properties
- The constructor runs again when unserialize() rebuilds the object
- When both exist, __wakeup() runs after __unserialize()
- Serializable is the modern replacement for __sleep() and __wakeup()
- PHP 8.5 emits E_DEPRECATED every time __sleep() is called