PHP 8.6 vychází 19. listopadu. DressCode pro něj kód přepisuje už teď. 16 dní Přechod na novější PHP →

Standardy a sady

Co přesně předepisuje PSR-12, PER Coding Style 3.1, Nette, Symfony nebo Laravel? Nejlépe vám odpoví samotný konfigurační soubor daného presetu – má totiž naprosto stejnou strukturu jako vaše vlastní konfigurace. Na této stránce najdete každý z nich v plném znění, včetně komentářů objasňujících důvody jednotlivých voleb. Standard definuje celkový vizuální vzhled kódu a projekt si z nich vybere právě jeden; sada reprezentuje konkrétní záměr a můžete ji libovolně připojit k jakémukoliv standardu.

Každý preset vychází z předchůdců uvedených v jeho klíči use a upravuje pouze to, co sám výslovně přepíše: standard nette tedy staví na perCs a ten vychází z psr12. Výslednou efektivní konfiguraci včetně původu jednotlivých hodnot vám kdykoliv vypíše příkaz dresscode config --preset nette. Způsob skládání presetů s vaší projektovou konfigurací detailně rozebírá stránka Konfigurace; význam každého klíče popisuje katalog Pravidla.

Standardy

Pětice vestavěných standardů komplexně určuje vše, co v kódu vidíte: odsazení, umístění závorek, mezery, prázdné řádky i pravidla zalamování. Standardy psr12 a perCs jsou napsány striktně podle oficiálních specifikací PHP-FIG, oddíl po oddílu, a nepřidávají nic vlastního. Standardy nette, symfony a laravel staví na základech PER Coding Style a doplňují zvyklosti a požadavky svých ekosystémů.

Řádek přesahující limit file.lineLength standard automaticky zalomí všude tam, kde to syntaxe dovoluje. Kde zalomení provést nelze (například u dlouhých textových řetězců nebo komentářů), tam psr12 a perCs hlásí pouze varování, protože PSR-12 považuje limit 120 znaků za doporučený (soft limit). Standard nette jej naproti tomu hlásí jako striktní chybu, zatímco symfony a laravel délku řádků nehlídají vůbec.

psr12

Standard PSR-12, oddíl po oddílu přesně podle specifikace PHP-FIG.

# PSR-12 Extended Coding Style
#
# PSR-12, section by section; the specification defines the style and this preset adds nothing of its own.

severity:
	file.longLines: warning                   # what nothing could spread only warns, the limit being soft

file:
	bom: forbidden
	openingTag: required                      # `<?php`, not `<?` or `<?PHP`
	strictTypes:
		position: nextLine                    # §3, `<?php` on a line of its own
	closingTagAtEnd: forbidden                # a `?>` before HTML stays
	lineEnding: majority                      # as most lines of the file, LF on a tie
	finalLineEndings: 1
	trailingWhitespace: forbidden             # the text of a string stays
	lineLength: 120                           # what is wider is spread
	longLines: forbidden
	statementsPerLine: 1                      # `foo(); bar();` takes two lines

indentation:
	unit: '4 spaces'
	binaryOperator: 0                         # `&& $b` right below `$a`, a level deeper after `$x =` or `return`
	ternary: 1                                # `?` and `:` opening a line
	switchCase: 1                             # `case` inside `switch`
	chain: flat                               # every link one level below the start of the chain
	commentLines: aligned

braces:
	position:
		class: nextLine
		function: nextLine                    # one with parameters on one line
		multilineSignature: sameLine          # `) {` on a line of their own
		closure: sameLine
		anonymousClass: sameLine
		controlStructure: sameLine
		continuingKeyword: sameLine           # `} else {`
	emptyBody:                                # not `{}`
		class: expanded
		function: expanded
		closure: expanded
		propertyHook: expanded
	bracelessBody: forbidden                  # `if ($a) foo();` gets braces
	singlelineClosure: forbidden              # `function () { return 1; }` is spread

blankLines:
	afterOpeningTag: 1
	afterDeclare: 1
	beforeNamespace: 1
	afterNamespace: 1
	afterImports: 1
	beforeDeclaration: 1
	betweenImports: 0
	betweenImportKinds: 1
	beforeOpenBrace:
		class: 0                              # §4.1: no blank line between the head and `{` below it
	bodyStart:
		class: 0
		anonymousClass: 0
		controlStructure: 0                   # §5: the body on the next line after `{`
	bodyEnd:
		class: 0
		anonymousClass: 0
		function: 0                           # §4.4, §7: `}` on the next line following the body
		closure: 0
		controlStructure: 0                   # §5
		branch: 0
	betweenTraitUses: 0
	afterTraitUses: 1

multiline:
	items:
		argument: perLine                     # an argument per line once spread
		parameter: perLine                    # a parameter per line once spread or too wide
		for: perLine                          # a section of `for` per line
		implements: perLine                   # an interface per line below `implements`
		matchCondition: perLine               # a condition of a `match` arm per line, `=>` after the last
		# the parts of a condition and the operands as their author spread them, a line each where too wide
		condition: [perLine, fill]
		logical: [perLine, fill]
		concatenation: [perLine, fill]
		arithmetic: [perLine, fill]
		bitwise: [perLine, fill]
	edges:
		argument: apart                       # `foo(` above the arguments, `)` below them
		parameter: apart
		implements: apart
		condition: apart                      # `if (` above `$a`, `) {` below the last part
	operatorPosition:                         # `+ $b` opens the line, not ends the one before
		operators: lineStart
		concatenation: keep
		ternary: keep

