forrest79 / phpcs
Forrest79 - PHP Coding Standard - PHP Code Sniffer rules
Installs: 10 811
Dependents: 13
Suggesters: 0
Security: 0
Stars: 0
Watchers: 2
Forks: 0
Open Issues: 0
Requires
- php: ^8.0
- slevomat/coding-standard: ^8.15
Requires (Dev)
- forrest79/phpcs-ignores: ^0.3
- nette/tester: ^2.5
- phpstan/phpstan: ^1.10
- phpstan/phpstan-strict-rules: ^1.5
README
Based on (no more developed) https://github.com/consistence/coding-standard and work of https://github.com/klapuch. Thanks!
Installation
The recommended way to install Forrest79 PHP Coding Standard is through Composer:
composer require --dev forrest79/phpcs
Forrest79 PHP Coding Standard requires PHP 8.0.
How to use it:
- Create your CS XML and include this Forrest79CodingStandard:
<?xml version="1.0"?> <ruleset name="MyOwnCS"> <rule ref="./Forrest79CodingStandard/ruleset.xml"/> </ruleset>
- Or you can use version with fully qualified global functions (
\
prefix before global functions i.e.\is_array($x)
) and constants (\
prefix before global constants i.e.\FILE_APPEND
):
<?xml version="1.0"?> <ruleset name="MyOwnCS"> <rule ref="./Forrest79CodingStandard/ruleset-fully-qualified-global.xml"/> </ruleset>
- You can use it and ignore some rules.
<?xml version="1.0"?> <ruleset name="MyOwnCS"> <rule ref="./Forrest79CodingStandard/ruleset.xml"> <exclude name="Forrest79CodingStandard.Exceptions.ExceptionDeclaration.NotChainable"/> </rule> </ruleset>
- Or just concrete files.
<rule ref="Forrest79CodingStandard.Exceptions.ExceptionDeclaration.NotChainable"> <exclude-pattern>tests/Sniffs/TestCase.php</exclude-pattern> </rule>
PSR-4 settings
- Recommend is to proper set PSR-4 via these settings:
<rule ref="SlevomatCodingStandard.Files.TypeNameMatchesFileName"> <properties> <property name="rootNamespaces" type="array" value=" apps/home/app/src=>Home\App, apps/home/tests/src=>Home\Tests, packages/internals/src=>Forrest79, tools/src=>Tools, "/> </properties> </rule>
- key is directory, value is namespace
Custom sniffs
Exceptions\ExceptionDeclarationSniff
Forrest79CodingStandard.Exceptions.ExceptionDeclarationSniff.NotEndingWithException
Or exceptions should have Exception
suffix.
Forrest79CodingStandard.Exceptions.ExceptionDeclarationSniff.NotChainable
Exceptions should be chainable, last constructor argument must be \Throwable
.
Forrest79CodingStandard.Exceptions.ExceptionDeclarationSniff.IncorrectExceptionDirectory
All exceptions should be in Exceptions
subdirectory. You can change subdirectory name via settings:
<rule ref="Forrest79CodingStandard.Exceptions.ExceptionDeclaration"> <properties> <property name="exceptionsDirectoryName" type="string" value="exceptions"/> </properties> </rule>
Methods\MethodStructureSniff
Forrest79CodingStandard.Methods.MethodStructureSniff.AlphabeticalOrder
Check if class has public methods in alphabetical order. Set files via settings:
<rule ref="Forrest79CodingStandard.Methods.MethodStructure"> <properties> <property name="checkFiles" type="array" value=" apps/home/app/src/Configurator.php, "/> </properties> </rule>
NamingConventions\MethodStructureSniff
Forrest79CodingStandard.NamingConventions.ValidVariableNameSniff.NotCamelCaps
Check camel caps variable names.
Nette\FinalInjectMethodSniff
Forrest79CodingStandard.Nette.FinalInjectMethodSniff.MissingFinal
For Nette Framework. If some presenter or other object uses inject functionality, class should be final
or inject
methods should be final
.
Nette\ParentCallSniff
Forrest79CodingStandard.Nette.ParentCallSniff.MissingParent
For Nette Framework. In presenters, all beforeRender
methods should call their parent.
Nette\PresenterMethodsSniff
Forrest79CodingStandard.Nette.PresenterMethodsSniff.NotProtected
For Nette Framework. In presenters, all startup
, beforeRender
and createComponent
methods should be protected
.
PHP\CorrectCaseTypeSniff
Forrest79CodingStandard.PHP.CorrectCaseType
Checks that all PHP types are lowercase except NULL
, TRUE
and FALSE
that must be uppercase. Based on Generic.PHP.LowerCaseTypeSniff
.
General naming conventions
- Avoid abbreviations, use them only if long name would be less readable.
- For 2-letter shortcuts use
UPPERCASE
, for longerPascalCase
.
<?php use Foo\IP\Bar; use Foo\Php\Bar; use Foo\UI\Bar; use Foo\Xml\Bar;
General formatting conventions
- Tab indentation is used.
- Files end with a single blank line
\n
. - Unix-style (LF) line endings
\n
are used. - If there is a list of information, where ordering has no semantic meaning, the list is sorted alphabetically.
- Sorting concatenated words (e.g.
PascalCase
) takes into account original words:
- Sorting concatenated words (e.g.
<?php use LogAware; use LogFactory; use LogLevel; use LogStandard; use LogableTrait; use LoggerInterface;
PHP files
- Contains only PHP code (no inline HTML etc.).
- File does not have the closing tag
?>
. - There are no characters (including BOM) before the PHP opening tag.
- Long opening tags are used (always
<?php
, never<?
). - There is one empty line after the line with the open tag.
- File either declares new symbols (classes, functions, constants, etc.) and causes no other side effects, or executes logic with side effects, but should not do both.
- Uses strict typing by enabling
declare(strict_types = 1);
.- This declaration is placed right behind the opening tag.
- There is no space on each side of the
=
operator.
<?php declare(strict_types=1); namespace Forrest79;
Strings
- Common strings are written using apostrophes (
'
). Only strings containing control characters (such as\n
) may use double quotes ("
). - For concatenation of mixed strings and variables
sprintf()
is used. If in-place strings are not needed concatenation is done with only concatenation operator (.
)..
is surrounded by one space on each side, unless it is on the beginning of the line.- Strings do not contain variables (
"Hello $name!"
).
<?php sprintf('%s/%s', $dir, $fileName); // vs $foo . $bar;
'SELECT `id`, `name` FROM `people`' . 'WHERE `role` = 1' . 'ORDER BY `name` ASC';
Arrays
- Short array syntax is used (
[
,]
) instead of thearray()
language construct.
Namespaces
- Namespaces are written in
PascalCase
. - Each file contains only one
namespace
declaration applied to the whole file.- Before the line with
namespace
there is one empty line.
- Before the line with
- Types from other namespaces are imported with
use
.use
declarations are separated fromnamespace
declaration with one empty line.- There is only one type imported per
use
declaration (one import per line). use
declarations are sorted alphabetically.use
declarations never begin with backslash (\
).
<?php namespace Forrest79; use Forrest79\Bar; use Forrest79\Foo; use DateTime; use Lorem\Amet; use Lorem\Ipsum\Dolor\Foo as DolorFoo; use Lorem\Sit; use ReflectionClass; use ReflectionMethod;
Types
- Type names are written in
PascalCase
. - Type names are nouns.
- Types are placed in namespaces (not global space).
- Opening brace and closing brace of type are always on a separate line.
- All parts of types are indented with one tab.
- Only one type per file is defined, name of this file has the same name as the type.
- Multiple types referenced in
implements
are separated with comma and one space and are ordered alphabetically. - Types referenced in code are always referenced using
Foo::class
syntax, never using a string. - If the referenced type in static access is the "current" one,
self
/static
is used instead of type name. - If type can be null, always use
|NULL
not?
-string|NULL
instead of?string
.
Interfaces
- Interfaces are never prefixed with
I
.
Scalar types
- Short type names are used in code (
int
,bool
). This also applies to PHP functions which offer both variants.
<?php if (!is_int($foo)) { return (int) $foo; }
float
is always used instead ofdouble
orreal
. This also applies to PHP functions which offer both variants.
Variables
- Variable names are written in
camelCase
. global
keyword is never used to declare global variables.
Properties
- All variables rules apply.
- All properties have explicitly declared visibility with
private
,protected
orpublic
. var
is never used.- Only one property is declared per statement.
Constants
- Constant names are written in
UPPER_CASE
. - Constants are defined only inside classes using
const
, global constants are never defined. - Constants have explicitly declared visibility with
private
,protected
orpublic
. - If there are more constants, that "belong together", empty lines between them may be omitted.
<?php class Foo { const FOO = 'foo'; const VISIBILITY_PRIVATE = 'private'; const VISIBILITY_PROTECTED = 'protected'; const VISIBILITY_PUBLIC = 'public'; }
Functions
- Function names are written in
camelCase
.- Calls to built-in PHP functions are exception to this rule and are written in
snake_case
.
- Calls to built-in PHP functions are exception to this rule and are written in
- There is no space between the function name and the opening parenthesis.
- Opening brace and closing brace of type are always on separate line.
- Exception: anonymous functions.
- Global functions are never declared, they should be defined inside a (static) class.
- Named functions (not anonymous) are never declared inside other functions.
Argument list
- There should be type hint defined whenever possible (including scalar type hints).
- Nullable types (
string|NULL
) are used to allow passing null to a type hinted argument.
- Nullable types (
- There is no space after the opening parenthesis, and there is no space before the closing parenthesis.
- Arguments both in function declaration and in function call are separated with comma, followed by one space (
,
).
<?php class X { public function __construct(Foo $foo, string $string) { // ... } }
- Function declaration and call with arguments on multiple lines:
- There is only one argument per line.
<?php class X { public function __construct( Foo $foo, string $string, ) { // ... } } new X( $foo, $string, );
- Default argument values are used only when needed to either express optional argument (only at the end of the list) or to allow passing null to a type hinted argument.
- Nullable types (
string|NULL
) are used to allow passing null to a type hinted argument and therefore default arguments are used only for optional arguments. - For scalar arguments default arguments are used only for optional arguments, not to allow passing nulls (see detailed example below).
- Nullable types (
<?php class X { /** * @param \Foo $a required type argument * @param \Foo|NULL $b required argument, but nullable type needed * @param string $c required scalar argument * @param string|NULL $d required argument with nullable scalar * @param string $e optional nullable scalar argument * @param string|NULL $f optional nullable scalar argument */ public function __construct( Foo $a, Foo|NULL $b, string $c, string|NULL $d, string $e = '', string|NULL $f = NULL ) { // ... } }
- Variadic argument is written in this format:
@param \Foo ...$foo
.
Return type
- There should be type hint defined whenever possible (including scalar type hints).
- Nullable types (
string|NULL
) are used to allow returning null.
- Nullable types (
- There is no space after the closing parenthesis, colon immediately follows and then there is one space between the colon and the type.
<?php class X { public function getFoo(): Foo { // ... } }
- When there is nothing to return,
void
return type must be specified.
<?php class X { public function process(Foo $foo): void { $foo->bar(); } }
- When method interrupt script,
never
return type must be specified.
<?php class X { public function process(Foo $foo): never { exit(1); } }
Anonymous functions
- There is a space between the
function
keyword and the opening parenthesis. - Opening brace is NOT placed on the next line.
- There is one space before and after the
use
keyword.
<?php array_walk($foo, function (Item $item) use ($bar): string { // ... });
Methods
- All methods have explicitly declared visibility with
private
,protected
orpublic
. - Order of keywords in declaration:
final
/abstract
,private
/protected
/public
,static
- Constructor is always defined with
__construct
name, never using the old PHP behavior - with name same as class name. - There is one new line before and after each function (so two new lines between two functions).
<?php class X { final public static function foo(): void { // ... } abstract public static function bar(): void { // ... } }
Control structures
- Conditional statement is surrounded by parentheses.
- There is no space after/before parentheses inside the statement.
- There is one space before/after parentheses around the statement.
- Opening brace is placed on the same line as the conditional statement.
else if
is used instead ofelseif
.case
statements inswitch
are indented with one tab, and their content on following lines again with another tab.case
statements inswitch
end with a colon:
.
<?php if ($foo) { // ... } if ( $foo && $bar ) { // ... } switch ($foo) { case 1: case 2: // ... break; default: // ... }
- In
switch
, there must be a comment such as// no break
when fall-through is intentional in a non-empty case body. - Empty bodies of control structures are forbidden.
- Exception is
catch
, but there must be a comment explaining situation.
- Exception is
Expressions
- After all operators, there is one space. Before operators, there is one space too, unless it is on the beginning of a line.
- Logical operators
&&
and||
are always used instead ofand
andor
. - All keywords are lowercase, exceptions are
TRUE
,FALSE
andNULL
. - Strict comparisons are used by default (
===
), if there is need for==
, usually a comment should be given explaining situation.- Magic PHP type conversions should be avoided - WRONG:
($foo)
, CORRECT:($foo !== NULL)
- only expressions already containing boolean values should be written in($foo)
form. - The same applies for
empty
function, so it is forbidden.
- Magic PHP type conversions should be avoided - WRONG:
- Yoda conditions should not be used.
- If expression needs to be written on multiple lines, operators belong on the beginning of the line.
<?php if ( ($lorem >= 3 && $lorem <= 5) || $ipsum !== NULL ) { // ... }
- There is always only one statement per line.
- One blank line may be used to separate other statements.
- If there are multiple method calls in a row, and it is needed to write this on multiple lines, all the method calls are indented (including the first one).
<?php $lorem ->ipsum() ->dolor() ->sit() ->amet();
- Parentheses in
new
statements should be always present, even if there are no arguments for constructor.
<?php new Foo();
- There is one space after type cast and no space inside the parentheses.
(binary)
and(unset)
casts are forbidden.
- For increments and decrements respective operators
++
/--
are used instead of "manual" addition/subtraction. - All static symbols should be accessed only with
::
, never using$this
. echo
,print
, ... allowing bothecho('...')
andecho ''
syntax are always used without parentheses and with one space after the keyword.
Closures and callables
- Closure is preferred to use instead of a callable (callable might be required while implementing a third party interface though).
- Closures are invoked using
$closure()
instead of using functions.call_user_func
should never be needed.call_user_func_array
is not needed since PHP 5.6 - argument unpacking was introduced.
<?php function foo($foo, Closure $callback): void { // ... $callback($bar); // ... } foo('foo', function (Bar $bar): void { // ... });
<?php function foo($foo, Closure $callback): void { // ... $callback(...$barArray); // ... } foo('foo', function (Bar ...$bars): void { // ... });
Exceptions
- Type name always ends with Exception.
- Exceptions are placed in a separate directory called
Exceptions
. - Class name describes the use-case and should be very specific.
- Inheritance is used for implementation purposes (not for creating hierarchies) - such as
Forrest79\PhpException
, where$code
argument is skipped. - Constructor requires only arguments, which are needed, the rest of the message is composed in the constructor.
- All exceptions should support exceptions chaining (allow optional
\Throwable
as last argument). - Arguments should be stored in private properties and available via public methods, so that exception handling may use this data.
- All exceptions should support exceptions chaining (allow optional
<?php namespace Forrest79\Foo; use Forrest79; class LoremException extends Forrest79\PhpException { private strinf $lorem; public function __construct(string $lorem, \Throwable $previous = NULL) { parent::__construct(sprintf('%s ipsum dolor sit amet', $lorem), $previous); $this->lorem = $lorem; } public function getLorem(): string { return $this->lorem; } }
##Commenting
//
inline style comments are used, never#
.- Inline comment has a space between
//
and the text. - If there is no explicit need to write something, it should be omitted - well named classes, variables, methods and arguments should be preferred.
- "Commented out" code is never present.
PHPDoc
Structure for types and methods:
<?php /** * Short description - one line (optional) * * Long description (optional) * * Documentation annotations (optional) * * Code analysis annotations (optional) * * Application annotations (optional) * * @param string $foo only if some optional description is needed * @param int $bar only if some optional description is needed * @return bool only if some optional description is needed * @throws \MyException\BarException * @throws \MyException\FooException */ public function myMethod(string $foo, int $bar): bool;
Structure for properties and constants:
<?php /** * Short description - one line (optional) * * Long description (optional) * * Documentation annotations (optional) * * Code analysis annotations (optional) * * Application annotations (optional) * * @var string optional description */ private $foo;
Annotation blocks
- Different types of annotations are grouped together (separated from other blocks by an empty line).
- See structure above.
Short+long description
- Optional.
- If there is no explicit need to write something, it should be omitted - well named classes, variables, methods and arguments should be preferred.
- Descriptions which only rephrase method names (etc.) are never written.
- Clear code is better than long explanation.
- Short description has only one line (with no dot at the end).
- Long description may contain example usage.
- In long description formatting may be used (e.g. with HTML).
Documentation annotations
- Optional.
- PHPDoc's annotations with documentation metadata like
@author
,@copyright
,@see
,@link
, ...
Code analysis annotations
- Are never user, use PHPCS-Ignores when needed.
Application annotations
- Optional.
- Annotations, that have functional significance for the application, such as Symfony, Doctrine and custom annotations.
Multi-line annotations
- Follows general formatting rules for dealing with separating statements to multiple lines.
- Two spaces are used for indentation.
/** * @ManyToMany(targetEntity="Phonenumber") * @JoinTable( * name="users_phonenumbers", * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, * inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}, * ) * @Foo **/
Allowed types for @param, @return, @var
List of allowed types (long variants are used):
int
bool
string
float
resource
null
object
mixed
- array/collection (see below)
- type (see below)
Multiple different types are separated with |
.
Mixed
- Used when nothing is known about the type.
Array/collection
- Written as
array<int, type>
,array<string, type>
orlist<type>
. - If the values are of more than one type, then
array<int, mixed>
is used (also if there is no knowledge about the types). - If associative array is expected (or a Map), in description, there should be description of used format, such as
array<string, string> $names format: lastName(string) => firstName (string)
. - If there are more nested arrays/collections, this is expressed with more
array<>
, e.g.array<int, array<string, integer>>
means array of arrays of integers.
Type
- FQN with a leading backslash.
- If the referenced type is the "current" one,
self
/static
is used instead of type name.
<?php use DateTime; use DateTimeImmutable; /** * @param DateTimeImmutable $date calendar date * @param array<string> $events * @param int|NULL $interval * @return DateTime */ public function myMethod(DateTimeImmutable $date, array $events, int $interval = NULL): DateTime { // ... }
@param
- Can be omitted when type hint is the same as annotation.
- Annotations are in the same order as defined in the argument list.
<?php use DateTime; /** * @param string $foo optional description * @param int $bar optional description * @param DateTime ...$dates optional description */ public function myMethod(string $foo, int $bar, DateTime ...$dates) { // ... }
@return
- If there are no
return
statements in the method,@return
is not present. - Can be omitted when type hint is the same as annotation.
@var
- If the
@var
annotation is the only annotation and there is no long description in the PHPDoc, then one-line format is used:
<?php /** @var string optional description */ private $foo;
- Inline
@var
is used to define types for variables, where the type is not clear.- Uses docblock comment
/** ... */
. - Type is defined first, followed by variable name.
- Uses docblock comment
<?php /** @var Foo $foo optional description */ $foo = $container->getService('foo');
@throws
- For implemented methods
@throws
is never used. @throws
is only used in interfaces or abstract methods as part of the defining contract.@throws
annotations are sorted alphabetically (according to the exception name).
Constants
@var
is not used for constants (type is defined by its value).- If there is no need for other annotations or description, then PHPDoc is omitted.