Eloquent Models & Active Record Pattern

Understanding Eloquent's Active Record pattern and how it differs from traditional ORMs

Eloquent ORM is Laravel's implementation of the Active Record pattern, where each model represents a database table and each instance represents a row. Unlike traditional Data Mapper patterns (like Doctrine), Active Record combines data access and business logic in the same object.

Why Active Record?
• Intuitive: Models directly represent database tables
• Clean Syntax: $user->save() instead of $entityManager->persist($user)
• Rapid Development: Less boilerplate code
• Convention over Configuration: Automatically maps to tables

Real-World Scenario:
You're building a blog platform. In a Data Mapper ORM, you'd need separate entity classes and repository classes. With Eloquent, your Post model handles everything: queries, relationships, and persistence. This makes development faster and code more readable.

Trade-offs:
• Active Record couples business logic to database structure
• Data Mapper provides better separation but requires more code
• For most Laravel apps, Active Record is perfect
• For complex domains, consider adding a Service layer

Code Examples

Basic Eloquent Model
💡 Solution php
<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Post extends Model
{
    use SoftDeletes;
    
    // Table name (optional if following convention)
    protected $table = 'posts';
    
    // Primary key (optional if 'id')
    protected $primaryKey = 'id';
    
    // Mass assignable attributes
    protected $fillable = [
        'title',
        'slug',
        'content',
        'user_id',
        'published_at',
    ];
    
    // Attributes that should be hidden
    protected $hidden = [
        'deleted_at',
    ];
    
    // Attribute casting
    protected $casts = [
        'published_at' => 'datetime',
        'is_featured' => 'boolean',
        'metadata' => 'array',
    ];
    
    // Timestamps (default: true)
    public $timestamps = true;
}

// Using the model
$post = new Post();
$post->title = "My First Post";
$post->content = "Hello World";
$post->save();  // INSERT query

// Or use mass assignment
$post = Post::create([
    'title' => 'My Second Post',
    'content' => 'Lorem ipsum',
]);

// Update
$post->title = "Updated Title";
$post->save();  // UPDATE query

// Delete
$post->delete();  // Soft delete (moves to deleted_at)
$post->forceDelete();  // Permanent delete

💡 Eloquent models extend Model and provide a clean interface for database operations. Properties like $fillable protect against mass assignment vulnerabilities.

Model Conventions vs Configuration
💡 Solution php
<?php
// Laravel uses conventions to reduce configuration

// ✅ CONVENTION (Recommended):
class User extends Model
{
    // Laravel automatically knows:
    // - Table name: 'users' (plural, snake_case)
    // - Primary key: 'id'
    // - Timestamps: created_at, updated_at
}

// 📝 CONFIGURATION (when you need different names):
class Person extends Model
{
    protected $table = 'people';  // Not 'persons'
    protected $primaryKey = 'person_id';  // Not 'id'
    public $timestamps = false;  // No timestamp columns
}

// Real-world example: Legacy database
class LegacyCustomer extends Model
{
    protected $connection = 'legacy_db';  // Different database
    protected $table = 'tbl_customers';   // Ugly table name
    protected $primaryKey = 'customer_pk'; // Non-standard PK
    public $incrementing = false;  // UUID instead of auto-increment
    protected $keyType = 'string';
}

💡 Follow Laravel conventions when possible. Only override when working with legacy databases or special requirements.

Attribute Casting - Type Safety
💡 Solution php
<?php
class Product extends Model
{
    protected $casts = [
        // Scalar types
        'price' => 'decimal:2',
        'stock' => 'integer',
        'is_available' => 'boolean',
        
        // Dates
        'published_at' => 'datetime',
        'expires_at' => 'date',
        
        // JSON
        'options' => 'array',
        'metadata' => 'collection',
        'settings' => 'object',
        
        // Custom casts
        'status' => ProductStatus::class,  // Enum (PHP 8.1+)
        'dimensions' => AsArrayObject::class,
    ];
}

// Without casting (BAD):
$product = Product::find(1);
$isAvailable = $product->is_available;  // "1" (string)
if ($isAvailable) { }  // Always true, even for "0"!

// With casting (GOOD):
$product = Product::find(1);
$isAvailable = $product->is_available;  // true (boolean)
if ($isAvailable) { }  // Properly evaluates

// Array casting example:
$product->options = ['color' => 'red', 'size' => 'large'];
$product->save();
// Stored in DB as: {"color":"red","size":"large"}

$retrieved = Product::find($product->id);
$color = $retrieved->options['color'];  // 'red' (array, not JSON string)

// Date casting:
$product->published_at = now();
$product->save();

$retrieved = Product::find($product->id);
$daysAgo = $retrieved->published_at->diffForHumans();  // "2 hours ago"
$formatted = $retrieved->published_at->format('Y-m-d');  // Carbon instance

💡 Casts automatically convert attributes to proper PHP types when retrieving from the database and back to storable formats when saving.