skip to content

In namespaced PHP code, why do some codebases write \strlen() or use function strlen, and what does it change at compile time?

level: seniorimportance: nice to knowfreq 24%

answer

  1. unqualified call cannot be bound early
  2. two-name runtime lookup per call site
  3. specialised opcodes for strlen, count, is_*
  4. \PHP_EOL substituted at compile time
  5. no shadowing by a namespaced function

basics

~20 s

An unqualified call inside a namespace might hit a namespaced function, so the compiler must defer the lookup. Writing \strlen() or importing it lets the compiler bind the built-in directly and use specialised opcodes for functions such as strlen and count.

solid answer

~40 s

Inside `namespace App;`, an unqualified `strlen($s)` could mean `App\strlen` if such a function exists by the time the line runs, so the compiler emits a generic namespaced call that looks up both names at run time. It therefore cannot apply its **compile-time specialisations**. When the name is fully qualified (`\strlen`) or imported (`use function strlen;`) and the function is a known built-in, PHP 8.5's compiler can replace calls such as `strlen`, `count`, `is_array`, `in_array` or `array_key_exists` with dedicated opcodes, and turn a constant such as `\PHP_EOL` into a literal. The gain is small per call and shows up in hot loops. The same qualification also stops a namespaced function from **shadowing** the built-in, which removes one test-mocking trick.

code

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

namespace App\Text;

use function count;
use const PHP_EOL;

function totalLength(array $lines): int
{
    $total = 0;
    for ($i = 0, $n = count($lines); $i < $n; $i++) {
        $total += \strlen($lines[$i]);   // bound to the built-in at compile time
    }
    return $total;
}

echo totalLength(['ab', 'cde']), PHP_EOL; // 5

go deeper

for a junior

Know that a leading backslash on a function call means the global function and never a namespaced one.

for a middle

Explain why an unqualified call must be resolved at run time inside a namespace, and that use function gives the same compile-time binding as a backslash.

for a senior

Quantify before arguing for it: the gain is per call and matters in hot loops, and check that no tests depend on namespace shadowing before a fixer rewrites calls.

for a principal

Decide the convention once for the codebase and automate it, weighing a small, measurable speed-up against readability and test seams based on shadowing.

## The question behind the style rule Some PHP projects write every built-in call with a leading backslash, `\count($items)`, or list `use function count;` at the top of each file. Code-style tools can enforce either form. Interviewers ask why, and the honest answer has two parts: a small **performance** gain from compile-time binding, and **predictability**, because the call can no longer be redirected by a namespaced function. ## What the compiler does with an unqualified call Inside a namespace, `strlen($s)` is ambiguous at compile time. The file might be compiled before some other file defines `App\strlen`, so the compiler cannot assume the global function. For an unqualified, unimported function name it emits a **namespaced call** carrying both candidate names; at run time the engine: 1. looks for `App\strlen`; 2. falls back to the global `strlen` if the first is missing; 3. caches the result for that call site. Because the target is unknown while compiling, the compiler must use the generic call sequence: set up a call frame, pass arguments, call. ## What changes with \strlen or use function When the name is **fully qualified** or **imported**, the resolved name is final. If it names an internal function known to the compiler, php-src 8.5 can go further: - **Specialised opcodes.** A fixed list of functions is compiled to dedicated instructions or inline sequences instead of a call. In the 8.5 compiler that list includes `strlen`, `count` and `sizeof`, the `is_*` type checks (`is_int`, `is_string`, `is_array`, `is_null` and others), `boolval`, `intval`, `floatval`, `strval`, `defined`, `chr`, `ord`, `in_array`, `array_key_exists`, `array_slice`, `get_class`, `gettype`, `call_user_func`, `call_user_func_array`, `func_get_args`, `func_num_args` and `sprintf`, each under argument conditions. - **Frameless internal calls.** For other suitable built-ins, the compiler can emit a lighter call that skips building a full call frame. - **Direct binding.** Otherwise it still emits a plain call to a known function, skipping the two-name lookup. - **Constant substitution.** `\PHP_EOL`, `\E_ALL` or an imported `use const PHP_EOL;` are persistent built-in constants, so the compiler replaces them with their values. An unqualified `PHP_EOL` inside a namespace resolves first to `App\PHP_EOL` and is fetched at run time instead. `true`, `false` and `null` are substituted either way. These optimisations are skipped when the call uses argument unpacking or named arguments, and when the function has been disabled and could be redeclared. | Call inside `namespace App;` | Resolution | Specialisation possible | |---|---|---| | `strlen($s)` | run time, `App\strlen` then `strlen` | no | | `\strlen($s)` | compile time, global | yes | | `use function strlen;` then `strlen($s)` | compile time, global | yes | | `\my_helper($x)` (user function) | compile time name, runtime lookup | no, not a built-in | ## How much it matters The saving is per call and small: it matters in tight loops over large arrays, in parsers and in library code called millions of times, and very little in a request that spends its time on I/O. Measure before selling it as a performance fix; the more durable argument is usually consistency. ## The shadowing trade-off The runtime fallback has one legitimate use: a test can define `App\time()` or `App\random_int()` in the namespace under test, and every unqualified call in that namespace picks it up. Fully qualifying or importing the call disables that trick. Two consequences follow: - Code relying on such shadowing in tests breaks silently once a fixer adds backslashes. - The resolved function is cached per call site, so a namespaced function defined **after** a call site first ran does not take over that call site for the rest of the request. Teams that want both usually inject a clock or random source rather than rely on shadowing. ## Misconceptions to avoid - The leading backslash does **not** make a call faster by skipping autoloading; functions are never autoloaded. - It does **not** change which function runs in normal code: without a namespaced shadow, both forms call the same built-in. - It does **not** help calls made through variables or `call_user_func()` with a string; those are always resolved at run time. - `use function` and `\name()` compile to the **same** result; the choice is readability. - Constants follow the same pattern as functions, so `\PHP_INT_MAX` and `use const PHP_INT_MAX;` both enable compile-time substitution. ## Choosing a convention - **Fully qualify built-ins** in performance-sensitive libraries, or everywhere for uniformity. - **Import with `use function`** if leading backslashes hurt readability; the compiled result is the same. - **Leave calls unqualified** if the team prefers minimal syntax; correctness is unaffected.

  • In PHP, does writing \my_helper() for your own global function give the same speed-up as \strlen()?
    Only partly. The leading backslash removes the two-name lookup, so the call targets `my_helper` directly. But the opcode specialisations apply only to a fixed list of internal functions the compiler recognises, and a user function defined in another file is still looked up by name at run time on first call.
  • In PHP, why can adding backslashes to built-in calls break a test suite?
    Some tests replace a built-in for code in one namespace by defining a function with the same name there, such as `App\Clock\time()`. That only works because unqualified calls check the namespace before the global space. Once a fixer rewrites the calls as `\time()`, the shadow is never consulted, and the tests see the real clock.

saying these in an interview costs you the question

  • A leading backslash on built-in calls is purely cosmetic
  • Unqualified built-in calls in a namespace are resolved and optimised at compile time
  • use function strlen; is slower than writing \strlen()
  • Qualifying built-ins makes every PHP request noticeably faster
  • The optimisation applies to any function, including user-defined ones