When is PHP's var_export() the right tool for generating PHP code, such as a cached config file, and what are its limits?
answer
- return ' . var_export($x, true) . ';
- objects need __set_state()
- enums and stdClass export cleanly
- circular references become NULL
- write to a temp file, then rename
basics
~20 svar_export($value, true) returns a PHP literal, so a file containing '<?php return ' plus that literal is a cache that include loads directly. It suits arrays, scalars, enums and stdClass; other objects need __set_state(), and cycles export as NULL.
solid answer
~40 s`var_export($value, true)` returns a **PHP literal** for the value, so `file_put_contents($tmp, '<?php return ' . var_export($config, true) . ';')` followed by `rename($tmp, $path)` produces a file that `$config = require $path;` loads directly, with no JSON or YAML parsing, and that an opcode cache can keep compiled. It is exact for scalars and arrays: strings are single-quoted with `'` and `\` escaped, floats keep a fraction (`1.0`), `true`/`false`/`NULL` are literals. `stdClass` exports as `(object) array(...)`, enums as `\Menu::Starter`. Its limits: other objects become `\Class::__set_state(array(...))`, which fails on load unless the class defines a static `__set_state()`; **circular references** export as `NULL` with a warning; **resources** export as `NULL`; closures cannot be exported usefully. Write atomically with `rename()` so a concurrent request never includes a half-written file.
go deeper
Recall that var_export() prints valid PHP code and, with true as the second argument, returns it as a string.
Explain what it emits for scalars, arrays, stdClass, enums and other objects, and why __set_state() is needed.
Design the cache write: generate at deploy time, write to a temp file and rename atomically, keep it out of the document root, and exclude objects that cannot round-trip.
Decide what belongs in a generated PHP cache versus JSON or a real cache service, weighing load speed against portability and the risk of executable artifacts.
## The pattern Many PHP applications merge configuration from several files, environment values and defaults at build or deploy time, then write the result as a PHP file: ```php <?php declare(strict_types=1); function writeCache(string $path, array $config): void { $code = '<?php return ' . var_export($config, true) . ';' . PHP_EOL; $tmp = $path . '.' . bin2hex(random_bytes(4)) . '.tmp'; file_put_contents($tmp, $code, LOCK_EX); rename($tmp, $path); // atomic replacement on the same filesystem } $config = require __DIR__ . '/cache/config.php'; ``` The cached file is an ordinary PHP script that **returns** an array. Loading it is an `include`, not a parse of JSON or YAML, and when an opcode cache is enabled the compiled file stays in memory between requests. Opcode caching is its own topic; the point here is that `var_export()` is what makes the file valid PHP. ## What var_export() emits | Value | Exported as | |---|---| | `true`, `false`, `null` | `true`, `false`, `NULL` | | int | the literal; `PHP_INT_MIN` as an expression, so it is not read back as a float | | float | shortest round-trip form with a fraction kept: `1.0`, `0.1` | | string | single-quoted, with `'` and `\` escaped; NUL bytes as `' . "\0" . '` | | array | `array ( key => value, ... )` | | `stdClass` | `(object) array( ... )` (since PHP 7.3) | | enum case | `\Menu::Starter` | | other object | `\Dish::__set_state(array( ... ))`, with a leading backslash since PHP 8.2 | | resource | `NULL` | Because strings are escaped, exporting untrusted strings does not let them break out into code. The generated file is still code: it must be written to a directory the web server cannot be tricked into writing arbitrary content to. ## The limits 1. **Objects need `__set_state()`.** For a class other than `stdClass` or an enum, the output calls `Class::__set_state()` with an array of all properties, including private ones. If the class does not define that static method, loading the file throws an `Error`. Value objects meant for caching can implement it; most service objects should not be cached this way at all. 2. **Circular references.** An array or object that contains itself cannot be written as a literal. `var_export()` writes `NULL` in its place and raises the warning "var_export does not handle circular references". `serialize()` handles cycles, at the cost of needing `unserialize()` to load, which has its own security concerns. 3. **Resources and closures.** Resources become `NULL`. A `Closure` is an object without `__set_state()`, so its export fails on load. 4. **Floats and precision.** Floats are written with `serialize_precision` (default `-1`, shortest round-trip), so a value read back is the same float, but decimal fractions such as `0.1` are still binary floats. Money in config should be integers or strings. ## Writing the file safely The race is real on a busy server: one request writes the cache while another includes it. - Write to a **temporary file in the same directory**, then `rename()` over the target. On the same filesystem the rename replaces the file in one step, so readers see the old file or the new one, never half of it. - Generate the cache at **deploy time** when possible, not on the first request. - Keep the cache directory **outside the document root** or deny direct HTTP access to it. ## When to choose something else - For data exchanged with other systems, use JSON: `var_export()` output is only readable by PHP. - For object graphs with cycles or objects without `__set_state()`, use `serialize()`, and never on untrusted input. - For debugging, `var_dump()` shows types more clearly.
- Why write the cache to a temporary file and rename it instead of writing the target directly?Because another request may `include` the file while it is being written and read a truncated script, which is a parse error or a partial config. Writing to a temporary file in the same directory and then calling `rename()` swaps the file in one step on the same filesystem, so readers see either the complete old version or the complete new one.
- A cached config contains an object of your own class; what must the class provide?A static `__set_state(array $properties)` method that builds an instance from the exported property array, which includes private and protected properties by name. Without it, loading the cache throws an `Error` for an undefined method. `stdClass` and enum cases are the exceptions: they export as `(object) array(...)` and `\Class::Case`.
saying these in an interview costs you the question
- var_export() output is safe to load even for objects without __set_state().
- var_export() handles circular references by writing a *RECURSION* marker.
- Writing the cache file in place with file_put_contents() is safe under concurrent requests.
- var_export() output can be read by any language, like JSON.
- Exporting a user-supplied string can inject code into the generated file.