Command Query Separation (CQS)
debt(d7/e3/b5/t5)
Closest to 'only careful code review or runtime testing' (d7). While phpstan and psalm are listed as detection tools, CQS violations are not automatically detectable — no static analyzer can reliably determine if a method 'should' be a query or command. The code patterns (method returning data AND writing to database, getter with side effects) require manual review to identify as violations rather than intentional design choices.
Closest to 'simple parameterised fix' (e3). The quick_fix states splitting a function that does both query and command into two separate functions. This is a localized refactor — extracting one method into two, updating callers to use the appropriate method. Not a one-liner (e1), but contained within a single component's scope.
Closest to 'persistent productivity tax' (b5). CQS applies across all contexts (web, cli, queue-worker) and is tagged as a principle affecting OOP and architecture. Once adopted, it shapes how every method signature is designed throughout the codebase. Not quite 'defines the system's shape' (b9), but more than a localized tax — it's a consistent mental model that affects all method design decisions.
Closest to 'notable trap' (t5). The misconception explicitly states developers confuse CQS (method-level rule) with CQRS (architectural pattern). This is a documented gotcha that most developers eventually learn, but initially causes confusion about scope and application. The common_mistakes also note acceptable exceptions (pop/shift) that can trip up developers applying the rule too rigidly.
Also Known As
TL;DR
Explanation
CQS (Bertrand Meyer) separates methods into commands (mutate state, return void) and queries (return value, no side effects). Mixing both in one method makes code harder to reason about — calling a method to get a value shouldn't change anything observable. Violations include pop() (removes and returns), which is sometimes justified for atomicity, but in most PHP code, separating read and write operations improves testability, predictability, and comprehension.
Common Misconception
Why It Matters
Common Mistakes
- Methods that both modify state and return the new state — callers cannot query without triggering changes.
- pop(), shift(), or similar stack/queue methods that violate CQS by design — acceptable exceptions but should be documented.
- Repository save() methods that return the persisted entity — borderline; acceptable if the entity is enriched (ID assigned).
- Not distinguishing CQS (method-level) from CQRS (architecture-level) — they operate at different scales.
Code Examples
// Violates CQS: modifies state AND returns a value
public function popItem(): Item {
$item = array_shift($this->items); // mutation
return $item; // query
}
// Separate command and query
public function removeFirst(): void { array_shift($this->items); } // command
public function first(): Item { return $this->items[0]; } // query
// Or for genuine stacks/queues, accept the exception with clear naming:
public function dequeue(): Item { ... } // name signals it both reads and mutates