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
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.