spacing:
	declare: 'declare(strict_types=1)'
	call: 'foo($a, $b)'                       # `function foo($a)` too
	padding:                                  # `foo($a)`, `if ($a)`, `($a + $b)`
		argument: forbidden
		parameter: forbidden
		closureUse: forbidden
		controlStructure: forbidden
		grouping: forbidden
	keyword: required                         # `return $x`, `if (`, `} else {`, `new`, `class Foo extends Bar`
	openBrace: required                       # `if ($a) {`, `function () {`
	alternativeSyntax: 'if ($a):'
	body: required                            # `{ return 1; }`, `{ get; set; }`
	import: 'use Acme\{Foo, Bar};'
	comma: '$a, $b'
	alignment:
		comma: forbidden                      # no extra spaces after a comma
	cast: '(int) $x'
	reference: '&$x'
	spread: '...$x'
	unaryOperator: '-$x'                      # `++$i` too
	logicalNot: ['!$x', '! $x']               # `! $x` may stay
	binaryOperator: '$a + $b'                 # `=`, `=>`, `instanceof` too
	concatenation: '$a . $b'
	ternary: '$a ? $b : $c'                   # `$a ?: $b` too
	anonymousClass: 'new class ($a)'
	anonymousFunction: 'function ($x)'
	typeDeclaration: '?int $x'                # `): int` too
	catchType: 'catch (A | B $e)'
	switchCase: 'case 1:'

# names are reported, never renamed
naming:
	class: PascalCase
	method: camelCase                         # `__construct()` passes
	constant: UPPER_CASE

casing:
	keyword: lowercase                        # `self`, `parent` too
	trueFalseNull: lowercase
	type: lowercase                           # `int`, `string`, `void`

imports:
	leadingBackslash: forbidden               # `use Foo\Bar;`, not `use \Foo\Bar;`
	order:
		kinds: [class, function, constant]    # the names of a kind in the order written

classes:
	visibility: required                      # `var` is `public`; interface methods and set visibility too
	modifiers:
		order: canonical                      # `final public static`
	members:
		order: [traitUse]
		grouping:                             # `public $a, $b;` is split
			property: separated
			traitUse: separated
	emptyParentheses:
		instantiation: required               # `new Foo()`

controlFlow:
	elseif: 'elseif'                          # not `else if`
	switch:
		fallThroughComment: no break          # the comment of a fall-through
	emptyStatement: forbidden                 # `foo();;`

expressions:
	cast: canonical                           # `(int)`, not `(integer)` or `( int )`

upgrading:
	php:
		caseSemicolon: forbidden              # `case 1:`, not `case 1;`

perCs

Standard PER Coding Style 3.1, přímý nástupce PSR-12: přináší koncové čárky u víceřádkových seznamů, zkrácené anonymní funkce (arrow functions), zkrácená prázdná těla {}, atributy, výčty (enumy), heredoc a property hooky.

# PER Coding Style 3.1
#
# PSR-12 plus what PER Coding Style added (trailing commas, short closures, empty bodies as `{}`, attributes,
# enumerations, heredoc, property hooks); the specification defines the style and this preset adds nothing of its own.

use: psr12

indentation:
	heredoc: 1                               # body and closing marker one level deeper than the start

braces:
	emptyBody:                               # `function foo(): void {}`, `new class {}`
		class: collapsed
		function: collapsed
		closure: collapsed
		propertyHook: collapsed
		anonymousClass: collapsed

blankLines:
	afterPhpdoc: 0                           # §12.2: no blank line between the docblock,
	afterAttributes: 0                       # the attributes and the structure

multiline:
	items:
		array: perLine                       # an item per line once spread
		list: perLine
		chain: perLine                       # a link per line once spread
		ternary: perLine                     # `?` and `:` each on a line once spread
	edges:
		array: apart                         # `[` above the items, `]` below them
		list: apart
		chain: apart                         # `$form` above `->addText()`
	operatorPosition:
		concatenation: lineStart             # `. $b` opens the line
		ternary: lineStart                   # `? $b` and `: $c` open the lines
	# a comma after the last item where the closing bracket has a line of its own, none on one line
	trailingComma:
		array: required
		argument: required
		parameter: required
		matchArm: required
		closureUse: required
		import: optional
		list: optional
	attributePosition: above                 # a group per line above the declaration, and above a parameter in a list spread over lines

spacing:
	attribute: '#[Foo($a)]'
	arrowFunction: 'fn($x) => $x'
	semicolon:
		before: '$a;'
	catchType: 'catch (A|B $e)'              # as a union type
	namedArgument: 'foo(name: $x)'

naming:
	enumCase: PascalCase                     # reported, never renamed

classes:
	visibility:
		publicWithSet: keep                  # `public` may be omitted with a set visibility
	members:
		grouping:
			constant: separated              # `const A = 1, B = 2;` is split
	emptyParentheses:
		anonymousClass: forbidden            # `new class {}`, not `new class() {}`
		attribute: forbidden                 # `#[Foo]`, not `#[Foo()]`
	instantiationWrapping: forbidden         # `new Foo()->bar()`, not `(new Foo())->bar()`, from PHP 8.4
	protectedInEnum: forbidden               # `private const X`

functions:
	arrowFunction:
		emptyBody: required                  # `fn() => null`, not `function () {}`

types:
	nullable: '?int'                         # not `int|null`
	nullPosition: last                       # `int|string|null`

literals:
	heredocWithoutInterpolation: forbidden   # a nowdoc `<<<'EOT'` instead
	arraySyntax:
		array: short                         # `[1, 2]`, not `array(1, 2)`

phpdoc:
	aboveAttributes: required

nette

