One of Laravel's most beloved features is Eloquent ORM—an elegant implementation of the Active Record pattern that makes database operations feel natural and expressive. At ZIRA Software, Eloquent has dramatically improved our development speed and code readability across dozens of Laravel projects.
Why Eloquent ORM?
Traditional PHP database code can be verbose and error-prone:
// Traditional PHP with MySQL
$result = mysql_query("SELECT * FROM users WHERE email = '" . mysql_real_escape_string($email) . "'");
$user = mysql_fetch_assoc($result);
if ($user) {
$posts_result = mysql_query("SELECT * FROM posts WHERE user_id = " . $user['id']);
while ($post = mysql_fetch_assoc($posts_result)) {
// Process posts
}
}
With Eloquent, the same operation becomes:
$user = User::where('email', $email)->first();
$posts = $user->posts;
Clean, readable, and safe from SQL injection.
Creating Your First Model
Eloquent models typically live in app/models and extend Eloquent:
<?php
class User extends Eloquent {
protected $table = 'users'; // Optional if table name is 'users'
protected $fillable = ['name', 'email', 'password'];
protected $hidden = ['password', 'remember_token'];
}
Convention over configuration:
- Table name: plural of model name (
usersforUser) - Primary key:
idcolumn - Timestamps:
created_atandupdated_atcolumns
Basic CRUD Operations
// Create
$user = new User;
$user->name = 'John Doe';
$user->email = 'john@example.com';
$user->save();
// Or use mass assignment
$user = User::create([
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => Hash::make('secret')
]);
// Read
$user = User::find(1);
$users = User::all();
$activeUsers = User::where('status', 'active')->get();
// Update
$user = User::find(1);
$user->email = 'newemail@example.com';
$user->save();
// Or update without retrieval
User::where('id', 1)->update(['email' => 'newemail@example.com']);
// Delete
$user = User::find(1);
$user->delete();
// Or delete without retrieval
User::destroy(1);
User::destroy([1, 2, 3]);
Eloquent Relationships
This is where Eloquent truly shines. Define relationships once in your models, then use them seamlessly throughout your application.
One-to-One
// User model
class User extends Eloquent {
public function phone()
{
return $this->hasOne('Phone');
}
}
// Phone model
class Phone extends Eloquent {
public function user()
{
return $this->belongsTo('User');
}
}
// Usage
$user = User::find(1);
echo $user->phone->number;
$phone = Phone::find(1);
echo $phone->user->name;
One-to-Many
// User model
class User extends Eloquent {
public function posts()
{
return $this->hasMany('Post');
}
}
// Post model
class Post extends Eloquent {
public function user()
{
return $this->belongsTo('User');
}
}
// Usage
$user = User::find(1);
foreach ($user->posts as $post) {
echo $post->title;
}
// Creating related models
$user->posts()->create([
'title' => 'New Post',
'content' => 'Post content...'
]);
Many-to-Many
// User model
class User extends Eloquent {
public function roles()
{
return $this->belongsToMany('Role');
}
}
// Role model
class Role extends Eloquent {
public function users()
{
return $this->belongsToMany('User');
}
}
// Usage
$user = User::find(1);
foreach ($user->roles as $role) {
echo $role->name;
}
// Attach/detach
$user->roles()->attach(1);
$user->roles()->attach([1, 2, 3]);
$user->roles()->detach(1);
$user->roles()->sync([1, 2, 3]); // Sync to exactly these IDs
Has-Many-Through
// Country has many Posts through Users
class Country extends Eloquent {
public function posts()
{
return $this->hasManyThrough('Post', 'User');
}
}
// Usage
$country = Country::find(1);
$posts = $country->posts; // All posts by users in this country
Polymorphic Relations
// Photo can belong to either Staff or Product
class Photo extends Eloquent {
public function imageable()
{
return $this->morphTo();
}
}
class Staff extends Eloquent {
public function photos()
{
return $this->morphMany('Photo', 'imageable');
}
}
class Product extends Eloquent {
public function photos()
{
return $this->morphMany('Photo', 'imageable');
}
}
// Usage
$staff = Staff::find(1);
foreach ($staff->photos as $photo) {
echo $photo->path;
}
Eager Loading: The N+1 Problem Solution
The N+1 query problem is a common performance killer:
// BAD - N+1 queries
$posts = Post::all(); // 1 query
foreach ($posts as $post) {
echo $post->user->name; // N queries (one per post)
}
// Total: 1 + N queries
Eager loading solves this:
// GOOD - 2 queries total
$posts = Post::with('user')->get(); // 2 queries total
foreach ($posts as $post) {
echo $post->user->name; // No additional queries
}
Load multiple relationships:
$posts = Post::with(['user', 'comments', 'tags'])->get();
// Nested eager loading
$users = User::with('posts.comments')->get();
// Conditional eager loading
$users = User::with(['posts' => function($query) {
$query->where('published', true)
->orderBy('created_at', 'desc');
}])->get();
Query Scopes
Define reusable query constraints:
class Post extends Eloquent {
public function scopePublished($query)
{
return $query->where('status', 'published');
}
public function scopeByAuthor($query, $authorId)
{
return $query->where('user_id', $authorId);
}
public function scopeRecent($query)
{
return $query->orderBy('created_at', 'desc')
->take(10);
}
}
// Usage
$posts = Post::published()->recent()->get();
$authorPosts = Post::published()->byAuthor(5)->get();
// Chain multiple scopes
$posts = Post::published()
->byAuthor(5)
->where('featured', true)
->get();
Accessors and Mutators
Transform attributes when retrieving or setting:
class User extends Eloquent {
// Accessor - modify data when retrieving
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
// Mutator - modify data when setting
public function setPasswordAttribute($value)
{
$this->attributes['password'] = Hash::make($value);
}
// Virtual attribute
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
}
// Usage
$user = User::find(1);
echo $user->first_name; // Auto-capitalized
echo $user->full_name; // Virtual attribute
$user->password = 'secret'; // Auto-hashed
$user->save();
Model Events
Hook into model lifecycle:
class User extends Eloquent {
public static function boot()
{
parent::boot();
// Before create
static::creating(function($user) {
$user->token = Str::random(32);
});
// After create
static::created(function($user) {
Mail::send('emails.welcome', ['user' => $user], function($message) use ($user) {
$message->to($user->email)->subject('Welcome!');
});
});
// Before update
static::updating(function($user) {
if ($user->isDirty('email')) {
$user->email_verified = false;
}
});
// Before delete
static::deleting(function($user) {
$user->posts()->delete(); // Delete related posts
});
}
}
Available events: creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored.
Soft Deletes
Keep deleted records in the database:
class Post extends Eloquent {
protected $softDelete = true;
}
// Now deletes just set deleted_at timestamp
$post = Post::find(1);
$post->delete(); // Sets deleted_at, doesn't remove from DB
// Query including soft deleted
$posts = Post::withTrashed()->get();
// Only soft deleted
$posts = Post::onlyTrashed()->get();
// Restore soft deleted
$post->restore();
// Permanently delete
$post->forceDelete();
Advanced Queries
// Aggregates
$count = Post::where('published', true)->count();
$max = Order::max('total');
$avg = Order::where('status', 'paid')->avg('total');
// Chunking (for large datasets)
Post::chunk(200, function($posts) {
foreach ($posts as $post) {
// Process post
}
});
// Raw queries
$users = User::whereRaw('age > ? and votes = 100', [25])->get();
// Joins
$users = User::join('posts', 'users.id', '=', 'posts.user_id')
->select('users.*', 'posts.title')
->get();
// Group by
$userCounts = User::groupBy('country')
->selectRaw('country, count(*) as total')
->get();
Collections
Eloquent returns Collection objects with powerful methods:
$users = User::all();
$names = $users->pluck('name');
$active = $users->filter(function($user) {
return $user->status === 'active';
});
$grouped = $users->groupBy('country');
$sorted = $users->sortBy('created_at');
$emails = $users->map(function($user) {
return $user->email;
});
Best Practices from ZIRA Software
- Always use eager loading to avoid N+1 queries
- Use scopes for reusable query logic
- Leverage accessors/mutators for data transformation
- Protect mass assignment with
$fillableor$guarded - Use soft deletes when data might need restoration
- Keep models lean - move complex logic to repositories or services
Conclusion
Laravel's Eloquent ORM strikes a perfect balance between simplicity and power. Whether you're building a simple blog or complex SaaS application, Eloquent provides the tools you need while keeping your code elegant and maintainable.
Building a Laravel application and need expert guidance? Contact ZIRA Software to discuss your project. We specialize in Laravel development with best practices and proven patterns.