unserialize

Rebuilds a PHP value from a string produced by serialize().

Signature
unserialize(string $data, array $options = []): mixed
  • Added in PHP 4.0

Parameters

ParameterTypeDescription
$datastringThe serialized string, as produced by serialize().
$optionsoptionalarray= []Options array. allowed_classes accepts true (any class), false (no classes), or an array of permitted class names. max_depth sets the maximum nesting depth permitted, default 4096, 0 disables the limit.

Return value

mixedThe converted value: bool, int, float, string, array or object. Returns false on failure.

Errors

Emits E_WARNING when the string is malformed or has trailing data. Throws TypeError or ValueError (PHP 8.4+) when allowed_classes has an invalid type. Objects being restored may throw from their __unserialize() or __wakeup() handlers.

Changelog

  • PHP 8.4.0Throws TypeError or ValueError if allowed_classes is neither an array nor a bool; previously it emitted a notice and behaved as true.
  • PHP 8.4.0Unserializing strings that use the uppercase "S" tag is deprecated.
  • PHP 8.3.0Emits E_WARNING when the input string has trailing unconsumed data.
  • PHP 8.3.0Emits E_WARNING instead of E_NOTICE when the string cannot be unserialized.
  • PHP 7.4.0Added the max_depth option.

Never feed it user input

This is the single most important thing on the page. Unserializing an attacker-controlled string lets them choose which classes get instantiated and which magic methods (__wakeup(), __destruct(), __toString()) run — the classic PHP object-injection chain that ends in remote code execution. allowed_classes => false reduces the blast radius but does not make the call safe. For anything crossing a trust boundary — cookies, request bodies, queue messages from another system — use json_decode(), or sign the payload with hash_hmac() and verify the signature before you unserialize.

Where it still belongs

Data your own process wrote and controls: a cache entry in APCu or Redis, a session payload, a column you serialized yourself. serialize() round-trips PHP types that JSON cannot — object identity, non-string array keys, floats, and shared references inside a structure — so it stays useful in trusted, internal storage.

The false problem

unserialize() returns false both when it fails and when the payload legitimately contains false. Compare the input against serialize(false) to tell the two cases apart, rather than treating every false as an error.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Read a cached array or DTO back out of Redis or APCu after storing it with serialize().
  • Restore a legacy database column that holds serialized data, using allowed_classes => false so only arrays and scalars come back.
  • Rebuild a queued job payload written by the same application, after verifying an HMAC signature on the string.

Frequently asked questions

Is unserialize safe to use on user input?

No. Unserializing untrusted data allows object injection attacks that can lead to remote code execution, and allowed_classes => false only narrows the attack surface rather than closing it. Use json_decode() for anything that crosses a trust boundary.

Why does unserialize return false?

Either the string is malformed or truncated, or the serialized value genuinely was false. Compare your input to serialize(false) — the string "b:0;" — to tell which case you are in.

What is __PHP_Incomplete_Class and why did I get one?

PHP produces it when the serialized data names a class that is not loaded or not permitted by allowed_classes. The property values are preserved, but you cannot call methods on it — load the class before unserializing, or add it to the allowed_classes array.

More examples

Telling failure apart from a stored false

Both calls return false. The comparison against serialize(false) — the literal string "b:0;" — is what separates a genuine stored false (printed as bool(false)) from a malformed string (reported as a failure).

Example · php
<?php
$stored = serialize(false);
$broken = 'this is not serialized data';

foreach ([$stored, $broken] as $input) {
    $value = @unserialize($input);
    if ($value === false && $input !== serialize(false)) {
        echo "failed to unserialize\n";
    } else {
        var_dump($value);
    }
}

Allowlisting specific classes

Point is on the allowlist, so it comes back as a real Point. ArrayObject is not, so it is replaced by __PHP_Incomplete_Class — the data is preserved but no ArrayObject code ever runs. Anything not allowlisted degrades this way rather than throwing.

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

$blob = serialize([new Point(1, 2), new ArrayObject(['a'])]);

$out = @unserialize($blob, ['allowed_classes' => ['Point']]);
echo get_class($out[0]), "\n";
echo get_class($out[1]), "\n";

Capping nesting depth

The 21-level nested array unserializes fine at max_depth 50 and returns false at max_depth 5. Setting max_depth is a cheap defence against a hostile payload that nests thousands of levels deep to exhaust the stack.

Example · php
<?php
$deep = [1];
for ($i = 0; $i < 20; $i++) {
    $deep = [$deep];
}
$blob = serialize($deep);

var_dump(is_array(unserialize($blob, ['max_depth' => 50])));
var_dump(@unserialize($blob, ['max_depth' => 5]));

Discussion

  • Be the first to comment on this lesson.