Nette Coding Standard představuje PER Coding Style obohacený o tabulátory, typické rozvržení knihoven Nette, zápis declare(strict_types=1) přímo na řádku s <?php a důkladnou hygienu kódu. Kromě perCs integruje sady cleanup, types a correctness, přičemž několik jejich pravidel vrací na keep: větev else následující po return, ladicí volání, třídy s __toString() bez rozhraní Stringable, oba způsoby zápisu polí v phpDoc a nativní typové deklarace parametrů a vlastností. Pokud projekt tyto sady uvede v use vedle standardu nette, vypnutá rozhodnutí se znovu neaktivují (každá sada se uplatní pouze jednou pod standardem); v případě zájmu si je v konfiguraci zapnete sami.

# Nette Coding Standard
#
# PER Coding Style with tabs and the layout of the Nette libraries, the hygiene of the code, and the choices the Nette
# libraries make: strict types declared on the line of the opening tag, how names are cased, in what order the members
# of a class stand, which functions and constructs are out, and what a doc comment of theirs holds. The layout departs
# from PER Coding Style in two blank lines between methods, none inside the braces of a body, one closing a branch or
# a case another one follows when its last statement stands apart, a tab that may align commas, the brace of
# a multi-line signature below a return type, promoted properties on lines of their own, an expression on the line of
# its return, and a chain, an array or a condition that may keep the shape it has.

use:
	- perCs
	- cleanup
	- types
	- correctness

file:
	strictTypes:
		declaration: required                 # risky, `'1'` passed to `int` then throws
		position: sameLine
	lineLength: 140                           # a tab counts as 4 columns
	lineLengthExcept: [imports, strings]      # a line too wide only for its strings is not reported

indentation:
	unit: tab
	chain: nested                             # a link may stand one level deeper than the one before

braces:
	position:
		typedMultilineSignature: nextLine     # `{` below `): Foo`, otherwise `) {`
	emptyBody:                                # not `{}` as in PER Coding Style
		class: expanded
		function: expanded
		closure: expanded
		propertyHook: expanded
		anonymousClass: keep
	singlelineClosure: keep
	alternativeSyntax: forbidden              # no `if: ... endif;`

# a range `1-2` leaves the choice to the author
blankLines:
	betweenImportKinds: 0
	beforeDeclaration: 2                      # above the class of a file
	aroundDeclarations: 1-2                   # around one among statements
	betweenStatementsInFunction: 0-2
	aroundMethods: 2
	aroundInterfaceMethods: 1
	betweenMemberKinds: 0-1                   # members without a doc comment
	betweenMembers: 0-1
	beforeDocumentedMember: 1
	bodyStart:
		blocks: 0
	bodyEnd:
		blocks: 0
		branch: lastStatementSetApart         # before `} else {` where the last statement stands apart
		switchCase: lastStatementSetApart     # likewise before `case`

multiline:
	items:
		array: fill                           # items may share a line
		list: fill
		import: fill                          # the names filling the lines
		argument: fillPlain                   # `foo($a, $b, self::X)` share lines, `foo(fn() => 1, bar())` one on a line
		matchArm: perLine
		matchCondition: fill                  # conditions may share a line
	edges:
		array: [apart, attached]              # `]` begins a line where the first item does
		list: [apart, attached]
		import: apart
		chain: attached                       # `$form->addText()` above `->setRequired()`
		condition: [apart, closingApart]      # `if (` above `$a`, or `if ($a` above `&& $b`
	maxWidth:
		array: 130
	fillMinItems:
		array: 6                              # fewer items stand one on a line
		list: 6
		matchCondition: 6
	spreadItem: sole                          # `foo(bar(` above the arguments of `bar()`, `))` below them
	spreadAlways: [promotedProperty]          # however short
	expressionStart:                          # `return [`, not `return` above `[`
		return: sameLine
		throw: sameLine
		yield: sameLine
		echo: sameLine
	semicolon: sameLine
	trailingComma: required                   # a group use and `list()` too

spacing:
	alignment:
		comma: tabs                           # tabs aligning columns may stay
		ternary: forbidden                    # no extra spaces around `?` and `:`
	semicolon:
		after: 'for ($i = 0; $i < 5; $i++)'
	logicalNot: '!$x'                         # never `! $x`
	doubleColon: 'Foo::bar()'
	objectOperator: '$a->b()'
	padding:
		array: forbidden                      # `[1, 2]`, `[$a, $b] = $x`
		list: forbidden
		offset: forbidden                     # `$a[0]`
	comment:
		marker: '// text'                     # also `#` and `/* */`
		afterCode: '$a; // text'              # at least one space

# names are reported, never renamed
naming:
	function: camelCase
	constant: PascalCase
	property: camelCase
	variable: camelCase
	abstractClassAffix: forbidden             # `Foo`, not `AbstractFoo`
	interfaceAffix: forbidden                 # `Foo`, not `FooInterface`
	traitAffix: forbidden
	errorAffix: forbidden

casing:
	magicConstant: uppercase
	class: declared                           # `DateTime`, not `datetime`
	function: declared                        # `strlen()`, not `StrLen()`
	method: declared                          # `$o->getTotal()`, not `$o->gettotal()`, with `typeAnalysis`

qualification:
	class:
		otherNamespace: imported
		currentNamespace: bare
		phpdoc: imported                      # `@param Order $order`, not `@param \Acme\Shop\Order $order`
	currentClass: self                        # `self::bar()` inside `Foo`, not `Foo::bar()`

imports:
	order:
		names: alphabetical                   # classes, functions, constants, each alphabetically
	grouping:
		class: [separated, grouped]           # `use A; use B;`, a group use stays
		function: [combined, grouped]         # `use function a, b;`
		constant: [combined, grouped]         # `use const A, B;`

