DrupalPractice checks¶
These rules port sniffs of Coder's DrupalPractice standard not run by core's phpcs.xml.dist, so
they're off in Drupal core.
Coder's Project class reads the nearest *.info.yml or *.info file, to find the module name of
a file that isn't named after it and to skip a Drupal 7 module. The rules read the same file, from
the worker's directory, which is the directory of the Mago config file:
drupal/class-prefixanddrupal/form-alter-commentread the module name from it;drupal/global-constant,drupal/request-superglobalanddrupal/global-functionskip a file whose info file is a*.infofile that names a core version below 8, or no version, as Coder does.
drupal/class-prefix¶
- Level: warning
- Fix: none
- Source: Coder's
DrupalPracticestandard - Ports:
DrupalPractice.General.ClassName.ClassPrefix
DrupalPractice asks a class or interface outside a namespace to start with the module name.
Reports:
- a class or interface outside a namespace whose name doesn't start with the module name, with or without the underscores of the name.
A .module, .install, .profile or .theme file takes the module name from its file name. Any
other file takes it from the nearest *.info.yml file, or a Drupal 7 *.info file, in its
directory or one above.
Not reported:
- a file with neither a module file name nor an info file;
- every declaration from the first
namespacestatement on; - traits, enums and anonymous classes.
Example
// Reported, in example.module.
class Helper {
}
// Fine.
class ExampleHelper {
}
Compared with Coder
- For
foo.bar.module, Coder usesfoo.baras the module name, and the rule usesfoo, the part before the first dot.
drupal/curl-ssl-verify¶
- Level: warning
- Fix: none
- Source: Coder's
DrupalPracticestandard - Ports:
DrupalPractice.FunctionCalls.CurlSslVerifier.SslPeerVerificationDisabled
DrupalPractice asks not to turn off curl's check of the server's SSL certificate.
Reports:
- a
curl_setopt()call that setsCURLOPT_SSL_VERIFYPEERtoFALSEor0, as incurl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE). The rule reads the arguments by position or by name.
Not reported:
- a call that spreads its arguments.
Compared with Coder
- The rule ignores the case of the function name and of
FALSE, and accepts a leading backslash, a value in parentheses and a zero in any integer base. - It reads the whole value, so
FALSE ?: TRUEdoesn't count. A method call doesn't count,$object?->curl_setopt()included. - It reads named arguments. Coder reads the arguments by position only.
- It skips a call with too few arguments and a call that spreads its arguments. Coder stops checking the file at either one.
drupal/escaped-placeholder¶
- Level: warning
- Fix: potentially unsafe. It replaces the call with its argument, which changes the text that users see.
- Source: Coder's
DrupalPracticestandard - Ports:
DrupalPractice.FunctionCalls.TCheckPlain.CheckPlain
The @, % and : placeholders escape their value. A value passed through Html::escape(), or
through check_plain() in Drupal 7, is then escaped twice, and a label such as Q&A shows as
Q&A.
Reports:
Html::escape()orcheck_plain()as the value of an@,%or:placeholder, in the placeholder array oft(),$this->t(),formatPlural(),watchdog(),new TranslatableMarkup(),new PluralTranslatableMarkup(),new TranslationWrapper()ornew FormattableMarkup(). Either array syntax counts.Htmlmust resolve toDrupal\Component\Utility\Html, through an import under any name or written in full;- an escape call that starts a longer value, as in
Html::escape($label) . ' (draft)'.
Not reported:
- an escape call after a
., as in'Draft: ' . Html::escape($label), which escapes only a part of the value; - the value of a
!placeholder, which Drupal 7 inserts as it is.
The fix applies only when the escape call is the whole value and has one argument, with no comment inside its parentheses.
Example
// Reported.
use Drupal\Component\Utility\Html;
$text = t('@label tokens.', ['@label' => Html::escape($label)]);
// Fine.
$text = t('@label tokens.', ['@label' => $label]);
Compared with Coder
- Coder reads only
check_plain(), the Drupal 7 function, int()andwatchdog()with anarray()of placeholders. The rule also readsHtml::escape(), the other calls listed above, a short array,\t()and\check_plain(). - Coder reports a
!placeholder too, where the escape is needed. - Coder reports a
check_plain()anywhere in the array that doesn't have a.right before it, such as one inside another call. The rule reads only the value of each placeholder.
drupal/form-alter-comment¶
- Level: warning
- Fix: none
- Source: Coder's
DrupalPracticestandard - Ports:
DrupalPractice.FunctionDefinitions.FormAlterDoc.Different
DrupalPractice asks that only the module's own hook_form_alter() be documented as
Implements hook_form_alter()..
Reports:
- a function with a docblock line that starts with
Implements hook_form_alter().and a name other than the module name and_form_alter. The module name comes from the file name or the nearest info file, as indrupal/class-prefix.
Not reported:
- a docblock that isn't right above the
functionkeyword, with a modifier, attribute or comment between them.
Example
// Reported, in example.module.
/**
* Implements hook_form_alter().
*/
function example_form_node_form_alter(array &$form) {
}
// Fine.
/**
* Implements hook_form_FORM_ID_alter().
*/
function example_form_node_form_alter(array &$form) {
}
Compared with Coder
- It compares the function name without regard to case, because PHP does.
- It finds the hook line only at the start of a docblock line, so
@see Implements hook_form_alter().isn't a hook line.
drupal/global-constant¶
- Level: warning
- Fix: none
- Source: Coder's
DrupalPracticestandard - Ports:
DrupalPractice.Constants.GlobalConstant.GlobalConstantDrupalPractice.Constants.GlobalDefine.GlobalConstant
DrupalPractice asks for constants on a class or interface, not in the global namespace.
Reports:
- a
conststatement at the top level of a file; - a
define()call at the top level of a.modulefile.
A statement in a class, a function, a closure, an arrow function, a block of if, switch, try,
a loop or declare, or a braced namespace isn't at the top level.
Not reported:
- a statement with a docblock with a
@deprecatedtag right above it. The tag must be at the start of a docblock line, and its name is case-sensitive. A one-line docblock with only the tag counts, and a tag in the middle of a sentence doesn't; - a Drupal 7 module.
Drupal's standard allows a global constant whose name starts with the module name, and asks for
const over define(). DrupalPractice, and this rule, still report it.
Example
// Reported, in example.module.
const EXAMPLE_LIMIT = 10;
// Fine.
class ExampleSettings {
const LIMIT = 10;
}
Compared with Coder
- The rule treats the body of an
if,elseiforelsewritten without braces, and an arrow function, as nested. Coder doesn't see an enclosing scope there. - It also reports
\define()andDEFINE(), which are missed by Coder. It doesn't report$object?->define()ordefine(...). - It keeps the exemption of a
@deprecateddocblock that is above an attribute list.
drupal/request-superglobal¶
- Level: error
- Fix: none
- Source: Coder's
DrupalPracticestandard - Ports:
DrupalPractice.Variables.GetRequestData:SuperglobalAccessed,SuperglobalAccessedWithVar
DrupalPractice asks for the request object in place of the $_GET, $_POST, $_COOKIE, $_FILES
and $_REQUEST superglobals.
Reports:
- a use of
$_GET,$_POST,$_COOKIE,$_FILESor$_REQUEST, including a write, a parameter orglobalname, and a use inside a string.
The message names the matching property of the request, and gives the source text of the key.
Not reported:
- a Drupal 7 module.
Example
// Reported.
$page = $_GET['page'] ?? 0;
// Fine.
$page = \Drupal::request()->query->get('page', 0);
Compared with Coder
- The rule reports a use inside a double-quoted string or a heredoc, which is missed by Coder.
- It treats
$_GET /* note */ ['a']as an access with a key.
drupal/strict-config-schema¶
- Level: error
- Fix: none
- Source: Coder's
DrupalPracticestandard - Ports:
DrupalPractice.Objects.StrictSchemaDisabled.StrictConfigSchema
DrupalPractice asks test classes to keep the strict check of the config schema on.
Reports:
- a
$strictConfigSchemaproperty of a test class, as inprotected $strictConfigSchema = FALSE;, where the first ofTRUE,FALSEandNULLin the default isn'tTRUE, or where there is none.
A test class has Test or Tests as a word of its name. For a property of an anonymous class, the
rule uses the nearest named class or trait.
Not reported:
- a class whose name only contains
Testinside a word, such asTestimonialorTestable; - an anonymous class outside any named class or trait.
Compared with Coder
- Coder takes a class with
Testanywhere in its name, such asTestimonialorTestable, as a test class. - For a property of an anonymous class, Coder uses the outermost scope, and reads the first
name after its
classkeyword, such asFooTestinnew class extends FooTest.
drupal/untranslated-description¶
- Level: warning
- Fix: none
- Source: Coder's
DrupalPracticestandard - Ports:
DrupalPractice.General.DescriptionT.DescriptionT
DrupalPractice asks for a #description text that runs through t(). Users read the description
under a form element, so it must be translatable. There isn't a fix, because a class takes
$this->t() where a function takes t().
Reports:
- a plain string as the value of a
#descriptionkey, in an array of either syntax, or assigned to the key, as in$form['name']['#description'] = 'Some text'. A string counts when it has more than three characters once its HTML tags are gone. The first string of a concatenation counts, as in'Some text ' . $more.
Not reported:
- a string in double quotes that holds a variable, and a heredoc;
- a value that starts with something other than a string, such as
t(), a variable, a ternary or a nested array; - a string added with
.=.
Example
// Reported.
$form['name'] = [
'#type' => 'textfield',
'#description' => 'The name shown on your profile.',
];
// Fine.
$form['name'] = [
'#type' => 'textfield',
'#description' => t('The name shown on your profile.'),
];
Compared with Coder
- For a
#descriptionstring in double quotes that holds a variable, Coder reports the next plain string of the statement instead, which can be the value of another key. The rule skips that string.
drupal/untranslated-options¶
- Level: warning
- Fix: none
- Source: Coder's
DrupalPracticestandard - Ports:
DrupalPractice.General.OptionsT.OptionsValue
DrupalPractice asks for #options labels that run through t().
Reports:
- a plain string label in the
#optionsof an element with#typecheckboxes,radios,selectortableselect. A label counts when it isn't a number and has more than three characters. Labels in nested option groups count.
Not reported:
- an
#optionsarray assigned on its own, as in$form['x']['#options'] = [...].
Example
// Reported.
$form['status'] = [
'#type' => 'select',
'#options' => ['published' => 'Published', 'draft' => 'Draft'],
];
// Fine.
$form['status'] = [
'#type' => 'select',
'#options' => ['published' => t('Published'), 'draft' => t('Draft')],
];
Compared with Coder
- The rule takes the
#typefrom the array that holds the#options, in either order and in either quote style. Coder takes the first'#type'of the statement. - It reports the last label of an array, a label with a comment before its comma, and a label in
an
array()group, which are skipped by Coder, and it doesn't need a comma after the label. It acceptsArray(. - Coder reads a
$form['x']['#options'] = [...]assignment when a'#type'is in the same statement.