Command Pattern
debt(d7/e5/b5/t5)
Closest to 'only careful code review or runtime testing' (d7). PHPStan is listed but cannot automatically detect when the Command pattern should be used — it can only enforce rules on existing commands. The detection_hints note 'automated: no' and the code_pattern describes duplicated logic across controllers and queue jobs, which requires manual code review to identify. No static analysis tool reliably catches missing abstraction opportunities.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix suggests encapsulating requests as Command objects, but common_mistakes reveal retrofitting undo() is expensive, and making commands serializable for queuing requires changing from service references to value types. This typically involves creating command classes, handlers, and updating multiple call sites — a significant refactor within one domain area.
Closest to 'persistent productivity tax' (b5). The pattern applies across web, cli, and queue-worker contexts per applies_to. Once adopted, commands become a load-bearing abstraction — every new operation needs a command class, handler registration, and potentially queue/serialization considerations. It shapes how features are implemented without fully defining the system architecture.
Closest to 'notable trap' (t5). The misconception explicitly states developers wrongly believe Command is 'only useful for undo/redo functionality' when it actually enables queuing, logging, macro recording, and decoupling. This is a documented gotcha that most developers eventually learn through experience with command buses and CQRS discussions, but initial misunderstanding leads to either avoiding the pattern or implementing it without considering serialization and dependency injection requirements.
Also Known As
TL;DR
Explanation
The Command pattern wraps a request — with all its parameters — as an object implementing a common execute() interface. The invoker (controller, queue worker) knows only the Command interface; the receiver (domain service) knows nothing about the invoker. This enables: queuing and deferred execution, logging all executed commands for audit, undo/redo by implementing an unexecute() method, and macro commands (composites of commands). In PHP, the Command Bus pattern (used in CQRS frameworks like Tactician and Laravel's Command Bus) dispatches command objects to dedicated handler classes.
Common Misconception
Why It Matters
Common Mistakes
- Implementing Command without an undo() method when reversibility is a requirement — retrofitting it later is expensive.
- Commands that access external services directly instead of through injected dependencies — makes testing hard.
- Using a command bus for simple CRUD with no queue, audit, or retry requirement — overkill that adds complexity.
- Not making commands serializable when they need to be queued or persisted — use value types, not service references.
Code Examples
// Direct invocation instead of command — no queuing, logging, or undo:
class OrderController {
public function cancel(int $id): void {
$order = Order::find($id);
$order->status = 'cancelled'; // Logic scattered, no audit trail
$order->save();
}
}
interface Command {
public function execute(): void;
public function undo(): void;
}
class MoveFileCommand implements Command {
public function __construct(
private string $from,
private string $to,
) {}
public function execute(): void { rename($this->from, $this->to); }
public function undo(): void { rename($this->to, $this->from); }
}
class CommandHistory {
private array $stack = [];
public function execute(Command $cmd): void {
$cmd->execute();
$this->stack[] = $cmd;
}
public function undo(): void {
array_pop($this->stack)?->undo();
}
}