classes:
	visibility:
		interfaceMethod: forbidden            # public anyway
		publicWithSet: required               # `public private(set)`
	members:
		# the methods then in the order written
		order: [traitUse, enumCase, publicConstant, protectedConstant, privateConstant, publicProperty, protectedProperty, privateProperty]
		grouping:
			constant: keep                    # `const A = 1, B = 2;` may stay
	emptyParentheses:
		instantiation: forbidden              # `new Foo`, but `new Foo()->bar()`

functions:
	innerFunction: forbidden
	arrowFunction:
		oneExpression: required               # `fn() =>` for a closure only returning
		emptyBody: keep                       # `function () {}` stays a callback doing nothing
	arrowFunctionNested: yes                  # also one holding another closure

types:
	declaration:                              # a type only where it says something
		parameter: keep
		property: keep
		return: keep
		anonymousFunction: keep

controlFlow:
	switch:
		fallThroughComment: break omitted     # the comment of a fall-through
	elseAfterReturn: keep                     # may stay

expressions:
	comparison:
		loose: forbidden                      # risky
		notEquals: '!='
		yoda: forbidden                       # `$a === 1`
	assignment:
		addingOne: forbidden                  # `$i++`, not `$i += 1`
		increment: postfix                    # `$i++;`
	ternary:
		short: required                       # `$a ?: $b`
	coalesce: required                        # `$a ?? $b`
	wordLogicalOperator: forbidden
	explicitPrecedence: required              # `($a && $b) || $c`
	doubleNegation: forbidden                 # `(bool) $a`, not `!!$a`

literals:
	quotes: single
	arraySyntax: short                        # `[1, 2]`, `[$a] =`
	implicitBackslash: forbidden              # `"\\d"`, not `"\d"`
	digitSeparator:
		integer: required                     # `1_000_000`
		fraction: required
	digitSeparatorMinDigits:
		integer: 7
		fraction: 20

comments:
	singleline: '//'                          # not `#`

phpdoc:
	annotation:
		casing: canonical                     # `@inheritDoc`, not `@inheritdoc`
		inlineVar: forbidden                  # becomes `assert()`, risky
	stars: aligned                            # below the first `*` of `/**`
	blankLines: trimmed
	property:
		plainComment: forbidden               # not `//` or `/* */`
		lineSpan: collapsed                   # `/** @var int */`
		promoted: '@var'                      # not `@param` of the constructor
	forbiddenAnnotations: [@access, @author, @copyright, @created, @license, @package, @since, @subpackage, @todo, @version]
	# `Constructor.`, `Name getter.`
	forbiddenLines: ['~^(?:(?!private|protected|static)\S+ )?(?:con|de)structor\.\z~i', '~^\S+ [gs]etter\.\z~i']
	types:
		array: keep                           # both notations stay
		nullPosition: last

hygiene:
	is_null: forbidden                        # `$x === null`
	settype: forbidden                        # `$x = (int) $x`
	conversionFunction: forbidden             # `(int) $x`, not `intval($x)`
	dirnameOfFile: forbidden                  # `__DIR__`
	nestedDirname: forbidden                  # `dirname($p, 2)`
	classNameNotation:
		currentClass: required                # `self::class`, `static::class`
		object: required                      # `$object::class`

mistakes:
	globalStatement: forbidden
	debugOutput: keep                         # may stay

upgrading:
	php:
		deprecatedApi: forbidden              # `mb_convert_encoding()`, not `utf8_encode()`, `E_STRICT` reported
		deprecatedArgument: forbidden         # `trigger_error($m, E_USER_ERROR)` reported
		dollarBraceInterpolation: forbidden   # `{$name}`, not `${name}`
		implicitNullable: forbidden           # `?int $x = null`, not `int $x = null`
		backticks: forbidden                  # `shell_exec()`
	syntax:
		octalPrefix: required                 # `0o17`, not `017`
	classes:
		Stringable: keep                      # implied by `__toString()`

# comments that silence a decision on their line
suppressionComments:
	'~\bintentionally\s+[!=]=~': {decisions: [expressions.comparison.loose, mistakes.strictArgument], example: '// intentionally =='}
	'~^//\s*@\s+\S~': {decisions: mistakes.errorSuppression, example: '// @ <why>'}

symfony

Symfony Coding Standards tak, jak je definuje oficiální sada @Symfony v PHP CS Fixeru (zatím bez většiny jejích specifických phpDoc pravidel). Přejímá PER Coding Style a doplňuje pravidla pro importy, mezery a prázdné řádky typické pro Symfony, přičemž zalamování konstrukcí na více řádků ponechává plně na uvážení autora.

# Symfony Coding Standards
#
# The Symfony Coding Standards as the @Symfony rule set of PHP CS Fixer defines them, without most of its rules about
# phpDoc, of which it takes only an empty doc comment, the trimming and the canonical types: PER Coding Style, which the
# set builds on, with the casing, the imports, the blank lines and the whitespace Symfony adds, and without the rules
# that would break a construct over lines, which Symfony leaves to the author.

use: perCs

file:
	lineLength: none
	longLines: keep

indentation:
	binaryOperator: keep
	chain: keep
	heredoc: keep                             # heredoc_indentation belongs to another set

braces:
	emptyBody:                                # not `{}` as in PER Coding Style
		class: expanded
		function: expanded
		closure: expanded
		propertyHook: expanded
		anonymousClass: keep
	singlelineClosure: keep
	bareStatementGroup: forbidden             # no `{ ... }` that nothing opens
	alternativeSyntax: forbidden              # no `if: ... endif;`

# a range `0-1` leaves the choice to the author, `1+` is one or more
blankLines:
	afterDeclare: keep                        # @Symfony leaves the line below a declare
	aroundDeclarations: 0-1                   # around one among statements
	aroundMethods: 1
	aroundInterfaceMethods: 1
	betweenTraitUses: keep
	afterTraitUses: keep
	beforeStatement: {return: 1+}             # a `return` set apart from the code above it
	belowComment:
		inlineVar: 0                          # `/** @var Order $order */` right above its statement

