Else If Without Else
ID |
php.else_if_without_else |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Code Smell |
Language |
Php |
Tags |
code-style, conditional, control-flow |
Description
Reports an if / elseif chain that does not end with a plain else.
At least one elseif rung is required: a lone if, and a plain if / else with no elseif, are
ordinary guards and are never reported. The chain is reported once, at its leading if, however
long the ladder.
This rule ships disabled. It is a house-style judgement rather than a defect, and on a codebase that has not adopted it every conditional ladder is a finding. Enable it per project.
Rationale
<?php
function tally(string $level, Counters $c): void
{
if ($level === 'warn') { // FLAW — no final else
$c->warn++;
} elseif ($level === 'error') {
$c->error++;
}
}
A chain of conditions with no catch-all leaves one case unwritten: the one where none of the conditions hold. Nothing in the source says whether doing nothing is the intended behaviour for that case or whether the branch was forgotten, so a reader cannot tell a deliberate no-op from an oversight, and neither can a reviewer.
The gap gets worse over time. When a value is added to whatever $level enumerates, the new value
falls through the whole chain silently — the counter is simply never incremented, with no warning
and no log line. A chain that ends in else fails loudly instead, at the one place that was
written to catch exactly this.
Remediation
End the chain with an else that states the remaining case, even when the statement is that
nothing should happen.
<?php
function tally(string $level, Counters $c): void
{
if ($level === 'warn') {
$c->warn++;
} elseif ($level === 'error') {
$c->error++;
} else {
$c->other++;
}
}
When the remaining case really is unreachable, say so where a reader and a test will both see it:
} else {
throw new \LogicException("unhandled level $level");
}
When nothing should happen, a comment in the else closes the chain and satisfies the rule:
} else {
// levels below error are counted elsewhere
}
If the chain is dispatching on a single value, a switch with a default says the same thing more
directly — see php.missing_switch_default.
Configuration
This rule has no configurable properties. Three points are worth spelling out.
Both spellings of a rung count. PHP writes the same rung as elseif (one keyword) or else if
(two), and the parser models them differently: elseif is a flat sibling of the leading if, while
else if nests a whole new if inside the else. The rule walks through the nested form, so a
ladder that mixes the two spellings is still one chain and still one finding.
The alternative syntax is covered. if (…): … elseif (…): … endif; is analysed exactly like the
braced form, including its else: branch.
An empty final else counts as a missing one, unless it carries a comment. An else { } states
nothing; else { /* nothing to do here */ } states that nothing is to be done, which is what the
rule is asking for.
A chain whose every branch return`s is still reported. Only the Go counterpart exempts that shape,
because Go’s early-return idiom asks for the final case to be written unindented after the chain.
PHP has no such convention, so a returning chain with no `else carries the same unstated case as
any other.