Hygiene
The hygiene section decides on code that can be written more simply with the same meaning: an alias
instead of the function itself, is_null() and intval() instead of a language construct,
dirname(__FILE__) instead of __DIR__, get_class() instead of ::class,
call_user_func() instead of a direct call, two calls of isset() or unset() instead of one,
and an argument that PHP ignores or that only repeats the default.
A language construct is always fast, and the namespace does not matter for it. Inside a namespace, these fixes are risky
fixes: an unqualified is_null() can be the function App\is_null() declared by your project. Until the
configuration says nameResolution: certain,
they wait for your permission.
A direct call instead of call_user_func() is also risky in a file with strict_types:
call_user_func() passes the arguments the way a caller without strict types would, so the direct call can throw a
TypeError where the value used to be converted. This does not apply where PHP itself already turns the call into a
direct one, that is, for a global function without named or unpacked arguments. A parameter passed by reference brings no risk:
PHP already reports a warning for it on every call of call_user_func(), so the only code that changes is code that
already does not work cleanly.
hygiene:
aliasFunctions: [core]
conversionFunction: forbidden
is_null: forbidden
dirnameOfFile: forbidden
classNameNotation:
object: required
call_user_func: forbidden
$count = sizeof($items); // The alias `sizeof()` must be written `count()`.
$id = intval($row['id']); // The `intval()` call must be written as the `(int)` cast.
if (is_null($user)) { // The `is_null()` call must be written as a comparison with `null`.
$dir = dirname(__FILE__); // The `dirname(__FILE__)` call must be written `__DIR__`.
}
$type = get_class($order); // The class name must be obtained with `$order::class`.
$result = call_user_func($callback, $id); // The callable must be called directly, not through `call_user_func()`.
$count = count($items);
$id = (int) $row['id'];
if ($user === null) {
$dir = __DIR__;
}
$type = $order::class;
$result = $callback($id);
The section and the set cleanup are
not the same thing: the set also makes decisions in other sections, such as unused imports or empty comments, and it takes only
part of this section. In --only, fixRisky or severity, you write the section as
hygiene and the set as cleanup. Only the section and the set types share a name, so where a
bare types could mean either, DressCode rejects it: you write the section as types.* and the set as
types.
hygiene.classNameNotation.currentClass
self::class, static::class and parent::class for get_class(),
get_called_class(), get_parent_class() and __CLASS__.
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
required, symfony keep, laravel keep ·
covers SlevomatCodingStandard.Classes.ModernClassNameReference
hygiene.classNameNotation.object
$object::class for get_class($object).
required: always therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
required, symfony keep, laravel keep · covers
get_class_to_class_keyword, SlevomatCodingStandard.Classes.ModernClassNameReference
hygiene.repeatedCatchBody
Two adjacent catches of one body, written as one catch (A|B $e).
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 7.1 · standards: perCs keep, psr12 keep,
nette forbidden, symfony keep, laravel keep
hygiene.unreadCatchVariable
The variable of a catch that is never read, catch (E) written instead.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 8.0 · standards: perCs keep, psr12 keep,
nette forbidden, symfony keep, laravel keep ·
covers SlevomatCodingStandard.Exceptions.RequireNonCapturingCatch
hygiene.aliasFunctions
The sets of functions whose aliases are written by their canonical names.
- a list of these words:
all: every set belowcore: the core functions,sizeof()iscount(),join()isimplode()imap:imap_*ldap:ldap_*mysqli:mysqli_*pg:pg_exec()ispg_query()oci:oci_*odbc:odbc_*openssl:openssl_*sodium:sodium_*ftp:ftp_quit()isftp_close()posix:posix_errno()isposix_get_last_error()pcntl:pcntl_errno()ispcntl_get_last_error()time:mktime()andgmmktime()without arguments aretime()
keep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
[core, imap, pg], symfony keep, laravel [core, imap, pg] ·
covers no_alias_functions
hygiene.call_user_func
call_user_func($f, $x) is $f($x), call_user_func_array($f, $a) is
$f(...$a).
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · PHP 7.0 · standards: perCs keep, psr12 keep,
nette forbidden, symfony keep, laravel keep ·
covers regular_callable_call
hygiene.call_user_funcMethods
Whether a method the callable names as [$this, 'run'], [self::class, 'make'] or
'Acme\Shop\Order::make' is called directly too.
yesorno
Default no · PHP 7.0 · standards: perCs no, psr12 no, nette
no, symfony no, laravel no
hygiene.conversionFunction
intval($x) is (int) $x; intval($x, 16) is no cast.
Not about which name of floatval() the code writes, doubleval() being an alias
hygiene.aliasFunctions renames too.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
forbidden, symfony keep, laravel keep · covers modernize_types_casting
hygiene.dirnameOfFile
dirname(__FILE__), which is __DIR__.
forbidden: never therekeep: 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_useless_dirname_call, combine_nested_dirname, dir_constant
hygiene.nestedDirname
A dirname() of a dirname(), which is one call with the levels argument.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
forbidden, symfony keep, laravel keep
hygiene.__invokeCall
$f->__invoke($x) is $f($x).
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
forbidden, symfony keep, laravel keep ·
covers SlevomatCodingStandard.PHP.DisallowDirectMagicInvokeCall
hygiene.is_null
is_null($x) is $x === null.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
forbidden, symfony keep, laravel keep · covers is_null
hygiene.strlenEmptyTest
strlen($s) === 0 is $s === ''.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · needs types · standards: perCs keep, psr12 keep,
nette forbidden, symfony keep, laravel keep · covers
PhpCsFixerCustomFixers/no_useless_strlen, string_length_to_empty
hygiene.settype
settype($x, 'int') as a statement is $x = (int) $x.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
forbidden, symfony keep, laravel keep · covers set_type_to_cast
hygiene.defaultArgument
An argument PHP ignores or that repeats the default.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
forbidden, symfony keep, laravel keep
An argument that only repeats the default is always removed from a function call. For a method, a static method and
new, DressCode needs the types of the code: it must know every class the
receiver can be, and each of them must declare the same default. It ignores a child class that might change the default, because
such a class would break the contract of its type. A receiver of an unknown type, or one that can be several classes with
different defaults, stays as it is, and without types, methods and new stay as they are everywhere.
typeAnalysis: phpstan
hygiene:
defaultArgument: forbidden
$json = json_encode($data, 0); // Useless `flags` argument of `json_encode()`, because it repeats the default value.
$memcached->delete($key, 0); // Useless `time` argument of `Memcached::delete()`, because it repeats the default value.
$json = json_encode($data);
$memcached->delete($key);
hygiene.separateIssets
isset($a) && isset($b) is isset($a, $b).
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
forbidden, symfony keep, laravel keep ·
covers combine_consecutive_issets
hygiene.separateUnsets
unset($a); unset($b); is unset($a, $b);.
forbidden: never therekeep: nothing is enforced, the code stays as it is
Default keep · standards: perCs keep, psr12 keep, nette
forbidden, symfony keep, laravel keep ·
covers combine_consecutive_unsets