forrest79/phpcs

Forrest79 - PHP Coding Standard - PHP Code Sniffer rules

v1.6 2024-03-27 22:41 UTC

This package is auto-updated.

Last update: 2024-04-27 22:58:04 UTC


README

Latest Stable Version Monthly Downloads License Build

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 longer PascalCase.
<?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:
<?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 the array() 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.
  • Types from other namespaces are imported with use.
    • use declarations are separated from namespace 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 of double or real. 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 or public.
  • 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 or public.
  • 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.
  • 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.
  • 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).
<?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.
  • 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 or public.
  • Order of keywords in declaration:
    1. final/abstract,
    2. private/protected/public,
    3. 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 of elseif.
  • case statements in switch are indented with one tab, and their content on following lines again with another tab.
  • case statements in switch 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.

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 of and and or.
  • All keywords are lowercase, exceptions are TRUE, FALSE and NULL.
  • 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.
  • 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 both echo('...') and echo '' 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.
<?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

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> or list<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.
<?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.