Class Cyclomatic Complexity
ID |
php.class_cyclomatic_complexity |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Complexity |
Language |
Php |
Tags |
complexity |
Description
Reports a class, trait or enum whose total cyclomatic complexity — the sum of the complexities of
the routines it declares — exceeds a configurable threshold (default 200).
Each routine’s own complexity comes from its control-flow graph, as arcs - nodes + 2; a routine
with no branching scores 1. Counted against a type are every method it declares plus every closure
and arrow function inside those methods, since branching moved into a callback is still branching
the type owns. Not counted are the members of a nested type: an anonymous class declared inside a
method has its own budget and is measured on its own, so the two never double-count. Abstract and
interface methods declare no body and contribute nothing, so a pure interface always scores 0.
One finding per type, on the declaration line.
Rationale
Where the per-function measure asks whether one routine has too many paths through it, this one asks whether one type has taken on too much altogether. A class can pass the per-function check on every single member and still be unownable: forty methods of five branches each are individually readable and collectively impossible to hold in your head.
That total is what says whether the type still has one reason to change. A class that keeps accumulating branches is usually a class that has quietly absorbed several responsibilities — validation, pricing, routing, notification — and every change to any of them now risks the others. It also has no natural unit test: the number of paths through the type as a whole is the product of its members' branching, not the sum a reader hopes for.
<?php
class OrderPipeline // FLAW - fourteen branchy methods, 73 in total
{
public function validate(array $order): array { /* 8 */ }
public function price(array $order): float { /* 7 */ }
public function tax(array $order): float { /* 7 */ }
public function shipping(array $order): float { /* 7 */ }
public function route(array $order): string { /* 6 */ }
public function status(array $order): string { /* 7 */ }
// ... eight more
}
class ShippingQuote // OK - one small responsibility, 4 in total
{
public function __construct(private float $weight) { }
public function amount(): float
{
if ($this->weight > 20) { return 15.0; }
return 4.0;
}
}
Remediation
Split the type along its responsibilities rather than trimming individual methods: extracting a
collaborator per concern — a validator, a pricer, a router — lowers the total and gives each part its
own tests. Where the branching is a dispatch on a type or a status, replacing the ladder with
polymorphism or a lookup table moves the decision out of the class entirely. Tune maxClassCcn to
your team’s standard.
Configuration
| Property | Default | Meaning |
|---|---|---|
|
|
Summed complexity strictly greater than this value is flagged. Deliberately far above the per-function limit: this is a whole type’s budget, and the check is aimed at the class nobody can hold in their head rather than the merely large one. |