Name Qualification

The qualification section decides how fully class, function and constant names are written: bare, imported with a use statement, or fully qualified with a leading backslash. It decides separately for classes from another namespace and for global classes, functions and constants, and once more separately for the functions and constants the PHP compiler can optimize.

A name can be written in three ways, and each key of the section takes a value for one of them:

  • bare: the bare name, strlen(), which inside a namespace reaches the global function only at runtime,
  • imported: the name from a use statement, use function strlen; and then strlen(),
  • backslashed: the name with a leading backslash, \strlen().

If you accept more than one form, write a list: every form in it passes, and a name that matches none of them is rewritten to the first one. This is exactly what the symfony standard wants: a global class with a backslash; a global function or constant with a backslash or bare, but never imported; and the same for the functions and constants the compiler can optimize. In the standard, this takes five lines of this section, and the example adds only imports.unused:

qualification:
	class:
		global: backslashed
	function:
		global: [backslashed, bare]
		optimized: [backslashed, bare]
	constant:
		global: [backslashed, bare]
		optimized: [backslashed, bare]
imports:
	unused: forbidden
namespace App\Report;

use App\Shop\Order;
use DateTimeImmutable;  // The import of `DateTimeImmutable` is unused.
use function sprintf;  // Global function `sprintf()` must not be imported.

function stamp(Order $order): string
{
	$date = new DateTimeImmutable;  // Global class `DateTimeImmutable` must be written with the leading backslash.
	return sprintf('%s: %d', $date->format('Y-m-d'), \count($order->items)) . PHP_EOL;  // Global function `sprintf()` must be written with the leading backslash.
}
namespace App\Report;

use App\Shop\Order;

function stamp(Order $order): string
{
	$date = new \DateTimeImmutable;
	return \sprintf('%s: %d', $date->format('Y-m-d'), \count($order->items)) . PHP_EOL;
}

\count() with the backslash and the bare PHP_EOL stayed, because the list allows both forms. The imported sprintf() and DateTimeImmutable got a backslash, and the imports that were no longer needed disappeared. The import of the class Order from another namespace stayed, because class.otherNamespace decides on it.

Classes behave differently from global functions and constants. In the namespace App, an unqualified class name like Exception always means App\Exception, so a global class cannot be written bare, only imported or qualified. An unqualified function or constant, on the other hand, reaches the global one when the namespace does not declare one with the same name. The page Functions and Constants in Namespaces explains why this is uncertain for a tool and how to remove the uncertainty.

qualification:
	class:
		otherNamespace: imported
		global: backslashed
	function:
		global: bare
		optimized: imported
imports:
	unused: forbidden

With this configuration, a class from another namespace is imported, a global class gets a backslash, global functions stay bare, and only those the PHP compiler can optimize are imported. imports.unused removes the imports that are no longer needed:

namespace App\Model;

use Exception;  // The import of `Exception` is unused.

class Cart
{
	public function add(array $items, string $name): int
	{
		if (count($items) > 10 || strlen($name) === 0) {  // Global function `count()` must be imported. // Global function `strlen()` must be imported.
			throw new Exception('Too many items');  // Global class `Exception` must be written with the leading backslash.
		}
		$clock = new \App\Util\Clock;  // The fully qualified name `\App\Util\Clock` must be imported.
		return \array_sum($items);  // Global function `array_sum()` must be written without the leading backslash.
	}
}
namespace App\Model;

use App\Util\Clock;
use function count;
use function strlen;

class Cart
{
	public function add(array $items, string $name): int
	{
		if (count($items) > 10 || strlen($name) === 0) {
			throw new \Exception('Too many items');
		}
		$clock = new Clock;
		return array_sum($items);
	}
}

qualification.leadingBackslash removes a backslash that changes nothing: in a file without a namespace, and anywhere before true, false and null. A backslash at the start of an import is the job of imports.leadingBackslash.

Compiler Optimizations

The PHP compiler can replace calls to about thirty functions such as strlen(), count() or is_array() with a single instruction, and it can evaluate constants such as PHP_VERSION_ID in advance. But it must know at compile time that the name refers to a global function, which means the name is imported or has a backslash. The keys function.optimized and constant.optimized decide on exactly these names, and function.global and constant.global do not apply to them. For functions, the arguments also matter: the compiler optimizes in_array() only with an array written directly in the call. A function belongs to function.optimized in the whole namespace when the code calls it this way at least once there; otherwise function.global decides on it, because an import that makes nothing faster is useless. For constants, the place decides: PHP_VERSION_ID helps the compiler in a condition, not as a function argument. The page Optimizations of Functions and Constants describes what exactly PHP does and how much it gains.