multiline:
	# no fixer of the set breaks a call, a signature, a chain, a condition, a ternary, an operator or an array
	items: keep
	edges: keep
	operatorPosition:                         # operator_linebreak moves only a boolean operator
		operators: keep
		logical: lineStart
		condition: lineStart
	trailingComma:
		argument: optional                    # may stay or not where spread, never on one line
		closureUse: keep
	attributePosition:
		parameter: keep

spacing:
	arrowFunction: 'fn ($x) => $x'
	alignment:
		comma: optional                       # aligning spaces and tabs may stay
		binaryOperator: forbidden             # no extra spaces around an operator
	semicolon:
		after: 'for ($i = 0; $i < 5; $i++)'
	logicalNot: '!$x'                         # never `! $x`
	concatenation: '$a.$b'
	objectOperator: '$a->b()'
	padding:
		array: forbidden                      # `[1, 2]`, `[$a, $b] = $x`
		list: forbidden
		offset: forbidden                     # `$a[0]`
	anonymousClass: 'new class($a)'
	comment:
		marker: '// text'                     # also `#` and `/* */`
		afterCode: '$a; // text'              # at least one space

casing:
	magicConstant: uppercase
	class: declared                           # `DateTime`, not `datetime`
	function: declared                        # `strlen()`, not `StrLen()`
	method: declared                          # `__toString()`, other methods with `typeAnalysis`
	hexDigit: keep                            # integer_literal_case left to the project

# global_namespace_import imports nothing: a class is written with the backslash, a function and a constant too where
# they are qualified, and a bare one stays bare
qualification:
	class:
		global: backslashed                   # `\DateTime`
	function:
		global: [backslashed, bare]           # `\ucfirst()` or `ucfirst()`, never imported
		optimized: [backslashed, bare]
	constant:
		global: [backslashed, bare]
		optimized: [backslashed, bare]

imports:
	order:
		names: alphabetical                   # classes, functions, constants, each alphabetically
	grouping: separated                       # `use A; use B;`, not `use A, B;` or `use Acme\{A, B};`
	unused: forbidden
	aliasEqualToName: forbidden               # `use Foo;`, not `use Foo as Foo;`

classes:
	visibility:
		publicWithSet: required               # `public private(set)`, as modifier_keywords writes it
	emptyParentheses:
		attribute: keep                       # `#[Foo()]` stays, as in @Symfony
	untypedPropertyNullDefault: forbidden     # `public $a;`, not `public $a = null;`
	instantiationWrapping: keep               # @Symfony leaves the parentheses
	protectedInFinalClass: forbidden          # `private` in a final class with no parent and no trait

functions:
	trailingBareReturn: forbidden             # no `return;` closing a function

controlFlow:
	elseAfterReturn: forbidden                # the `else` after a `return` goes, its body following the `if`

expressions:
	comparison:
		notEquals: '!='                       # not `<>`
		yoda: required                        # `null === $a`
	assignment:
		addingOne: forbidden                  # `++$i`, not `$i += 1`
		increment: prefix                     # `++$i;`
	doubleNegation: forbidden                 # `(bool) $a`, not `!!$a`
	parenthesesAfterConstruct: forbidden      # `return $x;`, not `return ($x);`

literals:
	quotes: single
	heredocWithoutInterpolation: keep         # heredoc_to_nowdoc belongs to another set
	concatenatedLiterals: keep                # no_useless_concat_operator left to the project

comments:
	singleline: '//'                          # not `#`
	empty: forbidden

phpdoc:
	aboveAttributes: keep                     # @Symfony leaves the order alone, unlike PER Coding Style
	blankLines: trimmed
	empty: forbidden
	types:
		builtin: canonical                    # `int`, not `integer`
		duplicate: forbidden                  # each type of a union once
		nullPosition: last

mistakes:
	continueInSwitch: forbidden               # a `continue` only leaving a `switch`, reported

upgrading:
	php:
		dollarBraceInterpolation: forbidden   # `{$name}`, not `${name}`
		implicitNullable: forbidden           # `?int $x = null`, not `int $x = null`
		backticks: forbidden                  # `shell_exec()`

laravel

Styl frameworku Laravel definovaný výchozím presetem laravel nástroje Laravel Pint: PER Coding Style se spojováním řetězců bez mezer, negací ! $x, vynechanými závorkami u new Foo a plně importovanými názvy tříd v phpDoc. Rozdíly oproti chování Pintu popisuje stránka Přechod z Laravel Pintu.

# Laravel
#
# The style of Laravel as the preset laravel of Laravel Pint defines it: PER Coding Style with the concatenation, the
# negation and the instantiation Laravel writes, the arrow function spaced, the imports qualified and sorted, a blank
# line before `return` and `continue`, the phpDoc trimmed, and without the rules that break a construct over lines,
# which Pint leaves to the author.

use: perCs

file:
	lineLength: none
	longLines: keep
	strictTypes:
		position: keep

indentation:
	heredoc: keep
	binaryOperator: keep
	ternary: keep

braces:
	alternativeSyntax: forbidden
	bareStatementGroup: forbidden
	position:
		anonymousClass: nextLine
	emptyBody:
		propertyHook: keep

blankLines:
	bodyStart:
		controlStructure: keep
	bodyEnd:
		function: keep
		closure: keep
		controlStructure: keep
		branch: keep
		class: keep
		anonymousClass: keep
	aroundMethods: 1
	aroundInterfaceMethods: 1
	beforeOpenBrace:
		class: keep
	afterTraitUses: keep
	betweenMemberKinds: 1
	betweenMembers:                           # enum cases together
		constant: 1
		property: 1
	beforeDocumentedMember: 1
	afterDeclare: keep
	afterAttributes: keep
	beforeStatement:
		continue: '1+'
		return: '1+'

