Functions and Constants in Namespaces
An unqualified call to strlen() inside a namespace can represent two entirely different functions in
PHP—and looking at a single isolated file, it is impossible to know which one is intended. Because of this, traditional code
style tools have to guess on dozens of common fixes, and the PHP engine itself cannot take the compiler shortcuts that make such
calls two to three times faster. In most cases, a single line of configuration is all it takes to tell DressCode what exists in
your namespaces: automated fixes immediately stop being risky, and your code can be formatted so the PHP engine executes it along
the fastest possible path.
One Name, Two Meanings
Take a look at the following snippet:
namespace App;
if (is_null($user)) {
// ...
}
Which function is actually being called here? Most developers naturally assume it's the global is_null() that
everyone knows. But PHP resolves this only at runtime: first it checks whether a local App\is_null() function exists,
and only if it doesn't does it fall back to the global one. All it takes is another file anywhere in your project declaring:
namespace App;
function is_null(mixed $value): bool
{
return $value === null || $value === '';
}
and the call above suddenly does something completely different. The file containing the call doesn't matter in the slightest—the only thing that decides behavior is whether that second file happened to be loaded during the request.
Constants behave in the exact same way: PHP_EOL inside the namespace App resolves to
App\PHP_EOL if defined, and to the global PHP_EOL otherwise. Classes do not work this way: an
unqualified Exception inside App always unequivocally means App\Exception, and the global
class must be written as \Exception or imported via use.
A Bit of History
Namespaces arrived in PHP 5.3, and their design was born through painful compromise. The release was originally scheduled for
the fall of 2008, with the first alpha appearing in August, yet core PHP developers were still fiercely debating how namespaces
should actually function well into October. That was also when the historic decision was made to swap the originally considered
:: separator for the backslash \, because double colons could not be reliably distinguished from static
method calls by the parser. PHP 5.3.0 finally shipped on June 30, 2009—nine months behind schedule.
One of the hardest questions was this: what should an identifier mean when it has no namespace prefix? The proposal from
October 30, 2008 chose completely different answers for classes and for functions. Classes were denied a fallback to the global
namespace because, when combined with autoloading, it would have resulted in a catastrophic performance penalty: on every single
new Exception, PHP would have first attempted to load App\Exception. Functions and constants, however,
have no autoloading, so that performance risk did not exist. And because the vast majority of PHP's standard library consists of
global functions, they were granted a silent runtime fallback to the global scope. Code written prior to namespaces could thus
move into a namespace without developers having to prepend a backslash to every strlen() or
in_array().
It was a pragmatic compromise for its era. The enduring cost, however, is that the meaning of an unqualified function call is not a static property of the file, but a dynamic property of the entire running program.
Why Tools Struggle with It
Static analysis and style fixers inspect individual files in isolation, not a running process. Faced with
is_null($x) inside a namespace, a tool encounters the exact same ambiguity you faced above, and it cannot know the
answer with certainty. Yet a whole range of widely desired modernizations assume the call is definitely the built-in PHP
function:
- rewriting
is_null($x)to$x === null, intval($x)to a typecast(int) $x,sizeof($a)tocount($a),dirname(__FILE__)to__DIR__,get_class()toself::class,- adding
use function count;so the compiler can optimize the call.
If the function happened to be your own App\is_null(), every one of those edits would silently alter program
behavior. A responsible tool must therefore classify such transformations as risky fixes and refuse to make them without explicit
permission. PHP CS Fixer flags rules like is_null, no_alias_functions, and dir_constant as
risky for this exact reason: they are unsafe if the function is overridden locally.
And this is far from a theoretical concern. A well-known testing pattern relies entirely on this mechanism: libraries like
php-mock declare App\time() or App\file_get_contents() inside a test suite so that code under test in
the App namespace invokes a mock rather than the real runtime function.
Let's Be Pragmatic
How many custom functions and constants do you actually declare in your namespaces? In almost all codebases, the answer is very
few, and often none at all. Functions in PHP cannot be autoloaded: for a function to be callable, its file must be
explicitly loaded ahead of time via require or a files entry in composer.json. That is why
application logic moved permanently into classes long ago, leaving namespaced functions a rare edge case. Function autoloading has
been proposed for PHP multiple times, but has never become part of the language.
This is also an excellent reason never to rely on unqualified calls for namespaced functions: if the file containing the
function fails to load, PHP raises no error—it silently calls the global function of the same name, if one exists. An explicit
import use function App\is_null; won't autoload the file either, but it guarantees that an uninitialized call fails
immediately and clearly with Call to undefined function App\is_null().
The reality is clear: the theoretical ambiguity affects everyone, but real custom functions in namespaces are exceedingly rare. All you need to do is enumerate them.
Tell DressCode What Is What
In the vast majority of projects, you declare no custom functions or constants in namespaces at all. When that is the case, a single configuration line is all you need:
nameResolution: certain
This single setting tells DressCode that every unqualified function or constant name that a file neither imports nor declares locally is guaranteed to be global. Not “probably,” but with absolute certainty.
If you do declare some custom functions or constants in namespaces, list them under the namespaces key—functions
under functions and constants under constants. Syntax matches standard use statements,
including grouped brackets. Wrap grouped entries in quotes so NEON doesn't parse them as maps:
nameResolution: certain
namespaces:
functions:
- 'App\Utils\{format, parse}'
- App\Legacy\helper
constants:
- 'App\{VERSION, DEBUG}'
DressCode then treats any function in that list as belonging to its namespace wherever it is invoked without an import. For functions, letter case does not matter at all; for constants, case matters only in the constant's own name (not in the namespace path)—matching PHP's native behavior.
The lists and the nameResolution directive represent two separate concepts, which is why each has its own key. The
list is a statement of fact: this function exists. The nameResolution directive is a decision: nothing else
exists.
nameResolution |
what it means |
|---|---|
certain |
The lists are complete; anything not listed is guaranteed to be global. |
uncertain |
The lists may be incomplete; identical to omitting the key. |
Under uncertain, DressCode acts with extreme caution: it assumes an unqualified identifier is global, but treats
any automated fix relying on that assumption as risky. It
reports the fix as a warning with an explanation on the line below the violation (e.g.
the namespace may declare `is_null()`), making the fix only when you approve the decision in fixRisky or
pass --fix-risky. That is why uncertain is the default: placing a rule in your configuration does not
imply blanket consent to alter code behavior. The moment you configure certain, however, these fixes stop
being risky.
Every Rule Benefits
What matters most is where this knowledge is applied. DressCode doesn't pass this setting to individual rules in
isolation; it provides it to the shared NameResolver analysis, which resolves names across the entire tool. Built-in
rules query it, and so can your custom rules. The domain
knowledge lives in one central place, so no rule has to maintain its own ad-hoc whitelist.
Rules query the analysis on two points:
- What an identifier represents. Calling
isGlobalFunctionCall($node, 'is_null')returnstruefor the global function, while recognizing thatApp\is_null()from your list is not global. Undercertain, this answer is definitive; otherwise, it is treated as an assumption. - How certain the resolution is. Calling
getUnqualifiedResolution('is_null', SymbolKind::Function, $node)specifies where the name points: definitively to the global scope (Global), definitively to the namespace (Namespaced), or to the global scope only assuming the namespace doesn't declare it elsewhere (Uncertain). A rule flags its fix as risky exclusively for occurrences markedUncertain—and therefore never in a project configured withcertain.
This works just as reliably in reverse, when a rule writes a function name into code itself. The rule
hygiene.aliasFunctions replaces sizeof() with count() and writes the new name matching the
original formatting: \sizeof() becomes \count(), and bare sizeof() becomes bare
count(). However, if the file imports or declares its own count, or if the configuration declares
count() in that namespace, it emits \count(), ensuring the rewritten code calls the exact same function
as before.
All rules read every identifier with identical consistency, regardless of which rule queries it first.
A New Function Does Not Slip Through
A static list would be a liability if it went stale. That is why DressCode guards the list itself: the moment your
configuration specifies nameResolution: certain, an internal validation check activates under
namespaces.functions and namespaces.constants. If DressCode encounters a newly declared function in a
namespace that is missing from the list, it flags it immediately (doing the same for const and
define('App\X', ...) calls). It even accounts for conditional polyfills wrapped in
if (!function_exists(...)). The moment anyone adds a function like App\Utils\slugify(), running
dresscode check prompts them to update the configuration:
src/Utils/helpers.php error 12:10 Function `App\Utils\slugify()` must be listed in `namespaces.functions`. namespaces.functions
This check is completely reliable because, unlike calls, declarations can be identified statically: they exist directly in the
files DressCode inspects. Just be mindful of excluded paths. If you declare a function in a test directory that DressCode skips,
and production code calls it without an import, add it to the list manually. Under uncertain, this validator stays
silent because there is nothing to enforce. Under certain, not even --only can turn it off: fixes that
rely on complete lists cannot afford to lose the check that keeps them complete.
You don't need to assemble your initial list by hand either. Running dresscode init scans your entire project, discovers all
functions and constants declared in namespaces, and writes their lists alongside nameResolution: certain. If any file
fails to parse (making its declarations unknown), init avoids writing certain and names the unverified
file in a comment.
In future releases, dresscode fix may automatically append newly discovered declarations directly
into the configuration file rather than merely reporting them.
Helper Functions in Tests
Test suites frequently declare helper functions directly inside their test namespace—such as
App\Tests\createUser(). Adding these to your global configuration and maintaining them would be tedious noise. For
test directories, simply add a scoped override stating that lists are
incomplete there:
nameResolution: certain
overrides:
- paths: [tests]
nameResolution: uncertain
DressCode will then inspect files in tests cautiously: fixes requiring global functions will only be reported as
risky warnings, and the unlisted-declaration validator remains silent. Production files in src continue to benefit
fully from complete certainty. This separation works because production code never calls test helpers, meaning those functions
have zero influence on identifier resolution in src.
One exception deserves careful attention: the mocked function pattern discussed above. If a test declares App\time() to force production code in the
App namespace to invoke a mock, that change in behavior targets calls in src, not in the tests. Such a
function definitely belongs in the project's root namespaces.functions list so that rules in src leave
it alone.
Functions Declared by a Framework
Sometimes you write application code directly inside a framework's namespace because the framework's architecture was
designed for it. Symfony, for instance, directs service configurations into files beginning with
namespace Symfony\Component\DependencyInjection\Loader\Configurator;—specifically so that developers can invoke
helper functions like service(), param(), or env() without import statements. Those
functions are declared by Symfony itself inside vendor/, where DressCode does not look.
DressCode understands these framework helpers natively: whenever a project installs symfony/dependency-injection,
it automatically adds those functions to the internal namespaces.functions list without requiring a single line of
configuration, regardless of which coding standard you use. Running dresscode config lists them under the
Names line, attributed to DressCode for symfony/dependency-injection.
Other packages can supply their own namespace lists through the namespaces key in their migration data files, merging seamlessly with your configuration. However,
neither a package nor a preset can set the nameResolution directive: they understand the framework, but they cannot
speak for your own application code.
Faster Calls When the Name Is Certain
Before PHP executes your code, it compiles it into bytecode instructions for its virtual machine (the Zend Engine). For dozens of built-in functions and constants, the compiler can take major shortcuts: replacing a call with a single CPU instruction, computing results ahead of time during compilation, or executing the function without the overhead of call-frame allocation. Let's look at what PHP does under the hood, what commonly prevents these optimizations, and how DressCode formats code so the engine can run at full speed.
What PHP Does with Code Before Running It
PHP never executes raw source code directly as it appears in your editor. First, it compiles it into a sequence of low-level instructions called opcodes, which are subsequently executed by the Zend Engine virtual machine. With OPcache enabled (the standard configuration in production environments), compiled opcodes are stored in shared memory so files do not have to be recompiled on every HTTP request; before saving them, the OPcache Optimizer runs several optimization passes over the bytecode.
A standard userland function call involves significant work for the virtual machine. For example, $len = foo($s);
compiles roughly to:
INIT_FCALL "foo" prepare the call
SEND_VAR $s pass the argument
DO_ICALL execute the function
The virtual machine must allocate a new call frame on the execution stack, copy the arguments into it, and transfer control to
the C function, which must then parse and validate those arguments. For a heavy function doing database work, this overhead is
negligible. For an elementary function like strlen(), which merely reads a string's length directly from its memory
header, the call-frame overhead is many times more expensive than the actual operation itself.
That is why PHP provides compiler shortcuts for a wide range of standard functions. Nearly all of them, however, share one strict prerequisite: the compiler must know with absolute certainty at compile time which function you are calling.
When PHP Knows What You Are Calling
The PHP compiler recognizes roughly thirty core functions and can replace their calls directly with specialized opcodes. Write
\strlen($s) and the entire multi-instruction call sequence collapses into a single line:
T1 = STRLEN $s
No stack frame allocation, no argument passing overhead. Similar direct shortcuts exist for many other functions:
- type checks
is_array(),is_string(),is_int(),is_null(), and otheris_*()functions turn into single opcode type checks, - casting functions
intval(),strval(),boolval(), andfloatval()compile directly to native type casts, - functions
count(),gettype(),get_class(),array_key_exists(),defined(), andfunc_get_args()compile to dedicated native opcodes, - calling
in_array()with an array of strings or numbers written directly as a literal in code is compiled into a lookup against an engine hash table built ahead of time, making execution time practically independent of array size, sprintf()calls from PHP 8.4 onward are transformed into plain string concatenations at compile time, provided the format string uses only%sand%dspecifiers in counts matching the passed arguments,- calling
strlen('hello')orchr(65)with a constant literal is evaluated during compilation, emitting the final result directly.
Why isn't writing strlen($s) without a backslash enough? Because of namespaces. In a file declaring
namespace App;, an unqualified call to strlen() could theoretically resolve to App\strlen()
at runtime if someone declared it. The compiler cannot know this at compile time, so it is forced to emit a generic, unoptimized
dynamic call:
INIT_NS_FCALL_BY_NAME "App\strlen"
SEND_VAR_EX $s
V1 = DO_FCALL_BY_NAME
The compiler uses its direct opcode shortcuts only when the identifier is completely unambiguous: when written with a leading
backslash as \strlen(), when imported explicitly via use function strlen;, or when the file contains no
namespace declaration at all.
How much difference does this make in practice? Here is a benchmark of twenty million iterations in a tight loop on PHP 8.5 (with OPcache enabled and JIT disabled):
| call in a namespace | time |
|---|---|
strlen($s) |
384 ms |
\strlen($s) |
165 ms |
is_array($s) |
473 ms |
\is_array($s) |
151 ms |
With the shortcut enabled, calls run two to three times faster. In absolute terms, this represents roughly ten to fifteen nanoseconds per call—imperceptible in an isolated web request, but immediately noticeable in loops processing hundreds of thousands of items.
What OPcache Computes in Advance
The OPcache Optimizer goes one step further. When all arguments are known constants at compile time, it evaluates the function during compilation and writes only the final evaluated result into the bytecode. A function like this:
function separator(): string
{
return \str_repeat('-', 3);
}
compiles directly to a simple RETURN "---". This constant folding works across variables as well: the code
$sep = ', '; return \implode($sep, ['a', 'b']); leaves only the finished string "a, b" in the compiled
opcode stream. PHP 8.4 can precompute over 170 deterministic functions whose outputs depend strictly on their inputs—including
str_repeat(), implode(), strtoupper(), substr(), trim(), and
version_compare().
OPcache gives special treatment to environment-checking functions: function_exists('mb_strlen'),
extension_loaded('intl'), is_callable('strlen'), defined('PHP_EOL'), and
constant('PHP_INT_SIZE'). When the answer is statically knowable, it replaces the call with true or the
constant's literal value directly. For function_exists(), however, it does this strictly when the function exists: a
missing function could be declared later by an autoloader or dynamic include, so negative checks must still be evaluated at
runtime.
Here too, the exact same condition applies: the compiler must have certainty regarding the function being called.
Constants Are Substituted Directly
Built-in constants provided by PHP or its extensions (such as PHP_EOL, PHP_INT_MAX,
PHP_VERSION_ID, or E_ALL) never change at runtime. The compiler therefore inlines their literal values
directly, allowing 'a' . \PHP_EOL to compile into a single concatenated string literal. Much more important than
saving a hash table lookup, however, is what this inlining enables next: the OPcache Optimizer factors inlined constants into its
branch analysis and completely eliminates dead code paths:
if (\PHP_VERSION_ID >= 80400) {
// code for modern PHP
} else {
// code for older PHP
}
When compiled on PHP 8.5, the condition is evaluated away entirely: the first branch is preserved and the else
block is purged from the opcode stream completely. The exact same optimization applies to conditions like
if (\PHP_OS_FAMILY === 'Windows').
Once again, the constant's name must be unambiguous. An unqualified PHP_VERSION_ID in a namespace could refer to
App\PHP_VERSION_ID (exactly like functions). PHP must therefore defer
resolution to runtime, forcing the entire condition and its dead branches to remain in memory. Writing
\PHP_VERSION_ID or adding use const PHP_VERSION_ID; solves this. The only exceptions are
true, false, and null, which PHP inlines unconditionally.
User-defined global constants are not inlined by OPcache because their values can vary across different HTTP requests.
Only Where the Compiler Makes Use of It
The simplest, naive approach would be to prepend a backslash or add an import statement for every single constant PHP defines.
That is what typical tools do: blindly backslashing every built-in constant they encounter. But substituting a value in isolation
merely saves a hash lookup (about 2.5 nanoseconds in our testing). The real performance dividend arrives only when the compiler
can actively compute with that value downstream: in a condition whose dead branch can be purged, or inside a compound expression
evaluated ahead of time, such as ENT_QUOTES | ENT_HTML5.
DressCode therefore inspects the surrounding syntactic context of each constant. A reference to PHP_VERSION_ID
inside if (PHP_VERSION_ID >= 80400) automatically receives an import, because doing so purges an entire dead
branch from compiled memory. Conversely, passing E_USER_DEPRECATED into trigger_error() remains
untouched, because an import there would add nothing but visual noise to the file header.
We verified this behavior across a large open-source codebase of nearly three thousand files. Bare PHP constants appeared across 627 locations, yet the compiler could actively optimize downstream expressions in only 117 of them. A blanket import-all strategy would have added 457 new import lines across 136 files. DressCode added only 82 lines across 40 files—over five times fewer—while capturing every single location where compiler optimizations actually make a difference.
Calls Without a Frame
PHP 8.4 introduced another major optimization, this time for nineteen of the most frequently used standard functions:
str_contains(), str_starts_with(), strpos(), strstr(), substr(),
str_replace(), strtr(), trim(), implode(), in_array(),
preg_match(), preg_replace(), min(), max(), class_exists(),
property_exists(), dirname(), dechex(), and is_numeric(). Each provides an
internal optimized variant that receives parameters directly via CPU registers without allocating a stack frame. The PHP engine
refers to these as frameless functions.
This optimization is unique in that it works even with unqualified calls inside namespaces. The compiler generates both execution paths and resolves between them upon first execution:
JMP_FRAMELESS "app\str_contains" if App\str_contains() does not exist, jump down
INIT_NS_FCALL_BY_NAME "App\str_contains" generic call
...
V1 = FRAMELESS_ICALL_2(str_contains) $s "x" call without a frame
The engine remembers whether App\str_contains() exists in OPcache after the first pass, meaning every subsequent
call costs only a single unconditional jump. An explicit import adds virtually no performance advantage here.
What Turns the Shortcuts Off
None of these function shortcuts work when a call passes named arguments or unpacks arrays via ...$args. For those
invocations, the compiler is forced to fall back to a full, dynamic function call.
Named arguments are written primarily for clarity: in_array($role, $roles, strict: true) explains the meaning of
true at a glance. But doing so forces PHP into a generic call, requiring a runtime parameter lookup by name:
| call | time |
|---|---|
\in_array('c', $list, strict: true) |
677 ms |
\in_array('c', $list, true) |
499 ms |
\in_array('c', ['a', 'b', 'c', 'd'], true) |
170 ms |
The second row shows the frameless call; the third row shows the direct instruction operating on a precompiled hash table.
Passing a named argument slows execution down even more than an unqualified namespace call: str_contains($s, 'x')
without a backslash takes 304 ms, whereas \str_contains($s, needle: 'x') takes 462 ms.
Unpacked arguments (e.g. \strlen(...$args)) disable the shortcuts as well, because the compiler cannot determine
the argument count ahead of time. Furthermore, frameless calls are only available for specific argument counts:
preg_match($pattern, $s) runs frameless, whereas passing the matches array via
preg_match($pattern, $s, $matches) requires a standard call frame.
Keep Expectations Realistic
These shortcuts accelerate individual calls by nanoseconds. In typical web applications that spend most of their lifecycle
waiting on database queries, network latency, or disk I/O, micro-optimizing function calls won't visibly shift page response
times. You will, however, see immediate benefits in code running in tight loops: parsers, template rendering engines, bulk array
processing pipelines, or foundation libraries invoked hundreds of thousands of times per request. Best of all, it costs you
nothing: DressCode applies these optimizations automatically, leaving your code clean and readable—adding just a few imports
where they matter, and using true instead of strict: true.
The Set compilerOptimizations
Tracking all of these low-level compiler rules by hand would be exhausting. That is why the compilerOptimizations
set automates the entire process. Simply add it to
your standard:
use:
- compilerOptimizations
No coding standard enables this set by default. The set selectively imports functions that the compiler handles specially—strictly where at least one invocation with the supplied arguments will genuinely benefit from optimization. It imports constants where the compiler can actively precompute expressions (allowing backslashes as an alternative for constants). All other global functions and constants remain bare, since qualifying them adds zero performance gain. And for calls the compiler optimizes, it converts safe named arguments back into positional ones while flagging unpacked arguments.
The set comprises four decisions from the qualification section:
function.global: bare, function.optimized: imported, constant.global: bare, and
constant.optimized: [imported, backslashed]. If you prefer backslashes over import statements, configure
backslashed for optimized identifiers (see the next section for
details).
Calls that can be replaced directly by native language constructs (such as is_null($x) by
$x === null, or intval($x) by (int) $x) are not handled by this set. Those refactorings
belong to decisions in the hygiene section: language
constructs need no compiler shortcuts, run at maximum speed by design, and are unaffected by namespaces. The nette
standard enables them out of the box.
Here is a method before optimization in a project targeting PHP 8.4 with nameResolution: certain:
use:
- compilerOptimizations
nameResolution: certain
targets:
php: '8.4'
namespace App;
class Access
{
public function isAllowed(array $roles, string $role): bool
{
if (PHP_VERSION_ID < 80400) { // Global constant `PHP_VERSION_ID` must be imported.
trigger_error('Old PHP', E_USER_DEPRECATED);
}
return in_array($role, $roles, strict: true) && strlen($role) > 2; // A named argument disables the compiler optimization of `in_array()`. // Global function `strlen()` must be imported.
}
}
namespace App;
use function strlen;
use const PHP_VERSION_ID;
class Access
{
public function isAllowed(array $roles, string $role): bool
{
if (PHP_VERSION_ID < 80400) {
trigger_error('Old PHP', E_USER_DEPRECATED);
}
return in_array($role, $roles, true) && strlen($role) > 2;
}
}
Imports were added for strlen() (which PHP always optimizes) and PHP_VERSION_ID (which the compiler
evaluates to eliminate the dead branch). The in_array() call remained bare: an import would only benefit an array
written directly as a literal, whereas $roles is a variable, and PHP 8.4's frameless call functions reliably with
bare names. However, the named argument strict was removed because it disabled that frameless call. And
E_USER_DEPRECATED, passed merely as a function argument, remained bare: an import would have saved nothing beyond a
single hash lookup. The measurements above show why this targeted
approach is superior to blanket imports.
All of these automated edits depend on these being genuine global PHP functions. If your configuration does not specify
nameResolution: certain, DressCode reports them as risky fixes and requires explicit approval (see Tell DressCode What Is What).
The set optimizes code regardless of your current target PHP version. Functions optimized only by newer PHP releases are qualified proactively: older runtimes simply ignore the shortcut without breaking, and the moment you upgrade PHP, your code is immediately primed to run at maximum speed. Named arguments are converted strictly when they already sit in their natural positional slots, preserving call semantics completely.
How Global Names Are Written
How global functions and constants are written in code is controlled by the qualification section. Every identifier can be
represented in three ways, and each decision key accepts one of three values: bare leaves the name bare relying on
runtime fallback, imported requires an explicit use function or use const import, and
backslashed mandates a leading backslash.
qualification:
function:
global: bare
optimized: imported
constant:
global: bare
optimized: imported
Under this configuration, ordinary global functions and constants remain clean and bare, while those the compiler can optimize receive an import:
namespace App;
use function count;
use function in_array;
At first glance, this might look redundant—count() works perfectly without an import statement. But the sole
motivation is performance: with an import, the compiler has compile-time certainty that the function is global and applies its
direct opcode shortcut (as detailed in the section on faster calls).
Using optimized: imported can lengthen your file header with multiple use function
lines. You can keep headers compact using imports.grouping.function: combined,
which bundles functions into a single statement: use function count, in_array, strlen;. Fewer lines mean fewer
tokens: AI agents inspecting the top of a file reach meaningful business logic faster without having to parse through walls of
imports. The nette standard formats imports this way.
Keys in the section proceed from general to specific:
| key | which names it decides |
|---|---|
function.global, constant.global |
Standard global functions and constants in namespaces (excluding those governed by optimized keys). |
function.optimized |
Functions handled specially by the compiler, applied where a call with the given arguments is genuinely optimized. |
constant.optimized |
Constants the compiler evaluates downstream: inside conditions or constant expressions. |
function.currentNamespace, constant.currentNamespace |
Functions and constants belonging to the file's own namespace. |
The function.optimized key evaluates arguments dynamically: PHP always optimizes strlen(), but
dirname(), defined(), and function_exists() are optimized only with constant arguments;
sprintf() is optimized only with format strings containing matching %s and %d specifiers;
and in_array() is optimized only with inline array literals. A function qualifies for this rule as soon as a
namespace calls it in an optimizable way at least once, after which every reference to that function in the namespace is formatted
consistently. A function never invoked in an optimizable way falls back to function.global, preventing unhelpful
imports from cluttering the file. The constant.optimized decision is similarly context-aware, looking at how the
constant is used rather than its name. Setting bare on these keys indicates that the project prioritizes clean,
unadorned code over compiler shortcuts. Each identifier is governed by exactly one key: if function.optimized is set
to keep, optimized functions remain untouched, and function.global: bare strips leading backslashes from
\array_map() while leaving \strlen() and \count() intact.
DressCode inserts imports matching the style your file already uses: integrating into existing multi-name statements, into
grouped imports for that namespace, and in correct alphabetical order so that rules in the imports section have nothing to complain about. If the file
already imports the function under an alias, it reuses that alias. Where an import cannot be added (for instance, due to a naming
collision), it falls back to a leading backslash. Conversely, it strips backslashes only where a bare name safely reaches the
global function, and automatically removes orphaned imports that are no longer referenced.
This brings us full circle to namespaces. An import statement use function strlen; permanently binds calls to the
global function. If your project had its own App\strlen(), adding that import would break runtime behavior. Under
uncertain, DressCode therefore treats any transition between bare names and imports or backslashes as risky. Under
certain, it knows App\strlen() does not exist and applies the import safely. Naturally, it never imports
a function listed in namespaces.functions.
Classes do not suffer from this ambiguity: an unqualified class name in a namespace never falls back to the global scope at
runtime. The keys class.otherNamespace and class.global therefore only choose between an import and a
backslash, and none of their automated fixes are risky. Identifiers without a leading backslash (such as Model\User
when use App\Model; is present) are left alone, as they resolve relative to imports or the file's namespace. In a
file without a namespace, leading backslashes are always redundant, and qualification.leadingBackslash cleanly
removes them.
Summary
- In PHP, an unqualified function or constant inside a namespace resolves either locally or globally—and an isolated file cannot tell which.
- Set
nameResolution: certainin your configuration (and list custom names undernamespacesif you have any). All rules will behave with complete certainty, and fixes relying on global names stop being risky. - Test suites declaring local helper functions can be scoped using an override with
nameResolution: uncertain. - Without
certain, DressCode acts conservatively, applying such fixes only with explicit consent infixRiskyor via--fix-risky. - Newly declared functions in namespaces are automatically tracked and validated under
namespaces.functions;dresscode initgenerates your initial whitelist. - Always call namespaced functions using an explicit
use functionimport. If the file is not loaded, you will find out immediately via a clear fatal error. - PHP compiles source code into bytecode and offers shortcuts for dozens of functions and constants: single-opcode execution, compile-time evaluation, and frameless calls.
- Nearly all shortcuts require unambiguous names:
\strlen(),use function strlen;, or files without namespaces. The same holds for constants likePHP_VERSION_ID. - Frameless calls introduced in PHP 8.4 work reliably even with unqualified names inside namespaces.
- Named arguments and array unpacking (
...$args) disable all compiler shortcuts. - The
compilerOptimizationsset formats code so PHP can utilize its shortcuts: qualifying functions where they yield real performance gains, and importing constants only where the compiler can evaluate them downstream.