So each name belongs to exactly one key. Where function.optimized is keep, the optimized functions stay as they are: function.global: bare by itself removes the backslash from \array_map(), but leaves \strlen() and \count() untouched. If you want all functions written the same way, write the value for the whole kind, function: bare, and it applies to every place that accepts it: global, optimized and currentNamespace. The same holds for constants.

How an optimized call is written has one more consequence: named and unpacked arguments turn the optimization off. Where function.optimized writes a function as imported or with a backslash, DressCode rewrites the named arguments of its calls to positional ones and reports an unpacked argument.

Risky Fixes

Switching a bare function to an imported one, or the other way round, is safe only when the namespace declares no function with the same name. This cannot be seen from a single file, so this is a risky fix: it waits for your permission until the configuration sets nameResolution: certain. With it, DressCode knows what is in your namespaces and makes these fixes right away.

qualification.currentClass

How a class refers to itself in an expression: a static access, an instantiation, an instanceof.

  • self: the class named self inside itself
  • keep: nothing is enforced, the code stays as it is

Default keep · standards: perCs keep, psr12 keep, nette self, symfony keep, laravel keep · covers self_accessor, SlevomatCodingStandard.Classes.UselessLateStaticBinding, Squiz.Classes.SelfMemberReference

qualification.staticInFinalClass

self for static in a class no subclass can extend, a final class, an anonymous class, an enum: self::create(), new self.

The return type static stays, saying what a subclass would return.

  • required: always there
  • keep: nothing is enforced, the code stays as it is

Default keep · standards: perCs keep, psr12 keep, nette keep, symfony keep, laravel required · covers self_static_accessor, SlevomatCodingStandard.Classes.UselessLateStaticBinding

qualification.class.otherNamespace

A class, interface, trait or enum of a namespace other than that of the file.

A name relative to an import or to the namespace, Shop\Order, stays as it is.

  • imported: imported, use Acme\Shop\Order; and Order
  • backslashed: with the leading backslash, \Acme\Shop\Order
  • a list of these words, where each one passes and code matching none of them is written like the first
  • keep: nothing is enforced, the code stays as it is

Default keep · standards: perCs keep, psr12 keep, nette imported, symfony keep, laravel imported · covers fully_qualified_strict_types, SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly

qualification.function.otherNamespace

A function of a namespace other than that of the file.

  • imported: imported, use function Acme\Text\normalize; and normalize()
  • backslashed: with the leading backslash, \Acme\Text\normalize()
  • a list of these words, where each one passes and code matching none of them is written like the first
  • keep: nothing is enforced, the code stays as it is

Default keep · standards: perCs keep, psr12 keep, nette keep, symfony keep, laravel keep · covers SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly

qualification.constant.otherNamespace

A constant of a namespace other than that of the file.

  • imported: imported, use const Acme\Shop\STATUS_PAID; and STATUS_PAID
  • backslashed: with the leading backslash, \Acme\Shop\STATUS_PAID
  • a list of these words, where each one passes and code matching none of them is written like the first
  • keep: nothing is enforced, the code stays as it is

Default keep · standards: perCs keep, psr12 keep, nette keep, symfony keep, laravel keep · covers SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly

qualification.class.phpdoc

A class, interface, trait or enum a doc comment names in a type, a class constant or the target of a tag.

A name relative to an import or to the namespace, Shop\Order, stays as it is, and so does a bare one no import reaches, which a doc comment cannot tell from a type of its own such as list.

  • imported: imported, use Acme\Shop\Order; and @param Order $order, a class of the namespace of the file bare, a global one where the file imports it
  • backslashed: with the leading backslash, @param \Acme\Shop\Order $order, a name an import reaches
  • a list of these words, where each one passes and code matching none of them is written like the first
  • keep: nothing is enforced, the code stays as it is

Default keep · standards: perCs keep, psr12 keep, nette imported, symfony keep, laravel imported · covers fully_qualified_strict_types

qualification.class.currentNamespace

A class, interface, trait or enum of the namespace of the file, Order in namespace Acme\Shop.

  • bare: bare, Order relative to the namespace
  • backslashed: with the leading backslash, \Acme\Shop\Order
  • a list of these words, where each one passes and code matching none of them is written like the first
  • keep: nothing is enforced, the code stays as it is

Default keep · standards: perCs keep, psr12 keep, nette bare, symfony keep, laravel bare · covers fully_qualified_strict_types, SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly

qualification.function.currentNamespace

A function of the namespace of the file, normalize() in namespace Acme\Text.

  • bare: bare, normalize() relative to the namespace
  • backslashed: with the leading backslash, \Acme\Text\normalize()
  • a list of these words, where each one passes and code matching none of them is written like the first
  • keep: nothing is enforced, the code stays as it is

Default keep · standards: perCs keep, psr12 keep, nette keep, symfony keep, laravel keep · covers SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly

qualification.constant.currentNamespace

