In PHP, how do constant() and defined() resolve a constant name built at runtime inside a namespace, and what happens when it is missing?
answer
- string names are fully qualified
- no current-namespace prefix, no fallback
- constant() throws Error since 8.0
- defined() returns bool, never throws
- __NAMESPACE__ builds the full name
basics
~20 sconstant() and defined() treat their string argument as a fully qualified name: the current namespace is not added and there is no fallback. constant() throws Error for a missing constant since PHP 8.0; defined() just returns false.
solid answer
~40 sBoth functions take the constant's name as a **string**, and PHP treats any string name as **fully qualified**. So inside `namespace App\Payroll`, `defined('TAX_RATE')` asks about the global `TAX_RATE`, not `App\Payroll\TAX_RATE`, and there is no fallback between the two, unlike a bare `TAX_RATE` in code. Build the full name with `__NAMESPACE__ . '\\TAX_RATE'`. `defined()` returns `true` or `false` and never throws. `constant()` returns the value, and since PHP 8.0 throws an `Error` ("Undefined constant ...") when the name does not exist; before 8.0 it returned `null` with a warning. `constant()` also reads class constants and enum cases with a `'Class::NAME'` string. The typical use is picking a constant by a runtime key, such as an API version from a request header.
code
php · 17 lines<?php
declare(strict_types=1);
namespace App\Payroll;
const API_V2 = '/api/v2/payslips';
const TAX_RATE = 0.2;
var_dump(\defined('TAX_RATE')); // bool(false): global name
var_dump(\constant(__NAMESPACE__ . '\TAX_RATE')); // float(0.2)
$requested = 'V9'; // e.g. from a request header, already whitelisted
try {
echo \constant(__NAMESPACE__ . '\API_' . $requested);
} catch (\Error $e) {
echo $e->getMessage(); // Undefined constant "App\Payroll\API_V9"
}go deeper
Know that defined() returns a bool and constant() returns the value of a constant whose name is in a string.
Explain that string names are fully qualified with no namespace fallback, and that constant() throws Error on a missing name since PHP 8.0.
Review dynamic lookups for the namespace trap and for unvalidated input, and replace pre-8.0 null checks around constant() with defined() guards.
Decide when dynamic constant lookup is justified at all versus an explicit map or match, keeping the set of reachable names auditable.
## Why a dynamic lookup is needed Normally you read a constant by writing its name: `echo API_VERSION;`. Sometimes the name is only known at runtime: a payroll module might select `API_V2` or `API_V3` from a request header, or check whether an optional feature flag constant was set by a bootstrap file. PHP provides two functions for that: - `defined(string $constant_name): bool` reports whether a constant with that name exists. - `constant(string $name): mixed` returns the value of the named constant. Both take the name as a **string**, and that is where most of the surprises come from. ## String names are always fully qualified When you write a bare constant name in namespaced code, the compiler resolves it: `TAX_RATE` inside `namespace App\Payroll` means `App\Payroll\TAX_RATE`, and if that does not exist PHP **falls back** to a global `TAX_RATE`. A string passed to `constant()` or `defined()` goes through none of that: | Code inside `namespace App\Payroll` | Looks for | |---|---| | `TAX_RATE` (bare) | `App\Payroll\TAX_RATE`, then global `TAX_RATE` | | `defined('TAX_RATE')` | global `TAX_RATE` only | | `defined('App\Payroll\TAX_RATE')` | `App\Payroll\TAX_RATE` only | | `constant(__NAMESPACE__ . '\\TAX_RATE')` | `App\Payroll\TAX_RATE` only | The rule follows the manual's section on namespaces and dynamic language features: in a dynamic name there is no difference between qualified and fully qualified, so a leading backslash is optional and the current namespace is never prepended. php-src has a test named for exactly this: `defined()` performs no namespace fallback. Also note that the namespace part of the name is matched case-insensitively while the constant's own name is case-sensitive. In double-quoted strings the backslash needs escaping (`"App\\Payroll\\TAX_RATE"`); single quotes are simpler. ## What each function does when the constant is missing - **`defined()`** returns `false`. It never throws for an unknown name, which makes it the guard of choice. - **`constant()`** throws an **`Error`** with the message `Undefined constant "App\Payroll\API_V9"` since **PHP 8.0**. Before 8.0 it emitted a warning and returned `null`, so old code that checked `constant($name) === null` silently stops working and now needs a `try`/`catch` or a `defined()` check first. ## Class constants and enum cases `constant()` also understands `'Class::NAME'` strings, so it can read a class constant or an enum case whose name is dynamic, for example `constant(PayrollApi::class . '::' . $version)`. `defined()` accepts the same form. How class constants and enums are declared belongs to the OOP topics; the point here is that these two functions are the dynamic door to them as well. (`define()` refuses such names: it throws a `ValueError`, since class constants cannot be created at runtime.) ## A safe pattern for runtime selection 1. Build the **fully qualified** name, from `__NAMESPACE__` or `SomeClass::class`, never from the bare short name. 2. **Whitelist** the runtime part first (for example, match it against `'v2'` or `'v3'`), so user input cannot probe arbitrary constants. 3. Check with `defined()`, or call `constant()` inside a `try` that catches `\Error`. 4. Prefer a `match` expression or an array map when the set of choices is small and known; dynamic constant lookup is best kept for genuinely open sets. ## Listing constants instead of guessing names When you do not know the names in advance, `get_defined_constants(bool $categorize = false): array` returns every constant as a name-to-value array. With `true` it groups them by the extension that registered them, and constants created by your own code sit under the `'user'` key. That is handy for debugging a bootstrap ("which payroll constants actually got defined on this entry point?"), but it is a diagnostic tool; production code should know which names it reads. ## Typical interview traps - Assuming `defined('TAX_RATE')` inside a namespace checks the namespaced constant. - Expecting `constant()` to return `null` for an unknown name on PHP 8. - Passing a magic constant name: `defined('__DIR__')` is `false`, because magic constants are compiler substitutions, not registered constants.
- Inside namespace App\Payroll, a bare TAX_RATE works but constant('TAX_RATE') throws. Why?The bare name is resolved by the compiler: it tries `App\Payroll\TAX_RATE` and falls back to a global `TAX_RATE`. The string passed to `constant()` is treated as fully qualified, so it only looks for global `TAX_RATE`. If only the namespaced constant exists, the lookup fails with an `Error`. Use `constant(__NAMESPACE__ . '\\TAX_RATE')`.
- How can constant() read an enum case whose name comes from configuration?Pass a `'Class::NAME'` string: `constant(PayFrequency::class . '::' . $name)` returns the enum case object, and throws an `Error` if that case does not exist. Validate `$name` against the allowed set first, or use the enum's own `tryFrom()` if it is a backed enum and you have its value rather than its case name.
saying these in an interview costs you the question
- Believes defined('TAX_RATE') inside a namespace checks the namespaced constant first.
- Expects constant() to return null for an unknown name on PHP 8.
- Says defined() throws an Error when the constant does not exist.
- Thinks a leading backslash is required in names passed to constant().
- Passes raw user input straight into constant() without a whitelist.