Naming and imports¶
Rules for the names of classes, constants, functions, methods, properties and variables, and for
use statements. Mago's own naming rules cover the rest, see
Configuration.
drupal/class-name-acronym¶
- Level: error
- Fix: none
- Standard: Classes, methods and properties
- Ports:
Drupal.NamingConventions.ValidClassName.NoUpperAcronyms
If an acronym is used in a class or method name, make it CamelCase too (SampleXmlClass, not SampleXMLClass).
Reports:
- a class, interface, trait or enum name that starts with three upper-case letters and doesn't have
a lower-case letter after them, such as
HTTPorURL_2. Digits, underscores and multi-byte letters don't count as lower case.
Not reported:
- a name with a lower-case letter after the capitals, such as
HTTPClient. The rule follows Coder here, although the standard asks forHttpClient.
Compared with Coder
- The rule reports on the name. Coder 9 reports on the
classkeyword, so the lines differ when a declaration spans several lines.
drupal/define-name¶
- Level: error
- Fix: none
- Standard: Constants
- Ports:
Generic.NamingConventions.UpperCaseConstantName.ConstantNotUpperCase
Constants should always be all-uppercase, with underscores to separate words.
Reports:
- a
define()constant name that isn't upper case. The rule reads the first argument, a string literal or literals joined with., and checks the part after the last backslash. Only ASCII letters count. It reports in every scanned file type and at any depth. It matchesdefinein any letter case, with or without a leading backslash, as Coder does.
Not reported:
- a name that has a variable in it, such as
'mymod_' . $x.
Mago's constant-name covers const, the other half of the sniff.
Compared with Coder
- A name built from literals joined with
.is checked as a whole. Coder 9 checks only the first literal. - Coder 9 reports a name that has a variable in it, such as
'mymod_' . $x. - The rule also reports a
define()call inside an attribute argument, andnamespace\define()in a file with no namespace declaration. Coder 9 reports neither.
drupal/enum-case-name¶
- Level: error
- Fix: none
- Standard: Enums
- Ports:
Drupal.NamingConventions.ValidEnumCase:NoUnderscores,NoUpperAcronyms,StartWithCapital
Enums follow the same conventions as classes with the addition that for their cases the enums must use UpperCamelCase.
Reports:
- an enum case name that doesn't start with a capital letter;
- an enum case name that holds an underscore;
- an enum case name that starts with three upper-case letters and doesn't have a lower-case letter
after them, the check of
drupal/class-name-acronym.
A name that fails several checks gets one issue for each.
Example
// Reported, on each case.
enum Status {
case pending;
case Not_Found;
case HTTP;
}
// Fine.
enum Status {
case Pending;
case NotFound;
case Http;
}
drupal/exception-suffix¶
- Level: error
- Fix: none
- Standard: Basic Exception Naming Conventions
- Ports: none. Coder doesn't check it.
All Exceptions must end with the suffix "Exception".
Reports:
- a class whose parent's name ends in
Exception, and whose own name doesn't.
Not reported:
- a class that extends a wrongly named exception. The rule reads the name in the
extendsclause, not the ancestry, and the parent is reported; - a class that extends
\Error.
Example
// Reported.
class WidgetNotFound extends \RuntimeException {
}
// Fine.
class WidgetNotFoundException extends \RuntimeException {
}
drupal/fully-qualified-name¶
- Level: error
- Fix: safe. It adds the import and writes the short name everywhere the file writes the class in full.
- Standard: "use"-ing classes
- Ports:
Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing
Classes and interfaces with a backslash
\inside their fully-qualified name (for example:Drupal\Tests\BrowserTestBase) must not use their fully-qualified name inside the code. If the namespace differs from the namespace of the current file, put ausestatement on the top of the file.
Reports:
- a namespaced class written out in full where a
usestatement belongs.
Not reported:
- a name with no namespace of its own, such as
\Exception; - a name relative to the current namespace, such as
namespace\Sub\Thing; - a namespaced function call or first-class callable;
- an
.api.phpfile.
In a file with no namespace, the fix puts the import below the @file docblock and any declare
that ends at its ;, or below the open tag, and after the last use when the file has one. A
declare with a body, such as declare(ticks=1) { ... }, holds code, so the import goes above
it. There isn't a fix in a file with several namespaces or a braced one, where the import would go
below code or a constant, such as code on the same line as the last use or the namespace, or
outside PHP, as when a close tag ends that line or a comment on it. There isn't one either when
the file already uses the short name for something else: another import, a class of that name, or
a docblock that writes the short name in the same case.
Example
The fix turns the reported code into the fine code.
// Reported.
function example_load(int $id): ?object {
return \Drupal\node\Entity\Node::load($id);
}
// Fine.
use Drupal\node\Entity\Node;
function example_load(int $id): ?object {
return Node::load($id);
}
Compared with Coder
- Coder reports a namespaced function call. Its fix imports the function name as a class, and the call then reaches the global function.
- The rule skips the names in a
usegroup. Coder reports the member, and its fix changes the statement. - A name in the conflict block of a trait
useisn't reported. Coder reports it. - In a file with no namespace, the fix never puts an import in the clause of a closure. Coder's
fix does when a closure holds the first
useof the file. - There isn't a fix when the short name is taken by an interface, a trait, an enum, an import that differs in case only, or a class whose name has a comment before it. Coder reports a fixable error there, and its fix writes code that PHP rejects.
drupal/function-name¶
- Level: error
- Fix: none
- Standard: Functions and variables
- Ports:
Drupal.NamingConventions.ValidFunctionName.InvalidName
Functions should be named using lowercase, and words should be separated with an underscore.
Reports:
- a function whose name isn't lower case, such as
mymoduleHelper()ormymodule_Helper(). The rule checks every function outside a class, interface, trait or enum, in every file.
Not reported:
- a leading or doubled underscore, so a private helper such as
_mymodule_helper()passes; - a function that starts with
hook_in an.api.phpfile, since such a file documents hooks with names such ashook_ENTITY_TYPE_insert().
Mago's own function-name wants snake case, so it reports _mymodule_helper(), which is accepted
by Coder. The configuration turns it off.
drupal/global-class-backslash¶
- Level: error
- Fix: safe. It adds the backslash.
- Standard: "use"-ing classes
- Ports: none. Coder doesn't have such a sniff.
Classes and interfaces without a backslash
\inside their fully-qualified name (for example, the built-in PHP Exception class) must be fully qualified. For example:new \Exception();.
Reports:
- a class name without a leading backslash in a file with no
namespace, such asnew DateTime()orDrupal::service()in a.modulefile. The rule reads class names in types,extends,implements, traituse, attributes,new, static calls and accesses,::class, and on the right ofinstanceof.
Not reported:
- a name that a
usestatement imports, also when the import is a global class, asFailureafteruse Exception as Failure;. A leading backslash would turn it into a class that doesn't exist.drupal/redundant-usereports the import, and its fix writes the class in full; self,static,parentand the built-in types, which aren't class names;- an
.api.phpfile. Its examples leave out the imports, so a short name there, such asUrl, stands for a namespaced class.
In a file with no namespace both forms name the same class, so the fix doesn't change what the code does.
Mago's no-fully-qualified-global-class-like, off by default, asks for the opposite.
drupal/hook-attribute-name¶
- Level: warning
- Fix: none
- Source: Coder's
Drupalstandard - Ports:
Drupal.Attributes.ValidHookName.HookPrefix
Coder asks for the hook name in a #[Hook] attribute without the hook_ prefix. The attribute
holds the hook name without the prefix, so a name with it is never invoked.
Reports:
- a
Drupal\Core\Hook\Attribute\Hookattribute whose first argument, positional orhook:, is a hook name that starts withhook_. The rule resolves the attribute name, so an alias or a fully qualified name counts, and a bareHookthat isn't imported from Drupal's class doesn't. Every attribute of a group is checked. A hook name built from literals joined with.is checked as a whole.
Example
// Reported.
namespace Drupal\example\Hook;
use Drupal\Core\Hook\Attribute\Hook;
class ExampleHooks {
#[Hook('hook_cron')]
public function cron(): void {
}
}
// Fine.
namespace Drupal\example\Hook;
use Drupal\Core\Hook\Attribute\Hook;
class ExampleHooks {
#[Hook('cron')]
public function cron(): void {
}
}
Compared with Coder
- Coder's fix removes the prefix. The rule doesn't have a fix, because removing the prefix changes which hook runs.
- It reads only the attribute's own first argument. Coder 9 reads the next string literal in the
file, so it also reports an unrelated string below a
#[Hook(self::CRON)]or a#[Hook]attribute.
drupal/literal-constant-case¶
- Level: error
- Fix: safe. It writes
TRUE,FALSEorNULLwithout the backslash. - Standard: Constants
- Ports:
Generic.PHP.UpperCaseConstant.Found
Constants should always be all-uppercase, with underscores to separate words. (This includes pre-defined PHP constants like TRUE, FALSE, and NULL.)
Reports:
- a fully qualified
\true,\falseor\nullthat isn't all upper case, such as\trueor\Null, anywhere in the code, default values and constant expressions included.
Not reported:
\TRUE,\FALSEand\NULL, which Coder accepts too;namespace\true, which Coder doesn't report either.
mago format with the drupal preset writes true, false and null in upper case. It leaves a
fully qualified one as written, since Mago reads it as a constant and not as a literal. PHP never
looks these three names up in a namespace, so dropping the backslash doesn't change what the code
does.
Example
The fix turns the reported code into the fine code.
// Reported.
function example_flag($flag = \false) {
return $flag ?? \null;
}
// Fine.
function example_flag($flag = FALSE) {
return $flag ?? NULL;
}
Compared with Coder
- phpcbf writes
\TRUEand keeps the backslash. The fix writesTRUE, the form that the rest of Drupal's code uses.
drupal/method-name¶
- Level: error
- Fix: none
- Standard: Classes, methods and properties
- Ports:
Drupal.NamingConventions.ValidFunctionName:NotCamelCaps,ScopeNotCamelCaps
Methods and class properties should use lowerCamel naming.
If an acronym is used in a class or method name, make it CamelCase too (SampleXmlClass, not SampleXMLClass).
Reports:
- a method name that doesn't start with a lower-case ASCII letter, holds a character other than an
ASCII letter or digit, or has two capitals in a row, such as
get_url(),GetUrl(),getURL()orget2FA(). A digit ends a run of capitals, sogetA2B()passes. The rule checks the methods of classes, interfaces, traits, enums and anonymous classes.
Not reported:
- the leading underscores, which the rule strips before the check.
drupal/method-name-underscorereports them; - a PHP magic method or a
SoapClientmethod, the names thatdrupal/method-name-underscoreaccepts; - a method named after its class, or after its class with one leading underscore, the PHP 4 constructor and destructor. The names are compared without regard to case.
A rename breaks the callers and the overrides of a method, so the rule doesn't have a fix. A name
that PHP or a parent class sets, such as the stream_open() method of a stream wrapper, needs a
@mago-expect lint:drupal/method-name comment.
Mago's own method-name accepts a run of capitals, as in getURL(), so it can't stand in for this
rule.
Example
// Reported.
class Example {
public function getURL(): string {
return '';
}
}
// Fine.
class Example {
public function getUrl(): string {
return '';
}
}
Compared with Coder
- The rule reports on the name. Coder 9 reports on the
functionkeyword, so the lines differ when an attribute or a line break comes between them. - Coder reports the same check under two codes,
ScopeNotCamelCapsfor a method with a visibility keyword andNotCamelCapsfor one without. The rule gives one message for both.
drupal/method-name-underscore¶
- Level: warning
- Fix: none
- Standard: Classes, methods and properties, Methods and functions
- Ports:
Drupal.NamingConventions.ValidFunctionName.MethodDoubleUnderscorePSR2.Methods.MethodDeclaration.Underscore
Method names must not be prefixed with a single underscore to indicate protected or private visibility. That is, an underscore prefix explicitly has no meaning.
Reports:
- a method name that starts with one underscore to mark it private;
- a method name that starts with two underscores and isn't a PHP magic method or a
SoapClientmethod.
Not reported:
- a name that is only two underscores, or starts with three.
The names are matched without regard to case. The two-underscore check accepts 29 names: the 17
magic methods of PHP and the 12 methods of SoapClient. Mago's method-name rule doesn't report a
name that starts with two underscores, so this check isn't repeated by another rule.
Compared with Coder
- The rule reports on the name. Coder 9 reports on the
functionkeyword, so the lines differ when a declaration spans several lines.
drupal/old-style-constructor¶
- Level: error
- Fix: none
- Source: Coder's
Drupalstandard - Ports:
Generic.NamingConventions.ConstructorName.OldStyle
PHP 4 ran a method named after its class as the constructor. PHP 8 doesn't, and in a namespaced class PHP never did. Such a method is an ordinary method, and its name suggests that it sets up the object.
Reports:
- a method of a class whose name is the name of the class, compared without regard to case, when
the class doesn't declare
__construct(). Abstract and static methods count.
Not reported:
- a class that declares
__construct(), in any case; - a method of an interface, a trait, an enum or an anonymous class.
A __construct() in an anonymous class inside a method doesn't count. A constructor that the class
takes from a trait doesn't count either, since the rule reads only the class's own declarations.
There isn't a fix. A rename to __construct() changes what new runs, and any rename breaks the
callers of the method.
Example
// Reported.
class ExampleWidget {
public function exampleWidget(): void {
}
}
// Fine.
class ExampleWidget {
public function __construct() {
}
}
The analyzer reports a call such as parent::ExampleWidget() when the parent doesn't have a method
of that name, as non-existent-method. That is the other code of the sniff, OldStyleCall.
drupal/property-name¶
- Level: error
- Fix: none
- Standard: Classes, methods and properties
- Ports:
Drupal.NamingConventions.ValidVariableName.LowerCamelNamePSR2.Classes.PropertyDeclaration.Underscore
Methods and class properties should use lowerCamel naming. In Drupal 8, properties of configuration entities are exempt of these conventions. Those properties are allowed to use underscores.
Reports:
- a class property whose name doesn't start with a lower-case letter or holds an underscore.
Not reported:
- local variables;
- a property of a config entity or a plugin annotation, which may use any case: a class whose
parent name contains
ConfigEntityor isPluginorViewsPluginAnnotationBase, and a class that implementsAnnotationInterface. A name that starts with an underscore is still reported in these classes.
The names are compared as written, so \Drupal\Core\Config\Entity\ConfigEntityBase counts, while
an alias of Plugin or \Drupal\Component\Annotation\Plugin doesn't. Only the first parent of an
interface counts. The rule reads the outermost class around the property, and only when that class
is at the top level of the file. An anonymous class in a method follows the class around it, and a
class declared inside an if or a function is checked.
Compared with Coder
- Core's config turns
PSR2.Classes.PropertyDeclaration.Underscoreoff. The rule still reports a leading underscore in core. The lowerCamelCase check covers it in most classes, but not in the config entities and plugin annotations above, where Coder doesn't report anything under core's config.
drupal/psr4-namespace¶
- Level: error
- Fix: none
- Standard: Summary
- Ports: none. Coder doesn't check it.
Anything after the module namespace directly maps to the directory and file structure in the
./src/folder.The identical logic applies to PHPUnit tests contained in
./tests/src/.
Reports:
- a
namespacestatement that doesn't match the path of its file. A class in another namespace isn't found by the autoloader. The namespace given by the path is:Drupal\{name}and the directories belowsrc/, for a file under thesrc/directory of a module, theme or profile;Drupal\Tests\{name}and the directories belowtests/src/, for a file under itstests/src/directory;- the directories from
Drupaldown, for a file under core'slib/Drupal/Core,lib/Drupal/Componentandtests/Drupal/*Testsdirectories.
The name is the one of the nearest *.info.yml file at or above the file's directory. So
modules/example/src/Form/SettingsForm.php declares Drupal\example\Form, and
modules/example/tests/src/Kernel/SettingsTest.php declares Drupal\Tests\example\Kernel.
Not reported:
- a file with more than one
namespacestatement. A test declares a second namespace to replace a function of the code under test; - a file that doesn't declare a class, interface, trait or enum, since it isn't loaded by name;
- a file outside the directories above, and a file that isn't named
*.php; - a file under a
srcdirectory inside core'slibandteststrees, which belongs to a fixture package with a namespace of its own.
Mago's file-name reports a class whose name doesn't match its file.
drupal/redundant-use¶
- Level: error
- Fix: safe. It removes the import and writes the name with a leading backslash, such as
\Exception, at every reference. - Standard: "use"-ing classes
- Ports:
Drupal.Classes.UseGlobalClass.RedundantUseStatement
Classes and interfaces without a backslash
\inside their fully-qualified name (for example, the built-in PHP Exception class) must be fully qualified. For example:new \Exception();. Do notuseglobal classes.
Reports:
- a
usestatement that imports a class from the global namespace, also one with a leading backslash such asuse \DateTime;.
Not reported:
use const;- grouped imports such as
use Foo\{Bar, Baz};.
The fix reads the references from the resolved names. There isn't a fix while a docblock in the file
names the class in the same case without a leading backslash, in a type, an annotation or prose.
drupal/doc-type-namespace fixes the types.
Example
The fix turns the reported code into the fine code.
// Reported.
namespace Drupal\example;
use Exception;
class Example {
public function fail(): void {
throw new Exception('Failed.');
}
}
// Fine.
namespace Drupal\example;
class Example {
public function fail(): void {
throw new \Exception('Failed.');
}
}
Compared with Coder
- Coder reports
use constand grouped imports, and phpcbf breaks the code there: it writes\constfor a constant import and the group prefix for a grouped name. - Coder's sniff skips an import with a leading backslash, such as
use \DateTime;. Coder reports that line only for the backslash, which is covered bydrupal/use-leading-backslash.
drupal/test-class-suffix¶
- Level: error
- Fix: none
- Standard: Classes, methods and properties
- Ports: phpstan-drupal's
TestClassSuffixNameRule(partly)
Test classes should always have the suffix "Test".
Reports:
- a concrete class in a PHPUnit test directory whose parent's name ends in
Test,TestBaseorTestCase, and whose own name doesn't end inTest. PHPUnit runs only the files named*Test.php, and so dophpunit -c coreandrun-tests.sh, so such a test never runs.
Not reported:
- a class outside the test directories below, where the parent's name doesn't say anything: core's
test entity class is called
EntityTest; - a class whose parent is declared in the same file without a parent whose name ends in
Test,TestBaseorTestCase. Such a parent is a fixture, not a test base.
The test directories are Unit, Kernel, Functional, FunctionalJavascript and Build under a
module's tests/src, and Tests, KernelTests, FunctionalTests, FunctionalJavascriptTests and
BuildTests under core/tests/Drupal.
The analyzer check drupal/test-class-name reads the
class's ancestry, and reports the TestCase descendants that the rule doesn't take for tests.
Example
// Reported.
namespace Drupal\Tests\example\Unit;
use Drupal\Tests\UnitTestCase;
final class ExampleChecks extends UnitTestCase {
}
// Fine.
// The class and its file are named ExampleChecksTest.
namespace Drupal\Tests\example\Unit;
use Drupal\Tests\UnitTestCase;
final class ExampleChecksTest extends UnitTestCase {
}
Compared with phpstan-drupal
- phpstan-drupal reports any concrete subclass of PHPUnit's
TestCasewhose name doesn't end inTest, in any directory and whatever its parent is called. The rule anddrupal/test-class-namereport those together. - The rule reads the parent's name, not its ancestry. So it reports a class in a test directory
whose parent's name ends in
TestorTestBasebut that isn't aTestCase, such as a helper that extends core'sEntityTestentity class in another file. phpstan-drupal doesn't.
drupal/use-leading-backslash¶
- Level: error
- Fix: safe. It removes the backslash.
- Standard: "use"-ing classes
- Ports:
SlevomatCodingStandard.Namespaces.UseDoesNotStartWithBackslash.UseStartsWithBackslash
When importing a class with "use", do not include a leading
\.
Reports:
- an import whose first name starts with a backslash, as in
use \Drupal\node\Entity\Node;. The rule reads the name afteruse,use functionoruse const, in any letter case, or the prefix of a group, and a comment before the name doesn't hide it.
Not reported:
- a later name of the statement, so
use A, \B;isn't reported.mago formatwrites each name on its ownusestatement, and then every name is checked.
A use statement always names a class, function or constant from the root, so the backslash adds
nothing.
Compared with Coder
- Coder reads
functionandconstonly in lower case, so it doesn't reportuse FUNCTION \foo;. The rule reads the keyword in any case, as PHP does.
drupal/variable-name¶
- Level: error
- Fix: none
- Standard: Functions and variables
- Ports:
Drupal.NamingConventions.ValidVariableName.LowerStart
Variables should be named using lowercase, and words should be separated either with uppercase characters (example: $lowerCamelCase) or with an underscore (example: $snake_case).
Reports:
- a variable whose name starts with an ASCII capital, at each place it's written: parameters,
promoted constructor parameters included, assignments, reads,
foreach,catch,static,global, destructuring, a closure'suselist, a dynamic property name such as$object->$Name, and variables outside a function.
Not reported:
$GLOBALS;- a property declaration, which
drupal/property-namechecks; - a static property or method name after
::, such asself::$Count; - a variable inside a double-quoted string or a heredoc;
- a name that starts with a capital outside ASCII, such as
$É.
A rename of a parameter breaks the callers that pass it by name, and a global or static variable is shared with other code, so the rule doesn't have a fix.
Mago's own variable-name checks only assignments and parameters, and it also reports a name such
as $foo_Bar, which Coder accepts.
Example
// Reported.
function example_total(array $Items): int {
return count($Items);
}
// Fine.
function example_total(array $items): int {
return count($items);
}
Compared with Coder
- Coder treats a variable inside a property hook as a property, and reports it as
LowerCamelName, an underscore included. The rule checks it as any other variable.