← CodeClarityLab Home
Browse by Category
+ added · updated 7d
← Back to glossary

Message Chains

quality Intermediate
debt(d5/e3/b5/t7)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5). The detection_hints list phpmd and phpstan as the tools, and automated is marked 'no' — meaning these tools can flag the pattern but it requires deliberate configuration of a specialist static analysis tool rather than a default linter or compiler check. Not silent in production (d7/d9) because the pattern is structurally visible in code review, but it won't be caught by a basic linter pass.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). The quick_fix describes adding a single delegation method (e.g. $invoice->getCustomerCity()) to replace the chain. This is a small, localised refactor — add one method on the outer object, update callers of the chain — contained within one or a few files. Not a one-line swap (e1) because callers must be updated, but not a multi-component refactor either.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5). The applies_to scope covers web, cli, and queue-worker contexts broadly. The why_it_matters states that the caller must understand every intermediate object's API, meaning the coupling tax is paid by every developer who touches the calling code. However, each individual chain is localised — it doesn't define the whole system's shape (b7/b9) — so b5 fits: it slows down multiple work streams without being fully architectural.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7). The misconception field explicitly states that developers believe message chains are 'readable and expressive so they should be encouraged,' directly contradicting the actual problem: each navigation step couples the caller to an intermediate type. This is a well-documented gotcha (Law of Demeter) but it contradicts the intuition that fluent-looking code is good code. The additional common_mistake of confusing message chains with builder patterns (which also chain) reinforces that the trap is serious — developers actively conflate two superficially similar but fundamentally different patterns.

About DEBT scoring →

Also Known As

train wreck code method chain smell dot chain

TL;DR

A long chain of method calls ($a->getB()->getC()->getD()) that tightly couples code to the internal structure of multiple objects.

Explanation

Message chains occur when code navigates a deep object graph to retrieve a value — each navigation exposes the internal structure of the intermediate objects. This violates the Law of Demeter and creates brittle code: any change to the intermediate objects breaks the chain. The remedy is Hide Delegate: introduce a method on the first object that returns what the caller actually needs, keeping the navigation internal. Short chains on a single object or fluent builder are not this smell — the issue is cross-object graph traversal.

Common Misconception

Message chains are readable and expressive so they should be encouraged. Chains that navigate through multiple objects couple the caller to every intermediate type — a change anywhere in the chain breaks the caller even if the endpoint did not change.

Why It Matters

Message chains couple the caller to the entire chain structure — removing any link breaks the caller, and the calling code must understand every intermediate object's API.

Common Mistakes

  • Chaining through DTOs to reach a nested value — add a convenience accessor on the outer object.
  • Not recognising that each method call in a chain is a navigation step through the object graph.
  • Confusing message chains with builder patterns — builders chain on the same object; message chains traverse different objects.
  • Hiding message chains inside private methods — the smell moves but the coupling remains.

Code Examples

✗ Vulnerable
$city = $order->getCustomer()->getAddress()->getCity()->getName();
✓ Fixed
// Add getCustomerCity() to Order — hide the internal navigation
$city = $order->getCustomerCity();

Added 15 Mar 2026
Edited 22 Mar 2026
Views 19
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 1 ping T 0 pings F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 2 pings F 1 ping S 1 ping S 0 pings M 0 pings T 0 pings W 1 ping T 0 pings F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W 1 ping T 0 pings F
No pings yet today
Amazonbot 9 Perplexity 2 Unknown AI 2 Google 2 Ahrefs 2 ChatGPT 2
crawler 16 crawler_json 2 pre-tracking 1
DEV INTEL Tools & Severity
🟢 Low ⚙ Fix effort: Medium
⚡ Quick Fix
Add a delegation method: if callers always chain $invoice->getCustomer()->getAddress()->getCity(), add $invoice->getCustomerCity() — one method instead of three-level knowledge of the object graph
📦 Applies To
any web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
$a->getB()->getC()->doSomething() three or more chained method calls; callers knowing structure of objects two levels deep
Auto-detectable: ✗ No phpmd phpstan
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: Medium Context: Function Tests: Update

✓ schema.org compliant