Design by Contract
debt(d7/e5/b5/t5)
Closest to 'only careful code review or runtime testing' (d7). While phpstan and psalm can detect some type-related contract violations, the core issue — undocumented assumptions, missing assertions, implicit contracts — requires careful code review to identify. Static analyzers catch type mismatches but cannot detect that a method assumes 'positive integer' when it only declares 'int'. Runtime assertions may catch violations, but missing contracts are invisible until integration bugs surface.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix suggests documenting and enforcing contracts with assertions, which sounds simple but requires examining each method's assumptions, adding docblock annotations, and inserting assertion calls. For a codebase that never practiced DbC, this is a component-wide refactor to retrofit contracts onto existing methods systematically.
Closest to 'persistent productivity tax' (b5). Design by contract applies across all contexts (web, cli, queue-worker) and becomes a persistent discipline — every new method needs contract documentation, every modification must preserve invariants. It's not architectural-defining (b7+) but it does create ongoing overhead that shapes how developers write and review code across the system.
Closest to 'notable trap' (t5). The misconception field explicitly states that developers confuse DbC with defensive programming. This is a documented gotcha that most devs eventually learn — DbC assigns responsibility (caller meets preconditions, callee guarantees postconditions) while defensive programming validates everything regardless. The common mistake of 'checking preconditions inside a method that the caller is responsible for' directly reflects this trap.
Also Known As
TL;DR
Explanation
Design by Contract (Bertrand Meyer, Eiffel) treats method interfaces as contracts: the caller satisfies preconditions, the callee satisfies postconditions, and the class maintains invariants throughout. In PHP, contracts are implemented via assertions or guard clauses at method entry, return type declarations, and documented invariants. Enforcing contracts explicitly shifts debugging from 'something went wrong somewhere' to 'this specific contract was violated at this specific point', dramatically reducing the time to locate bugs.
Common Misconception
Why It Matters
Common Mistakes
- Checking preconditions inside a method that the caller is responsible for satisfying — the method should assert, not silently correct.
- Not documenting contracts in docblocks when PHP lacks native contract syntax — implicit contracts are unknown contracts.
- Disabling contract checks in production without profiling whether they are actually expensive.
- Writing contracts that are too weak to catch real violations — a precondition of 'not null' when 'positive integer' is needed.
Code Examples
// No contract — accepts anything, fails unpredictably:
function divide(float $a, float $b): float {
return $a / $b; // Division by zero if $b === 0.0 — precondition not enforced
}
// With contract:
function divide(float $a, float $b): float {
assert($b !== 0.0, 'Precondition: divisor must not be zero');
return $a / $b;
}
/**
* @param positive-int $quantity Precondition: must be > 0
* @param float $price Precondition: must be >= 0
* @return float Postcondition: return value >= 0
*/
function lineTotal(int $quantity, float $price): float {
assert($quantity > 0, 'quantity must be positive');
assert($price >= 0, 'price must be non-negative');
$total = $quantity * $price;
assert($total >= 0, 'total postcondition violated');
return $total;
}