Introduction
Laravel’s Eloquent ORM (Object-Relational Mapping) is a powerful tool that provides an elegant and easy-to-use syntax for interacting with databases. While Eloquent’s basic CRUD operations are straightforward, mastering its advanced features can significantly improve the performance and maintainability of your Laravel applications. This comprehensive guide will delve into advanced Eloquent techniques, covering everything from relationships and scopes to advanced query building and performance optimization.
1. Eloquent Relationships
One-to-One
A one-to-one relationship is a basic relation where one model is related to a single instance of another model.
Defining One-to-One
In the User
model:
class User extends Authenticatable
{
public function profile()
{
return $this->hasOne(Profile::class);
}
}
In the Profile
model:
class Profile extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
Using One-to-One
$user = User::find(1);
$profile = $user->profile;
$profile = Profile::find(1);
$user = $profile->user;
One-to-Many
A one-to-many relationship is used when one model owns many instances of another model.
Defining One-to-Many
In the Post
model:
class Post extends Model
{
public function comments()
{
return $this->hasMany(Comment::class);
}
}
In the Comment
model:
class Comment extends Model
{
public function post()
{
return $this->belongsTo(Post::class);
}
}
Using One-to-Many
$post = Post::find(1);
$comments = $post->comments;
$comment = Comment::find(1);
$post = $comment->post;
Many-to-Many
A many-to-many relationship is used when both models can have many instances of the other model.
Defining Many-to-Many
In the User
model:
class User extends Authenticatable
{
public function roles()
{
return $this->belongsToMany(Role::class);
}
}
In the Role
model:
class Role extends Model
{
public function users()
{
return $this->belongsToMany(User::class);
}
}
Using Many-to-Many
$user = User::find(1);
$roles = $user->roles;
$role = Role::find(1);
$users = $role->users;
Has Many Through
A “has many through” relationship provides a convenient shortcut for accessing distant relations via an intermediate relation.
Defining Has Many Through
In the Country
model:
class Country extends Model
{
public function posts()
{
return $this->hasManyThrough(Post::class, User::class);
}
}
In the User
model:
class User extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}
}
Using Has Many Through
$country = Country::find(1);
$posts = $country->posts;
Polymorphic Relations
Polymorphic relationships allow a model to belong to more than one other model on a single association.
Defining Polymorphic Relations
In the Comment
model:
class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
}
In the Post
model:
class Post extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
In the Video
model:
class Video extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
Using Polymorphic Relations
$post = Post::find(1);
$comments = $post->comments;
$video = Video::find(1);
$comments = $video->comments;
$comment = Comment::find(1);
$commentable = $comment->commentable;
2. Query Scopes
Local Scopes
Local scopes allow you to define common constraints that you may easily reuse throughout your application.
Defining Local Scopes
In the User
model:
class User extends Model
{
public function scopeActive($query)
{
return $query->where('active', 1);
}
public function scopeOfType($query, $type)
{
return $query->where('type', $type);
}
}
Using Local Scopes
$users = User::active()->get();
$users = User::ofType('admin')->get();
Global Scopes
Global scopes allow you to add constraints to all queries for a given model.
Defining Global Scopes
Create a global scope class:
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class ActiveScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$builder->where('active', 1);
}
}
Apply the global scope in the model:
namespace App\Models;
use App\Scopes\ActiveScope;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected static function booted()
{
static::addGlobalScope(new ActiveScope);
}
}
Using Global Scopes
$users = User::all(); // Only active users
3. Eloquent Events
Eloquent models fire several events, allowing you to hook into various points in a model’s lifecycle.
Available Events
retrieved
creating
created
updating
updated
saving
saved
deleting
deleted
restoring
restored
Using Eloquent Events
Registering an event in the model:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected static function booted()
{
static::creating(function ($user) {
// Logic before creating a user
});
static::updating(function ($user) {
// Logic before updating a user
});
}
}
4. Mutators and Accessors
Accessors
Accessors allow you to format Eloquent attribute values when you retrieve them.
Defining Accessors
In the User
model:
class User extends Model
{
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
}
Using Accessors
$user = User::find(1);
echo $user->full_name;
Mutators
Mutators allow you to format attribute values when you set them.
Defining Mutators
In the User
model:
class User extends Model
{
public function setPasswordAttribute($value)
{
$this->attributes['password'] = bcrypt($value);
}
}
Using Mutators
$user = User::find(1);
$user->password = 'newpassword';
$user->save();
5. Attribute Casting
Attribute casting provides a convenient way to convert attributes to common data types.
Defining Attribute Casts
In the User
model:
class User extends Model
{
protected $casts = [
'is_admin' => 'boolean',
'created_at' => 'datetime',
'settings' => 'array',
];
}
Using Attribute Casts
$user = User::find(1);
$isAdmin = $user->is_admin;
$createdAt = $user->created_at;
$settings = $user->settings;
6. Custom Collections
Eloquent allows you to return custom collection objects, enhancing the functionality of Eloquent collections.
Defining Custom Collections
Create a custom collection class:
namespace App\Collections;
use Illuminate\Database\Eloquent\Collection;
class UserCollection extends Collection
{
public function active()
{
return $this->filter(function ($user) {
return $user->active;
});
}
}
Using Custom Collections
In the User
model:
namespace App\Models;
use App\Collections\UserCollection;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function newCollection(array $models = [])
{
return new UserCollection($models);
}
}
Using the custom collection:
$users = User::all();
$active
Users = $users->active();
7. Query Builders and Raw Expressions
Using the Query Builder
Eloquent provides a fluent interface for building queries.
Basic Queries
$users = User::where('active', 1)->get();
$user = User::where('email', '[email protected]')->first();
Raw Expressions
Use raw expressions for complex queries or database-specific functions.
Raw Selects
$users = User::selectRaw('count(*) as user_count, status')
->where('status', 'active')
->groupBy('status')
->get();
Raw Where Clauses
$users = User::whereRaw('age > ? and votes = 100', [25])
->get();
8. Performance Optimization
Eager Loading
Eager loading reduces the number of queries executed by loading relationships in advance.
Defining Eager Loading
$users = User::with('posts')->get();
Lazy Eager Loading
Lazy eager loading loads relationships after the initial query.
Using Lazy Eager Loading
$users = User::all();
$users->load('posts');
Query Caching
Cache query results to improve performance.
Using Query Caching
use Illuminate\Support\Facades\Cache;
$users = Cache::remember('users', 60, function () {
return User::all();
});
9. Eloquent Serialization
Converting Models to Arrays or JSON
Eloquent models can be easily converted to arrays or JSON.
Converting to Arrays
$userArray = $user->toArray();
Converting to JSON
$userJson = $user->toJson();
Hiding Attributes from JSON
Hide sensitive attributes when converting to JSON.
Defining Hidden Attributes
In the User
model:
class User extends Model
{
protected $hidden = ['password', 'remember_token'];
}
10. Testing with Eloquent
Using Factories
Laravel factories make it easy to create test data.
Defining Factories
In the database/factories/UserFactory.php
:
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class UserFactory extends Factory
{
protected $model = User::class;
public function definition()
{
return [
'name' => $this->faker->name,
'email' => $this->faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => bcrypt('password'),
'remember_token' => Str::random(10),
];
}
}
Using Factories in Tests
use App\Models\User;
public function testUserCreation()
{
$user = User::factory()->create();
$this->assertDatabaseHas('users', [
'email' => $user->email,
]);
}
11. Best Practices
Use Eager Loading
Always use eager loading to reduce the number of queries and improve performance.
Use Accessors and Mutators
Utilize accessors and mutators to encapsulate attribute transformations.
Optimize Query Performance
Use the query builder and raw expressions to optimize complex queries.
Handle Mass Assignment
Protect against mass assignment vulnerabilities by defining fillable attributes.
Use Custom Collections
Enhance Eloquent collections with custom methods to streamline your code.
Implement Caching
Cache frequently accessed data to improve performance and reduce database load.
12. Conclusion
Mastering advanced Eloquent techniques can significantly enhance the performance, maintainability, and functionality of your Laravel applications. By leveraging relationships, query scopes, events, mutators, attribute casting, custom collections, and performance optimization strategies, you can build robust and efficient applications. Follow best practices and continuously explore new features to stay ahead in the ever-evolving world of Laravel development.