skip to content

In PHP, what do the __debugInfo and __set_state magic methods customise, and which built-in functions call them?

level: middleimportance: nice to knowfreq 14%

answer

  1. shape what dumps show
  2. var_dump and print_r, not var_export
  3. returning null deprecated in 8.5
  4. static, rebuilds from exported array
  5. \Klass::__set_state(array(...)) code

basics

~20 s

__debugInfo returns the array var_dump and print_r show for an object, for example to hide a secret. __set_state is a static factory that var_export's output calls when that code is evaluated, rebuilding the object from an array of properties.

solid answer

~40 s

`__debugInfo(): ?array` replaces the property list that `var_dump()`, `print_r()` and `debug_zval_dump()` display, so a settings object can mask its API key or show a computed summary. It does **not** affect `var_export()`, `serialize()` or `json_encode()`, so it is not a guarantee against leaks. Since **PHP 8.5**, returning `null` is deprecated; return `[]` instead, and returning anything else that is not an array is a fatal error. `__set_state(array $properties): object` is a **static** method used with `var_export()`: exporting an object produces PHP code like `\Settings::__set_state(array('timeout' => 30))`, and evaluating or including that code calls the method to rebuild the object. `var_export()` does not check that the method exists, so re-importing without it throws `Error`.

code

php · 23 lines
php
<?php
declare(strict_types=1);

final class Settings
{
    public function __construct(private array $values) {}

    public function __debugInfo(): array
    {
        return ['keys' => array_keys($this->values), 'apiKey' => '***'];
    }

    public static function __set_state(array $properties): static
    {
        return new static($properties['values']);
    }
}

$s = new Settings(['timeout' => 30, 'apiKey' => 'secret']);
var_dump($s);                 // shows keys and a masked apiKey
$code = var_export($s, true); // \Settings::__set_state(array( 'values' => ...))
$copy = eval('return ' . $code . ';');
var_dump($copy == $s);        // bool(true)

go deeper

for a junior

Recall that __debugInfo changes what var_dump shows, and __set_state helps var_export output rebuild objects.

for a middle

Know exactly which functions call each method, the 8.5 null deprecation, and why __set_state must be static.

for a senior

Do not rely on __debugInfo to protect secrets; use __set_state deliberately when caching configuration as PHP code.

for a principal

Decide how secrets are kept out of dumps and logs across a codebase instead of relying on per-class debug hooks.

## Two hooks for developer tooling Both methods serve **developer-facing output** rather than application logic: - `__debugInfo()` controls what a **debug dump** of an object shows. - `__set_state()` lets PHP code generated by `var_export()` **rebuild** an object. ## `__debugInfo` By default, `var_dump($obj)` lists every property, including `private` and `protected` ones. For a settings object holding a database password or an API key, that puts the secret into any dump pasted into a ticket or log. `__debugInfo()` replaces that list: ```php public function __debugInfo(): array { return ['keys' => array_keys($this->values), 'apiKey' => '***']; } ``` Which functions use it: | Function | Uses `__debugInfo` | |---|---| | `var_dump()` | yes | | `print_r()` | yes | | `debug_zval_dump()` | yes | | `var_export()` | no, it exports real properties | | `serialize()`, `json_encode()` | no, they have their own mechanisms | Rules for the method: 1. It takes no parameters and must be public and non-static; a declared return type must be `?array` or narrower. 2. It should return an **array**. Since **PHP 8.5**, returning `null` emits `Deprecated: Returning null from Settings::__debugInfo() is deprecated, return an empty array instead`. 3. Returning any other type (a string, an object) is a fatal error: `__debugInfo() must return an array`. What a good `__debugInfo` returns: - the fields that identify the object (an id, a name, the keys it holds); - masked placeholders for secrets, so readers can see the field exists; - summaries instead of huge payloads, such as a count of 10,000 cached rows rather than the rows; - nothing that needs I/O: a dump must not run a query or an HTTP call. Because other output paths ignore it, `__debugInfo` is a convenience, not a security boundary. Secrets still leak through `var_export()`, serialization or a logger that reads properties directly; keep them out of objects that get logged, or wrap them in a dedicated value type. ## `__set_state` `var_export()` prints a variable as **valid PHP code**. For arrays and scalars that is simple. For an object of a user class, the output is a call: ```php \Settings::__set_state(array( 'values' => array('timeout' => 30), )) ``` The leading backslash makes the class name fully qualified. Evaluating that code (with `include` of a generated file, or `eval`) calls the **static** method `Settings::__set_state(array $properties)`, which must build and return an object from the array. Facts to know: - `__set_state` must be `public static`, take exactly one `array` parameter, and a declared return type must be `object` or narrower (for example `static`). - `var_export()` does **not** check that the method exists. Exporting works; re-importing the code without it throws `Error` for the undefined method. - `stdClass` has no `__set_state`, so `var_export()` writes it as `(object) array(...)`, and an enum case is written as `\Suit::Hearts`. - The array includes private and protected properties by name, so `__set_state` can restore internal state. The classic use is **caching configuration as PHP code**: build a config object once, `var_export()` it into a `.php` file that returns the value, and `include` that file on later requests. Loading PHP code is cheaper than re-parsing YAML or JSON on every request. ## A config cache step by step The `var_export()` plus `__set_state()` round trip is how some applications cache expensive configuration: 1. On deploy or first request, load and validate the raw configuration (YAML, JSON, environment) into a `Settings` object. 2. Write `'<?php return ' . var_export($settings, true) . ';'` to a cache file, ideally through a temporary file and `rename()` so readers never see a half-written file. 3. On later requests, `$settings = require $cacheFile;` evaluates the code, which calls `Settings::__set_state()` and returns a ready object. 4. Invalidate the cache whenever the source configuration changes, usually as part of the deploy. Because the cache is plain PHP, `__set_state` must stay compatible with the exported property names; renaming a private property without regenerating the cache breaks step 3. ## Summary - `__debugInfo` shapes `var_dump`/`print_r` output; return an array, never `null` on PHP 8.5. - It does not hide data from `var_export`, serialization or JSON. - `__set_state` is the static factory that `var_export()` output calls when evaluated.

  • In PHP, does implementing __debugInfo stop a secret property from appearing in var_export() output?
    No. `var_export()` reads the real properties and ignores `__debugInfo`. So do `serialize()` and property-reading loggers. Treat `__debugInfo` as tidying developer dumps, not as a data-protection measure.
  • In PHP, what happens when var_export() output for an object is evaluated but the class has no __set_state?
    `var_export()` produced the code without checking, so evaluating `\Klass::__set_state(array(...))` throws `Error` for the undefined static method. Only `stdClass` and enums get a different export form that needs no method.

saying these in an interview costs you the question

  • Says __debugInfo also controls var_export and json_encode output
  • Treats __debugInfo as a security control against secret leaks
  • Declares __set_state as an instance method
  • Believes var_export refuses to export objects without __set_state
  • Returns null from __debugInfo to hide everything on PHP 8.5