A constant of the namespace of the file, STATUS_PAID in namespace Acme\Shop.

  • bare: bare, STATUS_PAID relative to the namespace
  • backslashed: with the leading backslash, \Acme\Shop\STATUS_PAID
  • a list of these words, where each one passes and code matching none of them is written like the first
  • keep: nothing is enforced, the code stays as it is

Default keep · standards: perCs keep, psr12 keep, nette keep, symfony keep, laravel keep · covers SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly

qualification.function.optimized

A global function whose call PHP compiles to one opcode once it knows the function is global, count(), strlen(), is_int() and the others; a call of it written imported or with the backslash has its arguments passed positionally and an unpacked one reported.

A function is optimized in a namespace where one of its calls there is, an unpacked or named argument aside, in_array() only with a constant array, and every reference to it in that namespace is then written so; one none of whose calls is optimized is decided by function.global.

  • imported: imported, use function strlen;, so that the compiler optimizes it
  • backslashed: with the leading backslash, \strlen(), so that the compiler optimizes it
  • bare: bare, strlen(), forgoing the optimization
  • a list of these words, where each one passes and code matching none of them is written like the first
  • keep: nothing is enforced, the code stays as it is

Default keep · standards: perCs keep, psr12 keep, nette keep, symfony [backslashed, bare], laravel keep · covers global_namespace_import, native_function_invocation, SlevomatCodingStandard.Namespaces.FullyQualifiedGlobalFunctions, SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly, SlevomatCodingStandard.PHP.OptimizedFunctionsWithoutUnpacking

qualification.class.global

A class of the global namespace referenced in a namespace, DateTime, which a bare name there does not reach.

  • imported: imported, use DateTime; and DateTime
  • backslashed: with the leading backslash, \DateTime
  • a list of these words, where each one passes and code matching none of them is written like the first
  • keep: nothing is enforced, the code stays as it is

Default keep · standards: perCs keep, psr12 keep, nette keep, symfony backslashed, laravel keep · covers global_namespace_import, SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly

qualification.function.global

A global function called in a namespace, ucfirst(), but one PHP optimizes a call of there, which function.optimized decides.

Not a function of the namespace of the file, nor a name in a file without a namespace, which qualification.leadingBackslash decides.

  • bare: bare, ucfirst() without an import or a backslash, reached by the fallback at run time
  • imported: imported, use function ucfirst; and ucfirst()
  • backslashed: with the leading backslash, \ucfirst()
  • a list of these words, where each one passes and code matching none of them is written like the first
  • keep: nothing is enforced, the code stays as it is

Default keep · standards: perCs keep, psr12 keep, nette keep, symfony [backslashed, bare], laravel keep · covers global_namespace_import, native_function_invocation, SlevomatCodingStandard.Namespaces.FullyQualifiedGlobalFunctions, SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly

qualification.constant.global

A global constant in a namespace, PHP_EOL, but where the compiler computes with it, which constant.optimized decides.

  • bare: bare, PHP_EOL without an import or a backslash, reached by the fallback at run time
  • imported: imported, use const PHP_EOL; and PHP_EOL
  • backslashed: with the leading backslash, \PHP_EOL
  • a list of these words, where each one passes and code matching none of them is written like the first
  • keep: nothing is enforced, the code stays as it is

Default keep · standards: perCs keep, psr12 keep, nette keep, symfony [backslashed, bare], laravel keep · covers global_namespace_import, native_constant_invocation, SlevomatCodingStandard.Namespaces.FullyQualifiedGlobalConstants, SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly

qualification.constant.optimized

A constant of PHP the compiler computes with once it knows the constant is global, PHP_VERSION_ID in a condition, PHP_INT_MAX in a constant expression.

A constant merely passed to a call is not one the compiler computes with.

  • imported: imported, use const PHP_VERSION_ID;, so that the compiler computes with it
  • backslashed: with the leading backslash, \PHP_VERSION_ID, so that the compiler computes with it
  • bare: bare, PHP_VERSION_ID, forgoing the optimization
  • a list of these words, where each one passes and code matching none of them is written like the first
  • keep: nothing is enforced, the code stays as it is

Default keep · standards: perCs keep, psr12 keep, nette keep, symfony [backslashed, bare], laravel keep · covers global_namespace_import, native_constant_invocation, SlevomatCodingStandard.Namespaces.FullyQualifiedGlobalConstants, SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly

qualification.leadingBackslash

The leading backslash of a name where it changes nothing, in a file without a namespace, \strlen() and new \DateTime, and before true, false and null anywhere.

  • forbidden: never there
  • keep: nothing is enforced, the code stays as it is

Default keep · standards: perCs keep, psr12 keep, nette forbidden, symfony keep, laravel keep · covers PhpCsFixerCustomFixers/no_leading_slash_in_global_namespace