Functions Nested Too Deeply
ID |
php.functions_nested_deeply |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Complexity |
Language |
Php |
Tags |
complexity, readability |
Description
Reports a function declared too many levels inside other functions. Nesting depth is the number of
enclosing routines including the function itself, so a method or a free function is at depth 1, a
closure inside it at depth 2, and so on; the default limit is 3.
All three PHP shapes count, because to a reader they are the same construct: a closure
(function () { … }), an arrow function (fn($x) ⇒ …) and a named function declared inside
another function, which PHP allows.
Only the outermost offending function is reported — the one exactly one level past the limit. A chain six deep is one problem with one fix.
The count restarts at a type declaration. A method of an anonymous class built inside a closure reads as a fresh start and behaves as one, since it captures none of the closure’s locals.
Rationale
Every level adds a scope whose variables, $this binding and use captures the reader has to carry
down with them, and the levels are not visually distinct: three lines of closing brace and comma in
a row say nothing about which callback each one ends.
Depth is also where two specific bugs hide. A parameter or use capture silently shadows an outer
variable of the same name, and the reader has no marker telling them which binding is in scope at
the point they are looking at. And an early return looks like it leaves the routine the reader
thinks they are in, when it only leaves the innermost closure — so a guard clause written three
levels down does not guard what its author intended.
<?php
class ReportBuilder
{
public function normalise(array $rows): array
{
return array_map(function (array $row): array { // depth 2
return array_map(function (array $cell): array { // depth 3
return array_map(function (array $part): array { // FLAW - depth 4
return array_map(fn(string $atom): string => trim($atom), $part);
}, $cell);
}, $row);
}, $rows);
}
public function flatten(array $rows): array
{
return array_map(function (array $row): array { // OK - depth 2
return array_map(fn(string $cell): string => strtoupper($cell), $row);
}, $rows);
}
}
Remediation
Give the inner routines names and lift them out. A named private method or a static closure held
in a property says what the step does, can be tested on its own, and flattens the chain into a
sequence a reader can follow top to bottom. Where the nesting exists only to thread one value
through several transformations, a pipeline of successive array_map calls over a local variable
replaces the nesting with a list of steps.
<?php
class ReportBuilder
{
public function normalise(array $rows): array
{
return array_map([$this, 'normaliseRow'], $rows);
}
private function normaliseRow(array $row): array
{
return array_map([$this, 'normaliseCell'], $row);
}
private function normaliseCell(array $cell): array
{
return array_map(fn(string $atom): string => trim($atom), $cell);
}
}