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 |
|---|---|---|
|
|
An operator count strictly greater than this value is flagged, and only when the expression mixes more than one kind of operator. |
Related rules
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.