Serialization
Serialization is the process of turning an in-memory PHP value — including arrays, objects, and their full nested structure — into a single string that can be stored in a file, cached, or sent somewhere, and later reconstructed back into an equivalent value. PHP has its own native serialization format, produced by serialize() and reversed by unserialize(), predating JSON's popularity and still showing up in session storage, caches, and legacy codebases.
serialize() and unserialize(): the basics
serialize() accepts almost any PHP value and returns a string encoding both the value's type and its content. unserialize() takes that string and reconstructs the original value.
Round-tripping an array
<?php $data = ['name' => 'Firoz', 'age' => 30, 'active' => true]; $serialized = serialize($data); echo $serialized, "\n"; $restored = unserialize($serialized); print_r($restored);
a:3:{s:4:"name";s:5:"Firoz";s:3:"age";i:30;s:6:"active";b:1;}
Array
(
[name] => Firoz
[age] => 30
[active] => 1
)Reading the serialized format
The format is dense but readable once its pattern is known: each
value is written as a single letter for its type, followed by
length or value information. a:3:{...} means "an array with 3
entries." s:4:"name" means "a string of 4 characters: name."
i:30 means "an integer, 30." b:1 means "a boolean, true." This
length-prefixing is exactly why serialized strings are fragile to
hand-edit — changing "Firoz" to "Fir" without also updating the
s:5 length prefix in front of it corrupts the whole string and
makes unserialize() fail.
Serializing an object
<?php
class Point
{
public function __construct(
public float $x,
public float $y,
) {
}
}
$point = new Point(3.5, 7.0);
$serialized = serialize($point);
echo $serialized, "\n";
$restored = unserialize($serialized);
echo $restored->x + $restored->y, "\n";O:5:"Point":2:{s:1:"x";d:3.5;s:1:"y";d:7;}Notice the class name Point is embedded directly in the serialized string (the O:5:"Point" prefix). Reconstructing the object requires that exact class to exist and be loaded (autoloaded or already required) at the moment unserialize() runs — this detail becomes important below.
Controlling serialization: magic methods
Sometimes an object shouldn't be serialized exactly as-is — maybe it holds a database connection or file handle that can't survive being turned into a string, or it needs to recompute some derived state after being restored. PHP offers two generations of hooks for this. The older pair, __sleep() and __wakeup(), has been available since early PHP: __sleep() runs before serialization and returns an array of property names to actually include; __wakeup() runs right after unserialize() reconstructs the object, as a chance to re-open any resources or re-validate state.
__sleep and __wakeup
<?php
class DatabaseLogger
{
private $connection;
public string $tableName;
public function __construct(string $tableName)
{
$this->tableName = $tableName;
$this->connection = 'a live connection resource, not serializable';
}
public function __sleep(): array
{
// Only persist tableName; skip the connection entirely
return ['tableName'];
}
public function __wakeup(): void
{
// Re-establish a fresh connection after restoring
$this->connection = 'freshly reconnected';
}
}
$logger = new DatabaseLogger('events');
$restored = unserialize(serialize($logger));
echo $restored->tableName, "\n";events
PHP 7.4 introduced a more capable replacement pair, __serialize() and __unserialize(), which take full control: __serialize() returns an array representing whatever state should be saved (not limited to existing property names), and __unserialize() receives that array back and is responsible for rebuilding the object's state from it. When both generations are defined, PHP prefers the newer pair.
__serialize and __unserialize
<?php
class DatabaseLogger
{
private $connection;
public string $tableName;
public function __construct(string $tableName)
{
$this->tableName = $tableName;
$this->connection = 'a live connection resource';
}
public function __serialize(): array
{
return ['table' => $this->tableName];
}
public function __unserialize(array $data): void
{
$this->tableName = $data['table'];
$this->connection = 'freshly reconnected';
}
}
$restored = unserialize(serialize(new DatabaseLogger('events')));
echo $restored->tableName, "\n";events
Restricting which classes unserialize() may instantiate
<?php // Still risky, but meaningfully safer than the default of allowing any class $restored = unserialize($input, ['allowed_classes' => [Point::class]]); // Safer still: don't use PHP serialization for untrusted data at all $restored = json_decode($input, true, 512, JSON_THROW_ON_ERROR);
When PHP's native format still makes sense
Despite the risk with untrusted input, serialize()/unserialize() remain reasonable choices for data that never leaves your own trusted infrastructure and never crosses a language boundary — for example, caching a computed PHP object in an internal cache layer that only your own application writes to and reads from. JSON is the safer and more portable default for anything else: it's language-agnostic, human-readable, has no notion of "classes" for an attacker to abuse, and is understood natively by virtually every API client and browser.
serialize()encodes a PHP value, including objects, into a single string;unserialize()reverses it.The format embeds type letters and explicit length prefixes, which makes hand-editing a serialized string fragile.
__sleep()/__wakeup()are the older hooks for customizing what gets saved and what happens after restoring.__serialize()/__unserialize()(PHP 7.4+) are the modern, more flexible replacement and take priority when both exist.unserialize()on untrusted input enables PHP Object Injection attacks — treat it as a critical security boundary.Prefer JSON for any data that is untrusted, crosses a language boundary, or leaves your own infrastructure.