# method_argument_space ignores what spreads over lines, and Pint has nothing that would spread a construct
multiline:
	items:
		array: keep
		list: keep
		implements: keep
		condition: keep
		for: keep
		logical: keep
		concatenation: keep
		arithmetic: keep
		bitwise: keep
		chain: keep
		matchCondition: keep
		ternary: keep
		argument: keep
		parameter: keep
	edges:
		array: keep
		list: keep
		implements: keep
		condition: keep
		chain: keep
		argument: keep
		parameter: keep
	trailingComma:
		argument: optional                    # arrays only
		parameter: optional
		matchArm: keep
		closureUse: keep
	operatorPosition:
		condition: keep
		comparison: keep
		logical: keep
		concatenation: keep
		arithmetic: keep
		bitwise: keep
		coalesce: keep
		pipe: keep
		ternary: keep
	attributePosition:
		declaration: keep
		parameter: keep
	semicolon: sameLine

spacing:
	anonymousClass: 'new class($a)'
	comment:
		marker: // text
	concatenation: $a.$b
	alignment:
		binaryOperator: forbidden
		comma: optional
	objectOperator: '$a->b()'
	doubleColon: 'Foo::bar()'
	logicalNot: ! $x
	reference: keep
	spread: keep
	namedArgument: keep
	attribute: keep
	alternativeSyntax: keep
	body: keep
	import: keep
	arrowFunction: 'fn ($x) => $x'
	padding:
		array: forbidden
		list: forbidden
		offset: forbidden
	semicolon:
		after: 'for ($i = 0; $i < 5; $i++)'

naming:
	class: keep
	method: keep
	constant: keep
	enumCase: keep

casing:
	magicConstant: uppercase
	hexDigit: uppercase
	function: declared                        # `strlen()`, not `StrLen()`

qualification:
	staticInFinalClass: required              # `self::` in a final class
	class:
		otherNamespace: imported
		currentNamespace: bare
		phpdoc: imported                      # `@param Request $request`, not `@param \Illuminate\Http\Request $request`

imports:
	grouping:
		class: separated
		function: separated
		constant: separated
	order:
		kinds: [constant, class, function]
		names: alphabetical
	unused: forbidden
	aliasEqualToName: forbidden

classes:
	members:
		order: keep
		grouping:
			traitUse: keep
	protectedInEnum: keep
	visibility:
		constant: keep
	emptyParentheses:
		instantiation: forbidden              # `new Foo`
		attribute: keep
	instantiationWrapping: keep

functions:
	trailingBareReturn: forbidden
	arrowFunction:
		emptyBody: keep
	unreachableParameterDefault: forbidden

types:
	nullPosition: keep

controlFlow:
	switch:
		fallThroughComment: keep

expressions:
	parenthesesAfterConstruct: forbidden      # `return $x;`, not `return ($x);`
	assignment:
		increment: postfix
	doubleNegation: forbidden
	comparison:
		notEquals: '!='
		yoda: forbidden

literals:
	arraySyntax:
		list: short
	quotes: single

comments:
	singleline: //

phpdoc:
	empty: forbidden
	forbiddenAnnotations: [@access, @package, @subpackage]
	aboveAttributes: keep
	stars: aligned
	blankLines: trimmed
	types:
		builtin: canonical
	annotation:
		nativeTypeOnly: forbidden

hygiene:
	aliasFunctions: [core, imap, pg]

upgrading:
	php:
		implicitNullable: forbidden

Sady

Sada o vizuálním vzhledu kódu nerozhoduje, a proto se se zvoleným standardem nikdy nedostane do křížku. Připojuje se v konfiguraci vedle něj (use: [nette, modernizations]) a v čase se plynule rozvíjí: jakmile DressCode v nové verzi získá další pravidla daného záměru, automaticky přibudou do příslušné sady a váš projekt je získá bez nutnosti měnit konfigurační soubor.

modernizations

Modernější jazykové konstrukce PHP nahrazující starší zápisy téhož významu (každá podmíněná tím, zda ji cílové PHP projektu již bezpečně podporuje) a modernizace API nabízené soubory nainstalovaných migračních balíčků.

# Modernizations
#
# The construct a newer PHP has, written where an older one says the same, each where the target version of the
# project has it, and the newer form the upgrading files of the installed packages offer. It decides nothing about the
# layout, so it goes with any standard.

classes:
	instantiationWrapping: forbidden           # `new Foo()->bar()`, not `(new Foo())->bar()`

functions:
	arrowFunction:
		oneExpression: required                # `fn() =>` for a closure only returning
	arrowFunctionNested: yes                   # also one holding another closure

expressions:
	ternary:
		short: required                        # `$a ?: $b`
	coalesce: required                         # `$a ?? $b`

literals:
	arraySyntax: short                         # `[1, 2]`, `[$a] =`

hygiene:
	classNameNotation:
		currentClass: required                 # `self::class`, `static::class`
		object: required                       # `$object::class`, PHP 8.0

