Expression Too Complex

ID

php.expression_too_complex

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Complexity

Language

Php

Tags

complexity, control-flow, readability

Description

Reports a single expression that combines more &&, and, ||, or, xor and ternary ?: operators than a reader can hold at once. The expression can be an if/while test, an assignment’s right-hand side, a call argument, an array value — anywhere a boolean or ternary chain appears.

Only the outermost node of a connected chain is reported, so one dense expression yields one issue rather than one per nesting level. A nested function — a closure, an arrow function, a function declared inside another — starts a fresh expression: its operators are counted against it and it is checked on its own.

PHP spells conjunction and disjunction twice, as &&/|| and as the low-precedence word forms and/or. Both are counted, and both spellings of the same connective count as the same kind of operator: $a and $b and $c is one conjunction however it is written.

Two operators are deliberately excluded. Bitwise & and | do not short-circuit, so they add no branching of their own. Null coalescing ?? is a default-value chain rather than a condition, and a run of it reads as a priority list.

Rationale

Each conditional operator adds another combination of truth values a reader has to keep in mind, and && binding tighter than || makes the grouping invisible unless it is parenthesised. Long conditions are also where an inverted term hides: nothing in the layout distinguishes the one that is wrong.

A long chain of a single operator is not what costs a reader — $a || $b || $c || $d || $e is a list, as easy to follow at eight terms as at two. What has to be held in the head is the precedence of a mix, so only expressions that combine at least two kinds of operator (or nest one ternary inside another) are reported.

<?php
class AccessPolicy
{
    public function canPublish(array $u): bool
    {
        // FLAW - five operators of two kinds; the grouping is invisible
        return $u['active'] && $u['verified'] && $u['confirmed'] && $u['quota'] && $u['owner'] || $u['admin'];
    }

    public function anyRole(array $u): bool
    {
        // OK - one kind, a plain list
        return $u['admin'] || $u['owner'] || $u['lead'] || $u['reviewer'] || $u['author'];
    }
}

Remediation

Name the intermediate conditions and combine the names, or parenthesise the groups so the intended precedence is written down rather than inferred. Where the condition is really a rule about one subject, moving it behind a predicate method makes it testable on its own.

<?php
class AccessPolicy
{
    public function canPublish(array $u): bool
    {
        return $this->isTrustedAuthor($u) || $u['admin'];
    }

    private function isTrustedAuthor(array $u): bool
    {
        return $u['active'] && $u['verified'] && $u['confirmed'] && $u['quota'] && $u['owner'];
    }
}

Configuration

Property Default Meaning

maxLogicalOperators

4

An operator count strictly greater than this value is flagged, and only when the expression mixes more than one kind of operator.

php.cyclomatic_complexity scores a whole function body from its control-flow graph; this rule scores one expression’s own operator count. A function can be inside its complexity budget and still contain one unreadable condition, and a function of many simple branches can exceed the function-level budget with no complex expression in it.

© 2026 Xygeni. All rights reserved