skip to content

In PHP, what do compact() and extract() do, and why do most code reviews reject extract() in application code?

level: seniorimportance: should knowfreq 30%

answer

  1. names to keys, keys to names
  2. compact warns on undefined names
  3. extract default: EXTR_OVERWRITE
  4. extract returns a count
  5. variables nobody can see declared

basics

~20 s

compact('lat', 'lng') builds ['lat' => $lat, 'lng' => $lng] from variable names; extract($array) does the reverse, creating a variable per key and by default overwriting existing ones. Reviews reject extract() because data decides which variables exist.

solid answer

~40 s

`compact()` takes variable names, as strings or arrays of strings, and returns an array keyed by those names; since PHP 8.0 a name with no variable behind it raises `E_WARNING` "Undefined variable". `extract()` goes the other way: each key of an associative array becomes a local variable, and it returns the number of variables it imported. Its default flag is `EXTR_OVERWRITE`, so an array key like `lat` silently replaces an existing `$lat`; `EXTR_SKIP`, `EXTR_PREFIX_ALL` and `EXTR_IF_EXISTS` change that. Reviewers reject `extract()` because the reader, the IDE and static analysis cannot tell which variables it creates, and a changed payload can overwrite locals. A keyed destructuring pattern, `['lat' => $lat, 'lng' => $lng] = $point;`, does the same job explicitly.

code

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

$name = 'Ridge Loop';
$lat  = 46.5580;
$lng  = 7.9812;
$summary = compact('name', 'lat', 'lng');
// ['name' => 'Ridge Loop', 'lat' => 46.558, 'lng' => 7.9812]

$payload = ['lat' => 0.0, 'lng' => 0.0, 'name' => 'Injected'];
$count = extract($payload);   // EXTR_OVERWRITE by default
echo "$count $name", PHP_EOL; // 3 Injected - $name was replaced

// Explicit alternative: only the named fields, nothing overwritten by surprise
['lat' => $startLat, 'lng' => $startLng] = $payload;

go deeper

for a junior

Recall that compact() turns variable names into an array and extract() turns array keys into variables.

for a middle

Explain extract()'s default EXTR_OVERWRITE, the other collision flags, its integer return value, and compact()'s warning on undefined names since PHP 8.0.

for a senior

Reject extract() in review for invisible declarations and silent overwrites, accept it only in a tightly scoped template renderer, and replace it with keyed destructuring.

for a principal

Encode the rule in tooling: a static-analysis or coding-standard check that bans extract() outside an allow-listed renderer keeps the discussion out of every review.

## Two functions that cross the variable/array boundary PHP keeps a function's local variables in a **symbol table**, a map from variable names to values. `compact()` and `extract()` convert between that table and an ordinary array: | Function | Direction | Signature | Returns | |---|---|---|---| | `compact()` | variables → array | `compact($var_name, ...$var_names)` | an array keyed by the names | | `extract()` | array → variables | `extract(array &$array, int $flags = EXTR_OVERWRITE, string $prefix = "")` | the number of variables imported | ## compact() `compact('name', 'lat', 'lng')` returns `['name' => $name, 'lat' => $lat, 'lng' => $lng]`. Arguments can be strings or arrays of strings, nested arrays included. A name without a variable behind it is reported: - since PHP 8.0 with `E_WARNING` "Undefined variable $x"; - in PHP 7.3 and 7.4 with `E_NOTICE`; - before 7.3 it was silently skipped. `compact()` is mostly harmless: its inputs are literal names in the code, so a reader can see what goes into the array. Its main cost is that renaming a variable does not update the string, and tools treat the string as data. Many teams still prefer the literal `['name' => $name, 'lat' => $lat]`, which refactoring tools and static analysis understand. ## extract() `extract($point)` creates one local variable per key of `$point`. The details matter: 1. **Default is `EXTR_OVERWRITE`.** A key that matches an existing variable replaces its value without any warning. 2. **Other flags** change collisions: `EXTR_SKIP` keeps the existing variable, `EXTR_PREFIX_SAME` and `EXTR_PREFIX_ALL` add a prefix joined with an underscore, `EXTR_IF_EXISTS` only overwrites variables that already exist, and `EXTR_REFS` imports references to the array's elements. 3. **Invalid names are skipped.** Integer keys and keys that are not valid variable names are not imported unless a prefix flag makes them valid; the key `GLOBALS` is skipped, and a key `this` throws an `Error` ("Cannot re-assign $this"). 4. **It returns an `int`**, the count of variables imported, not the variables themselves. ## Why reviewers reject extract() - **Invisible declarations.** After `extract($payload)`, which variables exist depends on runtime data. A reader cannot find where `$lat` was defined, and IDEs and static analysers cannot either, so they report undefined variables or give up. - **Silent overwrites.** With the default flag, an unexpected key replaces a local such as `$userId` or `$isAdmin`. When the array comes from outside, that turns into a security problem, which the manual warns about explicitly; the injection side is a topic of its own. - **Fragile refactoring.** Renaming a key in an API response silently removes a variable in the consumer, and the failure appears later as an undefined-variable warning. The usual exception is a small template renderer that calls `extract($data)` inside a dedicated function scope just before including a template file, so the template can use `$title` instead of `$data['title']`. Even there, a fixed, trusted array and `EXTR_SKIP` are common guards. ## A review checklist When either function appears in a diff, reviewers typically ask: - **Where does the array come from?** A literal in the same function is low risk; a request, a decoded payload or a database row is not. - **Which flag is used?** No flag means `EXTR_OVERWRITE`, the riskiest option. - **How wide is the scope?** `extract()` inside a two-line renderer function is contained; inside a long controller method it can shadow many variables. - **Could a literal array or a keyed pattern do the same job?** In application code the answer is almost always yes. ## The explicit alternatives | Instead of | Write | |---|---| | `extract($point);` | `['lat' => $lat, 'lng' => $lng] = $point;` | | `extract($point, EXTR_IF_EXISTS);` | a keyed pattern plus `??` defaults for optional fields | | `compact('lat', 'lng')` | `['lat' => $lat, 'lng' => $lng]` | The keyed destructuring pattern names every variable it creates, fails per missing field with a warning, and is fully visible to tooling, so it replaces nearly every legitimate use of `extract()` in application code.

  • What does EXTR_IF_EXISTS change about extract()?
    It imports only keys whose variables already exist in the current scope and ignores every other key. That turns extract() into an allow-list driven by previously declared variables, which limits surprise variables, but existing ones are still overwritten and the code stays harder to follow than a keyed pattern.
  • Why do static analysers struggle with code after extract()?
    The variables extract() creates depend on the array's keys at runtime, which the analyser cannot know in general. It sees reads of variables that were never assigned in the source, so it either reports them as undefined or has to be told to ignore them, losing type information either way.

saying these in an interview costs you the question

  • extract() never overwrites variables that already exist
  • extract() returns the array of variables it created
  • compact() silently skips undefined names in PHP 8
  • extract() is safe on request data because it skips unknown keys
  • compact() copies the variables by reference