upgrading:
	packages: yes                              # as the upgrading files of the packages say
	php:
		sleepAndWakeup: forbidden              # `__serialize()` and `__unserialize()` instead
	syntax:
		spreadForArrayMerge: required          # `[...$a, ...$b]`, not `array_merge($a, $b)`
		dynamicClassConstant: required         # `X::{$name}`, not `constant(X::class . '::' . $name)`
		match: required                        # for a `switch` returning or assigning
		throwExpression: required              # `$a ?? throw new E`
		cloneWith: required                    # `clone($o, [...])`
		nullsafe: required                     # `$a?->b`, not `$a !== null ? $a->b : null`
		pipe: required                         # `$x |> a(...) |> b(...)`, not `b(a($x))`
		firstClassCallables: required          # `foo(...)`, not `Closure::fromCallable('foo')`
		octalPrefix: required                  # `0o17`, not `017`
		newInInitializer: required             # `Clock $c = new SystemClock`, not a `null` swapped in the body
		promotedProperties: required
		readonlyClasses: required              # `readonly class`, not `readonly` on every property
	functions:
		array_is_list: required
		arraySearchFunctions: required         # `array_find()`, `array_any()` for a `foreach`
		arrayFirstLast: required               # `array_first()`, not `reset()`
		clamp: required                        # `clamp($x, $lo, $hi)`, not `max($lo, min($x, $hi))`
		get_debug_type: required
		json_validate: required                # not `json_decode()` only testing
		substringFunctions: required           # `str_contains()`, not `strpos() !== false`
		roundingMode: required                 # `RoundingMode::HalfAwayFromZero`, not `PHP_ROUND_HALF_UP`
	phpdoc:
		deprecated: required                   # `#[\Deprecated]`, not `@deprecated`

deprecations

Prvky, které novější verze PHP označily za zastaralé nebo odstranily: automaticky opravené tam, kde bezpečná náhrada existuje, a ohlášené tam, kde musí rozhodnout vývojář. K tomu navíc zavržená volání evidovaná v souborech nainstalovaných migračních balíčků.

# Deprecations
#
# What a newer PHP deprecated or removed, fixed where a replacement exists and reported where none does, and what the
# upgrading files of the installed packages list as retired. A deprecation is reported whatever the target, the code
# meeting it wherever it runs on the version that made it.

classes:
	overridingMethod:
		signature: required                   # a child matching a parent changed by an upgrade
		parameterName: required

expressions:
	cast: canonical                           # `(int)`, not `(integer)`, PHP 8.5

# what a library or PHP breaks in a major version, reported
mistakes:
	finalParent: forbidden                    # a class extending a final one
	unimplementedAbstractMethod: forbidden
	__serialize: required                     # beside `Serializable`, PHP 8.1

upgrading:
	packages: yes                             # as the upgrading files of the packages say
	declarations:
		deprecatedClass: forbidden            # by what `@deprecated` names
		deprecatedMember: forbidden
		deprecatedFunction: forbidden
	php:
		nullArrayKey: forbidden               # `['' => 1]`, not `[null => 1]`, PHP 8.5
		nullArgument: forbidden               # `trim($text ?? '')`, not `trim($text)` of a null, PHP 8.1
		floatArrayKey: forbidden              # `$a[(int) $ratio]`, not `$a[$ratio]` of a float, PHP 8.1
		dynamicProperty: forbidden            # a property the class declares, reported, PHP 8.2
		caseSemicolon: forbidden              # `case 1:`, not `case 1;`, PHP 8.5
		implicitNullable: forbidden           # `?int $x = null`, not `int $x = null`, PHP 8.4
		deprecatedApi: forbidden              # `mb_convert_encoding()`, not `utf8_encode()`, `E_STRICT` reported
		deprecatedArgument: forbidden         # `trigger_error($m, E_USER_ERROR)` reported
		constructorReturnValue: forbidden     # PHP 8.6
		sleepAndWakeup: forbidden             # `__serialize()`, PHP 8.5
		returnInFinally: forbidden            # PHP 8.6
		csvEscapeArgument: required           # the `escape` of `fgetcsv()` written out, PHP 8.4
		dollarBraceInterpolation: forbidden   # `{$name}`, not `${name}`, PHP 8.2
		reservedNames: forbidden              # `let`, `is`, `_`, PHP 8.6
		backticks: forbidden                  # `shell_exec()`, PHP 8.5
	syntax:
		firstClassCallables: required         # `foo(...)`, not `Closure::fromCallable('foo')`

cleanup

Zbytečný kód, který v programu reálně nic nedělá: duplicita toho, co jazyk říká sám, mrtvý kód, který se nikdy nevykoná, zbytečné aliasy funkcí i komentáře bez informační hodnoty.

# Cleanup
#
# Code that is there for nothing: what repeats what the language already says, what can never run, an alias of a
# function, a comment or a doc comment that says nothing. It decides nothing about the layout, so it goes with any
# standard.

braces:
	bareStatementGroup: forbidden            # no `{ ... }` that nothing opens

qualification:
	leadingBackslash: forbidden              # `strlen()` in a file without a namespace, not `\strlen()`

imports:
	leadingBackslash: forbidden              # `use Foo\Bar;`, not `use \Foo\Bar;`
	unused: forbidden
	aliasEqualToName: forbidden              # `use Foo;`, not `use Foo as Foo;`
	fromCurrentNamespace: forbidden          # `use Acme\Shop\Order;` in `namespace Acme\Shop`

classes:
	modifiers:
		implied: forbidden                   # `final` in a final class
	overridingMethod:
		parentCallOnly: forbidden            # one only forwarding to `parent::`, risky
		ReturnTypeWillChange: forbidden      # on a method declaring its return type
	untypedPropertyNullDefault: forbidden    # `public $a;`, not `public $a = null;`

functions:
	trailingBareReturn: forbidden            # no `return;` closing a function
	unreachableParameterDefault: forbidden   # a default before a required parameter

controlFlow:
	emptyStatement: forbidden                # `foo();;`
	ifReturningTrueFalse: forbidden          # `return $a > 1;` for an `if` returning `true` or `false`
	elseAfterReturn: forbidden               # the `else` after a `return` goes, its body following the `if`

