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

Active Record Pattern

Code Quality PHP 5.0+ Intermediate
debt(d7/e7/b8/t5)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). While phpstan and phpcs are listed as detection tools, the detection_hints note automated=no — the real problem (business logic creeping into models, wrong architectural choice for a complex domain) cannot be caught automatically. The code_pattern describes Eloquent models with business logic methods sending emails/making HTTP calls, which requires careful code review to identify as a misuse. A static analysis rule could flag model size or certain method patterns, but the architectural mismatch is fundamentally a review-time judgment.

e7 Effort Remediation debt — work required to fix once spotted

Closest to 'cross-cutting refactor across the codebase' (e7). The quick_fix says 'use service classes or domain objects for logic, and models only for persistence,' but once Active Record models have become God Objects with 50+ methods, extracting business logic into services/domain objects touches every controller, job, command, and test that calls those model methods. This is a cross-cutting refactor. Moving from Active Record to Repository/Data Mapper when the domain has outgrown it is even harder, approaching architectural rework (e9). Scoring e7 as the typical remediation case.

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

Closest to 'defines the system's shape' (b9), adjusted down to b8. Active Record is a foundational architectural pattern that applies across all contexts (web, cli, queue-worker per applies_to). It shapes how every piece of the application interacts with data — models are referenced everywhere, relationships define the data graph, query scopes define how data is accessed. It has strong gravitational pull (b7) and arguably defines the system's shape (b9). Scoring b8 because while it's deeply load-bearing, frameworks like Laravel do provide escape hatches (raw queries, query builder) so it doesn't completely lock you in.

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

Closest to 'notable trap (a documented gotcha most devs eventually learn)' (t5). The misconception field states developers wrongly believe 'Active Record is always an anti-pattern,' when it's actually appropriate for simple CRUD. The inverse trap — using it for complex domains — is equally common. Common_mistakes list N+1 queries from lazy loading, God Objects from business logic in models, and difficulty unit testing without a database. These are well-documented gotchas that most developers eventually learn but routinely fall into initially. Not t7 because Active Record behaves as advertised; the trap is about scope/boundary of appropriate use rather than contradicting expectations.

About DEBT scoring →

Also Known As

Active Record Eloquent ORM Active Record

TL;DR

A design pattern where a database row is wrapped in an object that includes both the data and the persistence logic — the object knows how to save, update, and delete itself.

Explanation

Active Record (Martin Fowler, PoEAA) places persistence logic inside the domain object: User::find(1), $user->save(), $user->delete(). The object maps directly to a database row. Advantages: simple, low boilerplate, excellent for CRUD applications. Disadvantages: couples domain logic to persistence, makes unit testing without a database hard, violates SRP, and becomes unwieldy for complex domain logic. Laravel's Eloquent, Ruby on Rails, Django ORM are all Active Record implementations.

Common Misconception

Active Record is always an anti-pattern — it is entirely appropriate for simple CRUD applications and rapid development; the Repository pattern adds complexity only justified by complex domain logic.

Why It Matters

Choosing Active Record for a complex domain with rich business logic leads to God Objects that are impossible to test; choosing Repository for a simple CRUD API adds unnecessary abstraction layers.

Common Mistakes

  • Business logic inside Active Record models — they become unmaintainable God Objects.
  • Unit testing Active Record models without a database — requires database setup, making tests slow.
  • N+1 queries from lazy loading — always eager-load relationships with with() in Eloquent.
  • Using Active Record when a complex aggregate requires multiple tables — use Repository instead.

Avoid When

  • Domain logic is complex — mixing business rules with persistence makes both harder to test and change.
  • You need to support multiple storage backends or swap databases; the object is tightly coupled to one schema.
  • Writing unit tests that should not touch a database — the persistence coupling forces slow integration tests.

When To Use

  • Simple CRUD-heavy apps where domain logic is thin and co-locating persistence with data is acceptable.
  • Rapid prototyping or internal tools where the overhead of a separate data mapper is not justified.
  • Teams already using a framework (Laravel Eloquent, Rails) where Active Record is the idiomatic default.

Code Examples

💡 Note
The bad example shows an Active Record model handling validation, payment, and email — three concerns that should live elsewhere once the domain grows.
✗ Vulnerable
// Active Record model doing too much:
class Order extends Model {
    public function process(): void {
        // Validation, pricing, inventory, emails all in one class
        if ($this->total > $this->customer->credit_limit) throw new Exception();
        $this->status = 'processing';
        $this->save();
        $this->inventory->decrement($this->items);
        Mail::send(new OrderConfirmation($this));
        // Untestable without DB, email service, inventory service
    }
}
✓ Fixed
// Active Record for simple CRUD — appropriate:
class User extends Model {
    protected $fillable = ['name', 'email'];
    protected $hidden   = ['password'];

    public function orders(): HasMany {
        return $this->hasMany(Order::class);
    }
}
// Simple — maps rows to objects, relationships via ORM
// Complex domain logic extracted to dedicated service classes

Added 16 Mar 2026
Edited 31 Mar 2026
Views 62
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
1 ping T 0 pings W 0 pings T 0 pings F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 1 ping F 1 ping S 4 pings S 1 ping M 0 pings T 2 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 4 pings T 0 pings F 1 ping S 1 ping S 1 ping M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Perplexity 8 Google 8 Amazonbot 7 Scrapy 7 ChatGPT 5 Ahrefs 4 Unknown AI 3 Claude 2 Bing 2 PetalBot 1
crawler 44 crawler_json 3
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: Medium
⚡ Quick Fix
Keep business logic out of Active Record models — use service classes or domain objects for logic, and models only for persistence; avoid fat models with 50+ methods
📦 Applies To
PHP 5.0+ web cli queue-worker laravel eloquent
🔗 Prerequisites
🔍 Detection Hints
Eloquent model with business logic methods sending emails making HTTP calls calculating prices
Auto-detectable: ✗ No phpstan phpcs
⚠ Related Problems
🤖 AI Agent
Confidence: Low False Positives: High ✗ Manual fix Fix: High Context: Class Tests: Update


✓ schema.org compliant