Slevomat Coding Standard for PHP_CodeSniffer extends Consistence Coding Standard by providing sniffs with additional checks.
- Functional - improving the safety and behaviour of code
- Cleaning - detecting dead code
- Formatting - rules for consistent code looks
- Installation
- Using the standard as a whole
- Using individual sniffs
- Fixing errors automatically
- Suppressing sniffs locally
- Contributing
๐ง = Automatic errors fixing
๐ง = Sniff check can be suppressed locally
- Checks for missing property types in phpDoc
@var
. - Checks for missing typehints in case they can be declared natively. If the phpDoc contains something that can be written as a native PHP 7.0 or 7.1 typehint, this sniff reports that.
- Checks for missing
@return
and/or native return typehint in case the method body containsreturn
with a value. - Checks for useless doc comments. If the native method declaration contains everything and the phpDoc does not add anything useful, it's reported as useless and can optionally be automatically removed with
phpcbf
. - Some phpDocs might still be useful even if they do not add any typehint information. They can contain textual descriptions of code elements and also some meaningful annotations like
@expectException
or@dataProvider
. - Forces to specify what's in traversable types like
array
,iterable
and\Traversable
. - Distinguishes what's possible in PHP 7.0 (scalar typehints) and PHP 7.1 (nullable types and void return typehint).
Sniff provides the following settings:
enableNullableTypeHints
: enforces to transformFoo|null
in phpDoc into?Foo
in native typehint. It's on by default if you're on PHP 7.1.enableVoidTypeHint
: enforces to transform@return void
into nativevoid
return typehint. It's on by default if you're on PHP 7.1.traversableTypeHints
: enforces which typehints must have specified contained type. E. g. if you set this to\Doctrine\Common\Collections\Collection
, then\Doctrine\Common\Collections\Collection
must always be supplied with the contained type:\Doctrine\Common\Collections\Collection|Foo[]
.usefulAnnotations
: prevents reporting and removing useless phpDocs if they contain an additional configured annotation like@dataProvider
.
In PHP 7.0, a Throwable
interface was added that allows catching and handling errors in more cases than Exception
previously allowed. So if the catch statement contained Exception
on PHP 5.x, it means it should probably be rewritten to reference Throwable
on PHP 7.x. This sniff enforces that.
Enforces having declare(strict_types = 1)
at the top of each PHP file. Allows configuring how many newlines should be between the <?php
opening tag and the declare
statement.
Sniff provides the following settings:
newlinesCountBetweenOpenTagAndDeclare
: allows to set 0 to N newlines to be between<?php
anddeclare
spacesCountAroundEqualsSign
: allows to set number of required spaces around the=
operator
Disallows assignments in if
, elseif
and do-while
loop conditions:
if ($file = findFile($path)) {
}
Assignment in while
loop condition is specifically allowed because it's commonly used.
This is a great addition to already existing SlevomatCodingStandard.ControlStructures.YodaComparison
because it prevents the danger of assigning something by mistake instead of using comparison operator like ===
.
Disallows using loose ==
and !=
comparison operators. Use ===
and !==
instead, they are much more secure and predictable.
Although PHP_CodeSniffer is not suitable for static analysis because it is limited to analysing one file at a time, it is possible to use it to perform certain checks. UnusedPrivateElementsSniff
checks for unused methods, unused or write-only properties in a class and unused private constants. Reported unused elements are safe to remove.
This is very useful during refactoring to clean up dead code and injected dependencies.
Sniff provides the following settings:
alwaysUsedPropertiesAnnotations
: mark certain properties as always used, for example the ones with@ORM\Column
alwaysUsedPropertiesSuffixes
: mark properties with name ending with a certain string to be always marked as used
Looks for unused imports from other namespaces.
Sniff provides the following settings:
searchAnnotations
(defaults tofalse
): enables searching for mentions in annotations, which is especially useful for projects using Doctrine Annotations
Prohibits uses from the same namespace:
namespace Foo;
use Foo\Bar;
This sniffs finds unreachable catch blocks:
try {
doStuff();
} catch (\Throwable $e) {
log($e);
} catch (\InvalidArgumentException $e) {
// unreachable!
}
Commas after last element in an array make adding a new element easier and result in a cleaner versioning diff.
This sniff enforces trailing commas in multi-line arrays and requires short array syntax []
.
Yoda conditions decrease code comprehensibility and readability by switching operands around comparison operators forcing the reader to read the code in an unnatural way.
YodaComparisonSniff
looks for and fixes such comparisons not only in if
statements but in the whole code.
Checks whether uses at the top of a file are alphabetically sorted. Follows natural sorting and takes edge cases with special symbols into consideration. The following code snippet is an example of correctly sorted uses:
use Baz;
use Foo;
use Foo\Bar;
use Foo_Baz;
use Foo_bar;
use Foo1;
use Foo2;
use Foo11;
use Foo22;
use FooBaz;
use Foobar;
use Foobarz;
Enforces using shorthand scalar typehint variants in phpDocs: int
instead of integer
and bool
instead of boolean
. This is for consistency with native scalar typehints which also allow shorthand variants only.
For projects not following the PSR-0 or PSR-4 autoloading standards, this sniff checks whether a namespace and a name of a class/interface/trait follows agreed-on way to organize code into directories and files.
Other than enforcing that the type name must match the name of the file it's contained in, this sniff is very configurable. Consider the following sample configuration:
<rule ref="SlevomatCodingStandard.Files.TypeNameMatchesFileName">
<properties>
<property name="rootNamespaces" type="array" value="app/ui=>Slevomat\UI,app=>Slevomat,build/SlevomatSniffs/Sniffs=>SlevomatSniffs\Sniffs,tests/ui=>Slevomat\UI,tests=>Slevomat"/>
<property name="skipDirs" type="array" value="components,forms,model,models,services,stubs,data,new"/>
<property name="ignoredNamespaces" type="array" value="Slevomat\Services"/>
</properties>
</rule>
Sniff provides the following settings:
rootNamespaces
property expects configuration similar to PSR-4 - project directories mapped to certain namespaces.skipDirs
are not taken into consideration when comparing a path to a namespace. For example, with the above settings, file at pathapp/services/Product/Product.php
is expected to containSlevomat\Product\Product
, notSlevomat\services\Product\Product
.extensions
- allow different file extensions. Default isphp
.ignoredNamespaces
- sniff is not performed on these namespaces
In PHP 7.1 it's possible to declare visibility of class constants. In a similar vein to optional declaration of visibility for properties and methods which is actually required in sane coding standards, this sniff also requires to declare visibility for all class constants.
const FOO = 1; // visibility missing!
public const BAR = 2; // correct
Enforces consistent formatting of return typehints, like this:
function foo(): ?int
Sniff provides the following settings:
spacesCountBeforeColon
- the number of spaces expected between closing brace and colon
Checks whether the nullablity ?
symbol is present before each nullable and optional parameter (which are marked as = null
):
function foo(
int $foo = null, // ? missing
?int $bar = null // correct
) {
}
- Checks that there's a single space between a typehint and a parameter name:
Foo $foo
- Checks that there's no whitespace between a nullability symbol and a typehint:
?Foo
Group use declarations are ugly, make diffs ugly and this sniffs prohibits them.
Enforces fully qualified type references after configurable set of language keywords.
For example with the following setting, extended or implemented type must always be referenced with a fully qualified name:
<rule ref="SlevomatCodingStandard.Namespaces.FullyQualifiedClassNameAfterKeyword">
<properties>
<property name="keywordsToCheck" type="array" value="T_EXTENDS,T_IMPLEMENTS"/>
</properties>
</rule>
This sniff reduces confusion in the following code snippet:
try {
$this->foo();
} catch (Exception $e) {
// Is this the general exception all exceptions must extend from? Or Exception from the current namespace?
}
All references to types named Exception
or ending with Exception
must be referenced via a fully qualified name:
try {
$this->foo();
} catch (\FooCurrentNamespace\Exception $e) {
} catch (\Exception $e) {
}
Sniff provides the following settings:
- Exceptions with different names can be configured in
specialExceptionNames
property. - If your codebase uses classes that look like exceptions (because they have
Exception
orError
suffixes) but aren't, you can add them toignoredNames
property and the sniff won't enforce them to be fully qualified. Classes withError
suffix has to be added to ignored only if they are in the root namespace (likeLibXMLError
).
Prohibits multiple uses separated by commas:
use Foo, Bar;
Enforces to use all referenced names with configurable omissions:
fullyQualifiedKeywords
- allows fully qualified names after certain keywords. Useful in tandem with FullyQualifiedClassNameAfterKeyword sniff.
allowFullyQualifiedExceptions
, specialExceptionNames
& ignoredNames
- allows fully qualified exceptions. Useful in tandem with FullyQualifiedExceptions sniff.
allowPartialUses
- allows using and referencing whole namespaces:
use Foo;
//...
new Foo\Bar();
namespacesRequiredToUse
- if not set, all namespaces are required to be used. When set, only mentioned namespaces are required to be used. Useful in tandem with UseOnlyWhitelistedNamespaces sniff.
allowFullyQualifiedNameForCollidingClasses
- allow fully qualified name for a class with a colliding use statement
allowFullyQualifiedGlobalClasses
- allows using fully qualified names from global space (i.e. \DateTimeImmutable)
Disallows uses of other than configured namespaces.
Sniff provides the following settings:
namespacesRequiredToUse
- namespaces in this array are the only ones allowed to be used. E. g. root project namespace.allowUseFromRootNamespace
- also allow using top-level namespace:
use DateTimeImmutable;
Disallows leading backslash in use statement:
use \Foo\Bar;
Enforces one empty line after opening class/interface/trait brace and one empty line before the closing brace.
Enforces fully qualified names of classes and interfaces in phpDocs - in @var
, @param
, @return
, @throws
. This results in unambiguous phpDocs.
Reports forbidden annotations. No annotations are forbidden by default, the configuration is completely up to the user. It's recommended to forbid obsolete and inappropriate annotations like:
@author
,@created
,@version
- we have version control systems@package
- we have namespaces@copyright
,@license
- it's not necessary to repeat licensing information in each file@throws
- it's not possible to enforce this annotation and the information can become outdated
Sniff provides the following settings:
forbiddenAnnotations
: allows to configure which annotations are forbidden to be usedlinesCountAfterOpeningBrace
: allows to configure the number of lines after opening bracelinesCountBeforeClosingBrace
: allows to configure the number of lines before closing brace
The recommended way to install Slevomat Coding Standard is through Composer.
{
"require-dev": {
"slevomat/coding-standard": "^2.0"
}
}
It's also recommended to install jakub-onderka/php-parallel-lint which checks source code for syntax errors. Sniffs count on the processed code to be syntatically valid (no parse errors), otherwise they can behave unexpectedly. It is advised to run PHP-Parallel-Lint
in your build tool before running PHP_CodeSniffer
and exiting the build process early if PHP-Parallel-Lint
fails.
If you want to use the whole coding standard, besides requiring slevomat/coding-standard
in composer.json, require also Consistence Coding Standard:
{
"require-dev": {
"consistence/coding-standard": "^0.10"
}
}
Then mention both standards in ruleset.xml
:
<?xml version="1.0"?>
<ruleset name="AcmeProject">
<rule ref="vendor/consistence/coding-standard/Consistence/ruleset.xml" />
<rule ref="vendor/slevomat/coding-standard/SlevomatCodingStandard/ruleset.xml" />
<!-- additional settings -->
</ruleset>
To check your code base for violations, run PHP-Parallel-Lint
and PHP_CodeSniffer
from the command line:
vendor/bin/parallel-lint src tests
vendor/bin/phpcs --standard=ruleset.xml --extensions=php --encoding=utf-8 --tab-width=4 -sp src tests
If you don't want to follow the whole standard, but find a handful of included sniffs useful, you can use them selectively.
You can choose one of two ways to run only selected sniffs from the standard on your codebase:
Mention Slevomat Conding Standard in your project's ruleset.xml
:
<?xml version="1.0"?>
<ruleset name="AcmeProject">
<rule ref="vendor/slevomat/coding-standard/SlevomatCodingStandard/ruleset.xml" />
</ruleset>
When running phpcs
on the command line, use the --sniffs
option to list all the sniffs you want to use separated by a comma:
vendor/bin/phpcs --standard=ruleset.xml \
--sniffs=SlevomatCodingStandard.ControlStructures.YodaComparison,SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses \
--extensions=php --encoding=utf-8 --tab-width=4 -sp src tests
Mention Slevomat Conding Standard in your project's ruleset.xml
and list all the excluded sniffs:
<?xml version="1.0"?>
<ruleset name="AcmeProject">
<rule ref="vendor/slevomat/coding-standard/SlevomatCodingStandard/ruleset.xml">
<exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedClassNameAfterKeyword"/>
<exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedExceptions"/>
<exclude name="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly"/>
<exclude name="SlevomatCodingStandard.Namespaces.UseOnlyWhitelistedNamespaces"/>
<exclude name="SlevomatCodingStandard.Types.EmptyLinesAroundTypeBraces"/>
<exclude name="SlevomatCodingStandard.Files.TypeNameMatchesFileName"/>
</rule>
</ruleset>
Then run the remaining sniffs in the usual way:
vendor/bin/phpcs --standard=ruleset.xml --extensions=php --encoding=utf-8 --tab-width=4 -sp src tests
Sniffs in this standard marked by the ๐ง symbol support automatic fixing of coding standard violations. To fix your code automatically, run phpcbf insteand of phpcs:
vendor/bin/phpcbf --standard=ruleset.xml --extensions=php --encoding=utf-8 --tab-width=4 -sp src tests
Always remember to back up your code before performing automatic fixes and check the results with your own eyes as the automatic fixer can sometimes produce unwanted results.
Selected sniffs in this standard marked by the ๐ง symbol can be suppressed for a specific piece of code using an annotation. Consider the following example:
/**
* @param int $max
*/
public function createProgressBar($max = 0): ProgressBar
{
}
The parameter $max
could have a native int
scalar typehint. But because the method in the parent class does not have this typehint, so this one cannot have it either. PHP_CodeSniffer shows a following error:
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
67 | ERROR | [x] Method ErrorsConsoleStyle::createProgressBar()
| | does not have parameter type hint for its parameter $max
| | but it should be possible to add it based on @param
| | annotation "int".
| | (SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint)
If we want to suppress this error instead of fixing it, we can take the error code (SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
) and use it with a @phpcsSuppress
annotation like this:
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
* @param int $max
*/
public function createProgressBar($max = 0): ProgressBar
{
}
To make this repository work on your machine, clone it and run these two commands in the root directory of the repository:
composer install
bin/phing
After writing some code and editing or adding unit tests, run phing again to check that everything is OK:
bin/phing
We are always looking forward for your bugreports, feature requests and pull requests. Thank you.
This project adheres to a Contributor Code of Conduct. By participating in this project and its community, you are expected to uphold this code.