expressions:
	parenthesesAfterConstruct: forbidden     # `return $x;`, not `return ($x);`
	assignment:
		targetInValue: forbidden             # `$a += $b`, not `$a = $a + $b`
	ternary:
		ofTrueFalse: forbidden               # `$a > 1`, not `$a > 1 ? true : false`

literals:
	classNameInString: forbidden             # `Order::class`, not `'Acme\Shop\Order'`
	concatenatedLiterals: forbidden          # `'ab'`, not `'a' . 'b'`
	emptyStringConcatenation: forbidden      # no `. ''`

comments:
	empty: forbidden

phpdoc:
	empty: forbidden
	inheritdocOnly: forbidden                # one holding only `{@inheritDoc}`
	annotation:
		nativeTypeOnly: forbidden            # `@param int $a` on `int $a`
		constantVar: forbidden               # a bare `@var int` on a class constant

hygiene:
	aliasFunctions: [core, imap, pg]         # `count()`, not `sizeof()`
	strlenEmptyTest: forbidden               # `$s === ''`, not `strlen($s) === 0`
	countEmptyTest: forbidden                # `$a === []`, not `count($a) === 0`
	call_user_func: forbidden                # `$f($x)`, not `call_user_func($f, $x)`, risky
	__invokeCall: forbidden                  # `$f($x)`, not `$f->__invoke($x)`
	defaultArgument: forbidden               # `json_encode($a)`, not `json_encode($a, 0)`
	unreadCatchVariable: forbidden           # `catch (E)` where `$e` is not read
	repeatedCatchBody: forbidden             # `catch (A|B $e)` for two catches of one body
	separateIssets: forbidden                # `isset($a, $b)`
	separateUnsets: forbidden                # `unset($a, $b);`
	uselessVersionCheck: forbidden           # no `if (PHP_VERSION_ID >= 80000)` the target always passes

correctness

Konstrukce, které s vysokou pravděpodobností představují chybu bez ohledu na zvolený formátovací styl.

# Correctness
#
# What is most likely a mistake whatever the style: a call comparing loosely where it should not, `$this` where no
# object is, a `catch` nothing reaches, a doc comment contradicting itself or written so that no tool reads it,
# invisible characters in the code.

phpdoc:
	annotation:
		extraParam: forbidden                   # a `@param` of no parameter
		duplicateParam: forbidden               # a second `@param` of one parameter
		duplicateReturn: forbidden
		empty: forbidden                        # `@param`, `@return`, `@var`, `@see` without content
		duplicateVar: forbidden
		plainComment: forbidden                 # `/* @var */` and `/**@var`, which no tool reads

mistakes:
	__debugInfoReturningNull: forbidden         # `[]`, not `null`
	thisOutsideObject: forbidden                # in a static method or a function, reported
	staticSetState: required
	continueInSwitch: forbidden                 # a `continue` only leaving a `switch`, reported
	unreachableCatch: forbidden                 # after `catch (Throwable)`
	exceptionWhereThrowableBelongs: forbidden   # `catch (Exception)` where `Throwable` is meant, reported
	errorSuppression: forbidden                 # the `@` operator, reported
	invisibleCharacter: forbidden               # zero-width, no-break and bidi characters, escaped
	strictArgument: required                    # `in_array($a, $b, true)`, risky
	trailingWhitespaceInString: forbidden       # in a multi-line string or inline HTML
	consecutivePhpdocs: forbidden               # the first of two is lost, reported
	repeatedAssignment: forbidden               # `$a = $a = 1`
	looseNumberComparison: forbidden            # `0 == 'a'`, true before PHP 8.0, reported
	countOfUncountable: forbidden               # `count()` of no array and no `Countable`, reported
	debugOutput: forbidden                      # `// var_dump($a);`, risky

types

Typové deklarace zapsané všude tam, kde jim samotné PHP rozumí.

# Types
#
# Types written where PHP reads them: a native type on every parameter, property and return that has one, one notation
# of each type (`?int`, `int` rather than `integer`, `array<T>` rather than `T[]`), `never` on a function that only
# throws, `Stringable` on a class with `__toString()`.

types:
	declaration:
		parameter: required           # from `@param`, risky
		property: required            # from `@var`, risky
		return: required              # from `@return`, `void` where nothing returns, risky
		anonymousFunction: required   # `void` where a closure returns nothing, risky
	nullable: '?int'                  # `?int`, not `int|null`
	nullPosition: last                # `int|string|null`

phpdoc:
	annotation:
		nativeTypeOnly: forbidden     # the annotation the native type now says
	types:
		builtin: canonical            # `int`, not `integer`
		duplicate: forbidden          # each type of a union once
		array: 'array<T>'             # `array<int>`, not `int[]`

mistakes:
	neverReturnType:
		function: required            # on a function that always throws
		method: required

upgrading:
	classes:
		Stringable: required          # on a class with `__toString()`

compilerOptimizations

Globální funkce a konstanty zapsané do tvaru, který kompilátor PHP optimalizuje přímo při překladu (podrobnosti přináší stránka Rychlejší volání, když je jméno jisté).

# Compiler optimizations
#
# Global names written in the form PHP optimizes when it compiles the code: a function the compiler turns into an
# opcode and a constant it computes with are imported, so that the compiler knows them, and every other global name is
# written bare.

qualification:
	function:
		global: bare                                # `array_map()`, reached by the fallback at run time
		optimized: imported                         # `use function strlen;`, its arguments positional
	constant:
		global: bare                                # `E_ALL`
		optimized: [imported, backslashed]          # `PHP_VERSION_ID` imported or `\PHP_VERSION_ID`

Zobrazit na GitHubu · Nahlásit problém se stránkou na GitHubu · 2. 11. 2026