PHP 8.6 arrives on November 19. DressCode already rewrites code for it. 16 days Upgrading to a newer PHP →

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) to count($a),
  • dirname(__FILE__) to __DIR__,
  • get_class() to self::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') returns true for the global function, while recognizing that App\is_null() from your list is not global. Under certain, 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 marked Uncertain—and therefore never in a project configured with certain.

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 other is_*() functions turn into single opcode type checks,
  • casting functions intval(), strval(), boolval(), and floatval() compile directly to native type casts,
  • functions count(), gettype(), get_class(), array_key_exists(), defined(), and func_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 %s and %d specifiers in counts matching the passed arguments,
  • calling strlen('hello') or chr(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: certain in your configuration (and list custom names under namespaces if 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 in fixRisky or via --fix-risky.
  • Newly declared functions in namespaces are automatically tracked and validated under namespaces.functions; dresscode init generates your initial whitelist.
  • Always call namespaced functions using an explicit use function import. 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 like PHP_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 compilerOptimizations set 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.

Show on GitHub · Report a problem with this page on GitHub · Nov 2, 2026