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);
    }
}

Configuration

Property Default Meaning

maxNesting

3

The deepest nesting level that is accepted, counting the function itself. A function at a greater depth is flagged, and only the first level past the limit is reported.

© 2026 Xygeni. All rights reserved