Heredocs and nowdocs are indented in one of four ways. Mixed indentation is enabled by default.
Enabled when --heredoc-indent none is given.
<?php
$foo = [
'bar' => <<<EOF
Content
EOF,
];Enabled when --heredoc-indent line is given.
<?php
$foo = [
'bar' => <<<EOF
Content
EOF,
];Line indentation is applied to heredocs that start on their own line, otherwise hanging indentation is applied.
Enabled by default and when --heredoc-indent mixed is given.
<?php
$foo = <<<EOF
Content
EOF;
$bar =
<<<EOF
Content
EOF;Enabled when --heredoc-indent hanging is given.
<?php
$foo = <<<EOF
Content
EOF;
$bar =
<<<EOF
Content
EOF;Code between brackets ((), [], {}) is always indented when the open
bracket is followed by a line break.
The PlaceComments rule aligns comments with subsequent code unless the next
code token is a close bracket or switch case:
<?php
switch ($foo) {
//
case 0:
// Indented
case 1:
bar();
// Indented
case 2:
break;
//
case 3:
// Indented
// Indented
//
case 4:
baz();
// Indented
// Indented
//
case 5:
break;
// Indented
//
//
case 6:
qux();
// Indented
//
case 7:
break;
//
//
default:
}Hanging indentation is applied when the context of a line of code is unclear after standard indentation has been applied.
Consider this contrived example:
<?php
if (!$request->route()->isPrivate() ||
($request->hasUser() &&
($this->userHasRoute($request->user(),
$request->route()) ||
$request->user()->role() === User::ADMIN_ROLE ||
$request->user()->role() === User::EDITOR_ROLE)) ||
Session::hasCarteBlanche()) {
Console::log('Access granted');
}Without pretty-php's "overhanging" indentation, more effort is required to
understand how it works because the same level of indentation has been applied
to adjacent lines that belong to different contexts:
<?php
if (!$request->route()->isPrivate() ||
($request->hasUser() &&
($this->userHasRoute($request->user(),
$request->route()) ||
$request->user()->role() === User::ADMIN_ROLE ||
$request->user()->role() === User::EDITOR_ROLE)) ||
Session::hasCarteBlanche()) {
Console::log('Access granted');
}With no hanging indentation at all, even more effort is required:
<?php
if (!$request->route()->isPrivate() ||
($request->hasUser() &&
($this->userHasRoute($request->user(),
$request->route()) ||
$request->user()->role() === User::ADMIN_ROLE ||
$request->user()->role() === User::EDITOR_ROLE)) ||
Session::hasCarteBlanche()) {
Console::log('Access granted');
}One solution (adopted in PSR-12 / PER) is to insert line breaks after open brackets. In many cases this is sensible, but it doesn't cover every PHP construct and isn't always desirable.
<?php
if (
!$request->route()->isPrivate() ||
(
$request->hasUser() &&
(
$this->userHasRoute(
$request->user(),
$request->route()
) ||
$request->user()->role() === User::ADMIN_ROLE ||
$request->user()->role() === User::EDITOR_ROLE
)
) ||
Session::hasCarteBlanche()
) {
Console::log('Access granted');
}Using arrays as a metaphor for constructs with one or more entries that may be
arbitrarily nested, the HangingIndentation rule recognises the following
indentation scenarios.
.hh.represents a level of hanging indentation, and.HH.represents a level of "overhanging" indentation.
-
Standard indentation is sufficient
[ ___, ___, ___, ___ ]; -
One level of hanging indentation is required
[ ___, ___ .hh.___, ___, ___, ___ ]; -
One level of hanging indentation is sufficient
[___, ___, .hh.___, ___]; -
Two levels of hanging indentation are required
[___, ___ .hh..HH.___, ___, .hh.___, ___]; -
Two levels of hanging indentation are required per level of nesting
[___, [___, .hh..HH..hh.___], .hh.___,[___, ___ .hh..HH..hh..HH.___, .hh..HH..hh.___]];[[[___ .hh..HH..hh..HH..hh..HH.___, .hh..HH..hh..HH..hh.___], .hh..HH..hh.___], .hh.___]
Overhanging indentation is also applied to blocks that form part of a continuing
structure, e.g. the if block above.
After finding a token to indent, HangingIndentation creates a context for it,
and if indentation for that context has already been applied, the token is not
indented further.
A token's context is an array{?Token, ?Token, ?Token, 3?:Token|int, 4?:int}
comprised of:
- its parent (or
nullif it's a top-level token) - the most recent assignment operator or
T_DOUBLE_ARROWin the same statement (ornullif it isn't part of an expression after an assignment or=>) - its ternary context (or
nullif it isn't part of a ternary expression) - an optional token and/or precedence value shared by any siblings that should receive the same level of indentation
The aim is to differentiate between lines where a new expression starts, and lines where an expression continues:
<?php
fn($a, $b) =>
$a === $b
? 0
: $a <=>
$b;There are many more examples in the HangingIndentation unit
test.