Types7 min readExamples run on PHP 8.5.10

unserialize()

Learn how unserialize() rebuilds PHP values from serialized strings, how allowed_classes limits objects, and why false needs careful handling.

Signature

unserialize(string $data, array $options = []): mixed

Purpose

unserialize() takes a string created by serialize() and turns it back into a PHP value. The value can be a boolean, integer, float, string, array or object. It is useful when you need to restore PHP-specific data that you stored yourself, such as a cached array or an internal object snapshot.

The format is PHP-specific and can describe objects, so it is not a general exchange format. For data that comes from users or other systems, JSON is normally the safer choice.

Signature

Parameters and defaults

  • $data is a single serialized value. If it describes an object, PHP calls __unserialize() or __wakeup() after rebuilding it, when the class defines one.
  • $options is an associative array and defaults to an empty array. Two keys are documented:
    • allowed_classes is an array of class names, true or false. Omitting it behaves like true, so any class may be instantiated. With false, or with a class that is not in the list, objects become __PHP_Incomplete_Class instances. The option does not affect enumerations.
    • max_depth is an integer limiting how deeply structures may nest, to guard against stack overflows. The default is 4096, and 0 disables the limit.

Return value and failure cases

On success you get the rebuilt value. If the string cannot be unserialized, the function returns false and raises E_WARNING. PHP 8.3 changed this from E_NOTICE to E_WARNING. PHP 8.3 also warns when the input has unconsumed data after the first value.

Since PHP 8.4, an allowed_classes value that is neither a bool nor an array of class names throws TypeError and ValueError. Objects can also throw their own exceptions from their unserialization handlers.

PHP 8.4 deprecates the uppercase S string tag in serialized data. Use the lowercase s form.

Example 1: a round trip

The first example serializes an array, prints the stored text, then restores it and prints it as JSON for predictable formatting.

Example
<?php
$data = ['name' => 'Ada', 'tags' => ['php', 'json'], 'active' => true];
$text = serialize($data);
echo $text, "\n";
$copy = unserialize($text);
echo json_encode($copy, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
a:3:{s:4:"name";s:3:"Ada";s:4:"tags";a:2:{i:0;s:3:"php";i:1;s:4:"json";}s:6:"active";b:1;}
{"name":"Ada","tags":["php","json"],"active":true}

Each piece of the text carries a type and often a length, for example s:3:"Ada" for a three-byte string.

Example 2: telling failure from a stored false

Because false is both the error result and a legitimate value, checking the result alone is not enough. One approach is a temporary error handler that records whether a warning occurred.

Example
<?php
function decode(string $text): array {
    $warned = false;
    set_error_handler(function () use (&$warned) {
        $warned = true;
        return true;
    });
    $value = unserialize($text);
    restore_error_handler();
    return ['value' => $value, 'warned' => $warned];
}

echo json_encode(decode(serialize(false))), "\n";
echo json_encode(decode('not serialized')), "\n";
Output
{"value":false,"warned":false}
{"value":false,"warned":true}

Both calls return false, but only the invalid string triggers a warning. You can also compare the input with serialize(false) before decoding.

Example 3: restricting classes

Object instantiation is the risky part of unserialization. The allowed_classes option lets you decide which classes may be created.

Example
<?php
class Point {
    public function __construct(public int $x = 0, public int $y = 0) {}
}

$text = serialize(new Point(1, 2));
echo $text, "\n";
$safe = unserialize($text, ['allowed_classes' => false]);
echo get_class($safe), "\n";
$point = unserialize($text, ['allowed_classes' => ['Point']]);
echo get_class($point), ' ', $point->x + $point->y, "\n";
Output
O:5:"Point":2:{s:1:"x";i:1;s:1:"y";i:2;}
__PHP_Incomplete_Class
Point 3

With false, the data is not turned into a Point; you receive an incomplete-class placeholder instead. With an explicit list, the allowed class is restored normally.

Edge cases

  • A stored false and a failure look the same in the return value, so decide how to distinguish them.
  • Trailing data after a valid value produces a warning as of PHP 8.3.
  • Undefined classes become __PHP_Incomplete_Class unless the unserialize_callback_func directive supplies a way to load them.
  • Deeply nested input can hit max_depth. Lowering it is reasonable if you know your data is shallow.
  • Autoloading can run during object creation, which is one reason class restrictions matter.

Common mistakes

  1. Unserializing untrusted input. The manual warns not to pass untrusted user input to unserialize(), even with allowed_classes. Object instantiation and autoloading can lead to code being loaded and executed. Use json_decode() for data that crosses a trust boundary.
  2. Assuming false always means an error. A serialized false decodes to false without any warning.
  3. Leaving allowed_classes unset. Omitting it is the same as allowing every class.
  4. Passing a wrong option type in PHP 8.4. A value such as a string for allowed_classes now throws instead of merely failing.
  5. Editing serialized strings by hand. String lengths are part of the format, so changing text without updating its length breaks the data.
  6. Skipping integrity checks for stored data. If serialized data lives outside your process, the manual suggests validating it with hash_hmac() so nobody else can modify it.
  • serialize() creates the string that unserialize() reads.
  • json_encode() and json_decode() are the safer, portable alternative.
  • hash_hmac() helps verify that stored data was not tampered with.
  • The magic methods __serialize(), __unserialize() and __wakeup() customize object handling.

Recap

unserialize() restores a value from a string made by serialize(). It returns false with a warning on failure, but false can also be a valid result. Use allowed_classes to limit object creation, max_depth to limit nesting, and never feed it untrusted input. Prefer JSON when data comes from outside your own code.

Tested examples

Round trip an array
<?php
$data = ['name' => 'Ada', 'tags' => ['php', 'json'], 'active' => true];
$text = serialize($data);
echo $text, "\n";
$copy = unserialize($text);
echo json_encode($copy, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
a:3:{s:4:"name";s:3:"Ada";s:4:"tags";a:2:{i:0;s:3:"php";i:1;s:4:"json";}s:6:"active";b:1;}
{"name":"Ada","tags":["php","json"],"active":true}
Distinguish a stored false from failure
<?php
function decode(string $text): array {
    $warned = false;
    set_error_handler(function () use (&$warned) {
        $warned = true;
        return true;
    });
    $value = unserialize($text);
    restore_error_handler();
    return ['value' => $value, 'warned' => $warned];
}

echo json_encode(decode(serialize(false))), "\n";
echo json_encode(decode('not serialized')), "\n";
Output
{"value":false,"warned":false}
{"value":false,"warned":true}
Restrict classes with allowed_classes
<?php
class Point {
    public function __construct(public int $x = 0, public int $y = 0) {}
}

$text = serialize(new Point(1, 2));
echo $text, "\n";
$safe = unserialize($text, ['allowed_classes' => false]);
echo get_class($safe), "\n";
$point = unserialize($text, ['allowed_classes' => ['Point']]);
echo get_class($point), ' ', $point->x + $point->y, "\n";
Output
O:5:"Point":2:{s:1:"x";i:1;s:1:"y";i:2;}
__PHP_Incomplete_Class
Point 3