Symbol Table Resolution
debt(d3/e3/b3/t7)
Closest to 'default linter catches the common case' (d3), bordering specialist. Undefined variables and missing class imports surface as compile-time errors instantly for the most common cases, and the detection_hints tools phpstan and psalm catch closure capture and namespace resolution issues; a careful d3 fits the everyday case more than d5.
Closest to 'simple parameterised fix' (e3). quick_fix is mechanical: add missing use imports, list captured variables in use() clauses, or rename shadowed variables. These are small targeted edits within a single file/function, slightly more than a one-line swap when multiple identifiers need re-scoping.
Closest to 'localised tax' (b3). Scope and resolution decisions are mostly contained to the function or namespace where they occur; while applies_to spans web/cli/library, a single mis-scoped variable or import doesn't reshape the whole system — it taxes the component that contains it.
Closest to 'serious trap' (t7). The misconception that resolution is a one-time parser lookup, combined with PHP's function-scoping (no block scope), closures not auto-capturing outer variables, and namespace-relative class resolution, all contradict how a developer coming from block-scoped, auto-capturing languages would expect. The 'obvious' assumption is consistently wrong here.
Also Known As
TL;DR
Explanation
Namespaces add a separate resolution pass: unqualified class names resolve against `use` imports, then the current namespace, with no global fallback - which is why a missing `use` produces 'class not found' even when the class exists globally. Unqualified function and constant names, by contrast, do fall back to the global namespace.
Common Misconception
Why It Matters
Common Mistakes
- Assuming variables have block scope in PHP - they are function-scoped, so a variable declared inside an if block leaks to the rest of the function.
- Forgetting that closures do not capture outer variables automatically in PHP and must list them in a use() clause.
- Omitting a use statement and expecting an unqualified class name to resolve globally - it resolves against the current namespace first.
- Shadowing an outer variable with a same-named inner one and being surprised the outer value is unchanged.
- Relying on case-insensitive function and class names while assuming variable names are also case-insensitive.
Avoid When
- Do not manually reason about complex namespace resolution when a static analyser like PHPStan or Psalm can flag unresolved names automatically.
- Avoid relying on global fallback resolution for classes - it never happens, so always import classes explicitly with use.
When To Use
- Reach for explicit use() capture lists whenever a closure must read or mutate outer variables across PHP's function scope boundary.
- Use namespace imports and fully qualified names deliberately when resolving classes so the compiler binds the intended declaration.
Code Examples
$count++; // $count is undefined here; warns, treats null as 0, returns 1 each call
<?php
namespace App;
use Monolog\Logger;
function makeCounter() {
$count = 0;
// Bind $count by reference so the closure shares the same symbol
return function () use (&$count) {
$count++;
return $count;
};
}
$next = makeCounter();
echo $next(); // 1
echo $next(); // 2 - resolution captured the outer binding
$logger = new Logger('app'); // resolves to Monolog\Logger