0

Object serialization — serialize(), unserialize(), __sleep, __wakeup, Serializable

Intermediate5 min read·php-07-016
interview

Concept

PHP's serialize() and unserialize() convert objects to and from a portable string representation. This is used for caching, session storage, and data transfer — but comes with significant security considerations.

serialize($value) produces a PHP-specific format string: O:4:"User":2:{s:4:"name";s:5:"Alice";s:3:"age";i:30;}. It includes the class name, property names, and values recursively. Static properties are excluded. Resources cannot be serialized.

unserialize($str, $options): Reconstructs the object from the string. In PHP 7.0+, always pass ['allowed_classes' => ['ClassName', ...]] or ['allowed_classes' => false] to prevent PHP object injection attacks. Without this restriction, unserializing user-supplied data can trigger arbitrary __wakeup/__destruct calls.

__sleep(): Called before serialization. Return an array of property names to include in the serialized form. Use to exclude sensitive data (passwords, tokens) or resource handles.

__wakeup(): Called after unserialization. Use to reinitialize resources that couldn't be serialized (database connections, file handles).

Modern alternative: Serializable interface (deprecated in PHP 8.1) — serialize() method returns a string, unserialize() restores from it. Now replaced by __serialize() / __unserialize() (PHP 7.4+) which work with arrays instead of raw strings — cleaner and more explicit.

Security: Never unserialize() user-supplied data without allowed_classes. PHP object injection vulnerabilities exploit __wakeup or __destruct on unexpected classes.

Code Example

php
<?php
declare(strict_types=1);

class SessionData
{
    private string $password = 'secret'; // should NOT be serialized

    public function __construct(
        private string $username,
        private array  $permissions = [],
    ) {}

    // Exclude password from serialization
    public function __sleep(): array
    {
        return ['username', 'permissions']; // only these properties serialized
    }

    public function __wakeup(): void
    {
        // Re-initialize anything that needed to be excluded
        // e.g., reconnect to a database, reload config
        echo "Woken up: {$this->username}\n";
    }
}

$data       = new SessionData('alice', ['read', 'write']);
$serialized = serialize($data);
echo $serialized;
// O:11:"SessionData":2:{s:8:"username";s:5:"alice";s:11:"permissions";...}

// Safe unserialize with allowed_classes
$restored = unserialize($serialized, ['allowed_classes' => ['SessionData']]);
echo $restored instanceof SessionData ? "OK\n" : "Failed\n";

// Modern alternative: __serialize / __unserialize (PHP 7.4+)
class Token
{
    private string $secret;

    public function __construct(private string $value, string $secret)
    {
        $this->secret = hash_hmac('sha256', $value, $secret);
    }

    public function __serialize(): array
    {
        return ['value' => $this->value]; // don't include the secret
    }

    public function __unserialize(array $data): void
    {
        $this->value = $data['value'];
        $this->secret = ''; // will need to be re-set
    }
}

// Security: block object injection
$malicious = 'O:8:"Anything":0:{}';
$safe = unserialize($malicious, ['allowed_classes' => false]);
// Returns stdClass, NOT an instance of "Anything"

// For cache storage, prefer JSON or igbinary (smaller, faster)
$json = json_encode(['class' => 'SessionData', 'username' => 'alice']);
// But JSON can't represent objects with methods — use for data only