Fluent Interface / Method Chaining
debt(d5/e3/b3/t5)
Closest to 'specialist tool catches it' (d5). The detection_hints list phpstan as the tool, and the code_pattern notes builder-class methods returning void when they should return $this — PHPStan with appropriate rules or custom stubs can flag this, but it won't surface via compiler or default linter. Automated detection is explicitly marked 'no', reinforcing that it requires deliberate specialist tooling rather than trivial static analysis.
Closest to 'simple parameterised fix' (e3). The quick_fix is 'Return $this (or a new instance) from every chainable method' — mechanically straightforward per method but may require touching several methods across a builder class. It does not span the whole codebase architecturally, but it is slightly more than a single one-line patch when multiple methods need updating, landing at e3.
Closest to 'localised tax' (b3). The fluent interface pattern applies within specific builder or configuration classes. Callers benefit from chaining but aren't burdened if the pattern is wrong — the structural debt is contained to the class implementing the API. The common mistakes (e.g. error signalling, immutability) create a local maintenance tax rather than a system-wide gravitational pull.
Closest to 'notable trap' (t5). The misconception field highlights the Law of Demeter confusion — competent developers often believe fluent chaining violates LoD, a well-documented but correctable gotcha. Additionally, common mistakes around error signalling (returning $this when failure needs signalling) and forcing callers to break chains for meaningful return values are documented gotchas that most PHP developers encounter eventually, placing this firmly at t5.
Also Known As
TL;DR
Explanation
A fluent interface (Martin Fowler) returns the object ($this or a new instance) from each method so calls can be chained: $query->select('users')->where('active', 1)->orderBy('name')->limit(10). This improves readability for configuration-heavy objects and DSL-like APIs. In PHP, mutable fluent interfaces return $this; immutable ones return a new clone. Common in query builders, HTTP clients, and test assertion libraries. Avoid chaining across objects — that violates the Law of Demeter. Also avoid chains so long they become unreadable or untestable.
Common Misconception
Why It Matters
Common Mistakes
- Returning $this from methods that should signal failure — callers cannot detect errors in a chain.
- Using fluent interfaces for methods with meaningful return values — forces callers to break the chain to get the value.
- Fluent setters on domain objects that should be immutable — return a new instance instead.
- Chains so long they become hard to debug — add intermediate variables at breakpoints.
Code Examples
// Without fluent interface — verbose temporary variables:
$query = new QueryBuilder();
$query->setTable('users');
$query->setWhere('active = 1');
$query->setLimit(10);
$result = $query->execute();
// Fluent:
$result = (new QueryBuilder())
->table('users')->where('active = 1')->limit(10)->execute();
class EmailBuilder {
private string $to = '';
private string $subject = '';
private string $body = '';
private array $cc = [];
public function to(string $email): static {
$this->to = $email;
return $this; // return $this enables chaining
}
public function subject(string $s): static {
$this->subject = $s;
return $this;
}
public function body(string $b): static {
$this->body = $b;
return $this;
}
public function cc(string ...$emails): static {
$this->cc = array_merge($this->cc, $emails);
return $this;
}
public function send(): void { /* dispatch */ }
}
(new EmailBuilder())
->to('alice@example.com')
->cc('bob@example.com', 'carol@example.com')
->subject('Invoice #42')
->body('Please find attached...')
->send();