Object Serialization
What Is Serialization?
In simple terms: Serialization turns a value — including an object with all its properties — into a string, so it can be stored in a file, a cache, a session, or a queue, and later turned back into the same value. Unserialization is the reverse: it rebuilds the object from the string.
Technically: serialize() converts a PHP value into PHP's own text format, which records types, class names, and property values. unserialize() recreates the value. Classes can control the process with the magic methods __serialize() and __unserialize() (PHP 7.4+). PHP uses serialization internally, for example to store $_SESSION data.
Where Serialization Is Used
| Use | Example |
|---|---|
| Sessions | PHP serializes $_SESSION between requests |
| Caching | Storing computed objects in files, APCu, or Redis |
| Queues | Laravel and Symfony serialize job objects to run them later |
| Snapshots | Saving the state of an object for later restoration |
For exchanging data with other systems (APIs, JavaScript, other languages), use JSON instead — serialization is a PHP-specific format.
serialize() and unserialize()
<?php
class Product
{
public function __construct(public string $name, public int $price) {}
}
$data = serialize(new Product('Pen', 20));
echo $data . "\n";
$restored = unserialize($data);
var_dump($restored instanceof Product);
echo "{$restored->name}: ₹{$restored->price}\n";
Output
O:7:"Product":2:{s:4:"name";s:3:"Pen";s:5:"price";i:20;}
bool(true)
Pen: ₹20
Reading the Format
| Part | Meaning |
|---|---|
O:7:"Product" | An Object of the class with the 7-character name Product |
:2:{...} | Two properties follow |
s:4:"name" | A string of length 4: the property name |
s:3:"Pen" | The value: a string of length 3 |
i:20 | An integer value |
How It Works
serialize()records the class name and every property, including protected and private ones (which are stored with special name prefixes).unserialize()creates an object of the recorded class without calling its constructor and sets the properties directly.- The class must be defined (or autoloadable) when unserializing; otherwise PHP creates an
__PHP_Incomplete_Classplaceholder.
Controlling Serialization: __serialize() and __unserialize()
By default, all properties are serialized exactly as they are. Often that is not ideal: some properties should be excluded (database connections, caches), some should be stored in a simpler form, and stored data may need to survive changes to the class. The two magic methods give full control:
<?php
final class Cart
{
private array $items = [];
private DateTimeImmutable $updatedAt;
public function __construct(DateTimeImmutable $now)
{
$this->updatedAt = $now;
}
public function add(string $sku, int $quantity): void
{
$this->items[$sku] = ($this->items[$sku] ?? 0) + $quantity;
}
public function summary(): string
{
return array_sum($this->items) . ' items, updated ' . $this->updatedAt->format('d M Y H:i');
}
public function __serialize(): array
{
return [
'version' => 2,
'items' => $this->items,
'updated' => $this->updatedAt->format(DATE_ATOM),
];
}
public function __unserialize(array $data): void
{
$this->items = $data['items'] ?? [];
$this->updatedAt = new DateTimeImmutable($data['updated'] ?? 'now');
}
}
$cart = new Cart(new DateTimeImmutable('2026-10-15 10:00', new DateTimeZone('Asia/Kolkata')));
$cart->add('PEN-01', 3);
$cart->add('NB-A5', 2);
$stored = serialize($cart);
echo $stored . "\n";
$restored = unserialize($stored);
echo $restored->summary() . "\n";
Output
O:4:"Cart":3:{s:7:"version";i:2;s:5:"items";a:2:{s:6:"PEN-01";i:3;s:5:"NB-A5";i:2;}s:7:"updated";s:25:"2026-10-15T10:00:00+05:30";}
5 items, updated 15 Oct 2026 10:00
How It Works
__serialize()returns an array describing the object's state. Only this array is stored.- The date is stored as a simple ISO 8601 string instead of a serialized
DateTimeImmutableobject. - A
versionnumber is included, so future versions of the class can recognize and convert older data. __unserialize()receives the same array and rebuilds the object. Using??defaults keeps it working even if older stored data lacks a key.
Older Mechanisms
| Mechanism | Status |
|---|---|
__sleep() / __wakeup() | Older; __sleep() returns property names to keep, __wakeup() restores resources. Superseded by __serialize()/__unserialize(), which take precedence if both exist |
Serializable interface | Deprecated since PHP 8.1 unless the class also implements __serialize()/__unserialize() |
What Cannot Be Serialized
<?php
try {
serialize(fn() => 'hello');
} catch (Exception $e) {
echo $e->getMessage() . "\n";
}
Output
Serialization of 'Closure' is not allowed
| Value | Result |
|---|---|
| Closures | Exception |
| Anonymous class objects | Exception |
| Resources (file handles, sockets) | Cannot be meaningfully restored; stored as an integer |
Objects such as PDO connections | Exception |
| Enums | Supported (stored as the case reference) |
Objects holding such values must exclude them in __serialize() and recreate them after unserialization — for example, reconnecting to the database through a service rather than storing the connection.
Security: Never Unserialize Untrusted Data
unserialize() can create objects of any class available in the application and set their properties to attacker-chosen values. Magic methods such as __wakeup(), __unserialize(), __destruct(), or __toString() of those classes then run with that data. Attackers chain existing classes from frameworks and libraries ("gadget chains") to delete files or even execute code. This is called PHP object injection.
Rules
- Never pass user-controlled data (cookies, form fields, request bodies, URLs) to
unserialize(). - For data from users or other systems, use JSON (
json_encode()/json_decode()), which never creates arbitrary objects. - If you must unserialize data that could be tampered with, restrict the allowed classes and sign the data with an HMAC (see $_COOKIE).
The allowed_classes Option
<?php
class Product
{
public string $name = 'Pen';
}
$data = serialize(new Product());
$safe = unserialize($data, ['allowed_classes' => false]);
echo get_class($safe) . "\n";
$allowed = unserialize($data, ['allowed_classes' => [Product::class]]);
echo get_class($allowed) . "\n";
Output
__PHP_Incomplete_Class
Product
With allowed_classes => false, no real objects are created; any object becomes an inert __PHP_Incomplete_Class. An array of class names permits only those classes. This option reduces the risk, but it is not a substitute for keeping untrusted data away from unserialize().
Serialization vs JSON
serialize() | json_encode() | |
|---|---|---|
| Readable by other languages | No | Yes |
| Preserves PHP classes and types | Yes | No (objects become JSON objects; decoding gives arrays or stdClass) |
| Safe for untrusted input | No | Yes (decoding never creates your classes) |
| Typical use | Sessions, caches, queues — data your application wrote itself | APIs, front ends, storage meant to be shared |
Common Mistakes
Unserializing User Input
Effect: Object injection vulnerabilities.
Fix: Use JSON for anything that comes from outside.
Serializing Resources and Connections
Effect: Exceptions or broken objects after unserializing.
Fix: Exclude them in __serialize() and recreate them afterwards.
Expecting the Constructor to Run
Effect: Validation or initialization in __construct() is skipped on unserialization.
Fix: Validate in __unserialize().
Changing Classes Without Handling Old Data
Effect: Cached or queued objects from older deployments fail to load.
Fix: Version the serialized data and handle missing keys in __unserialize(), or clear caches during deployment.
Best Practices
- Serialize only data your own application created and controls.
- Implement
__serialize()/__unserialize()to store stable, minimal, versioned state. - Never store connections, closures, or other non-serializable values.
- Use JSON for data exchange and untrusted input.
- Restrict
allowed_classeswhenever unserialization cannot be avoided for less trusted data.
Official reference: https://www.php.net/manual/en/language.oop5.serialization.php