| name | eloquent-expert |
| description | Advanced Eloquent ORM patterns including relationships, eager loading, factories, and query optimization. |
Eloquent Expert
Relationships
Define with Return Types
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class)->withTimestamps();
}
Relationship Types
| Type | Model A | Model B | Use Case |
|---|
hasOne | User | Profile | One-to-one |
hasMany | User | Posts | One-to-many |
belongsTo | Post | User | Inverse |
belongsToMany | Post | Tags | Many-to-many (pivot) |
hasManyThrough | Country | Posts | Through Users |
morphMany | Post/Video | Comments | Polymorphic |
Eager Loading (N+1 Prevention)
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name;
}
$posts = Post::with('author')->get();
foreach ($posts as $post) {
echo $post->author->name;
}
$posts = Post::with(['author', 'comments.user'])->get();
$posts = Post::all();
$posts->load('author');
Factories
Define Factory
class PostFactory extends Factory
{
public function definition(): array
{
return [
'title' => fake()->sentence(),
'body' => fake()->paragraphs(3, true),
'user_id' => User::factory(),
'published_at' => null,
];
}
public function published(): static
{
return $this->state(fn (array $attrs) => [
'published_at' => now(),
]);
}
}
Use Factory
$post = Post::factory()->create();
$posts = Post::factory()->count(5)->create();
$post = Post::factory()->published()->create();
$post = Post::factory()
->has(Comment::factory()->count(3))
->create();
Query Scopes
public function scopePublished(Builder $query): void
{
$query->whereNotNull('published_at');
}
public function scopeByAuthor(Builder $query, User $user): void
{
$query->where('user_id', $user->id);
}
Post::published()->byAuthor($user)->get();
Attribute Casting
protected $casts = [
'metadata' => 'array',
'is_active' => 'boolean',
'published_at' => 'datetime',
'price' => 'decimal:2',
'status' => PostStatus::class,
];
Query Optimization
User::select(['id', 'name', 'email'])->get();
Post::chunk(100, function ($posts) {
foreach ($posts as $post) {
}
});
foreach (Post::cursor() as $post) {
}
Common Patterns
Avoid N+1 with withCount
$users = User::withCount('posts')->get();
Update or Create
Post::updateOrCreate(
['slug' => $slug],
['title' => $title, 'body' => $body]
);
First or Create
$user = User::firstOrCreate(
['email' => $email],
['name' => $name]
);
Critical Rules
Avoid DB:: Facade
DB::table('posts')->where('user_id', $userId)->get();
Post::query()->where('user_id', $userId)->get();
Use Model::query() for type hints and IDE support. Only use DB:: for complex raw SQL that Eloquent can't express.
Laravel 12 Patterns
Casts Method (Preferred over $casts)
protected function casts(): array
{
return [
'metadata' => 'array',
'is_active' => 'boolean',
'published_at' => 'datetime',
'status' => PostStatus::class,
];
}
protected $casts = ['status' => PostStatus::class];
Eager Loading with Limit
$users = User::with(['posts' => function ($query) {
$query->latest()->limit(5);
}])->get();
Modern Accessors/Mutators
use Illuminate\Database\Eloquent\Casts\Attribute;
protected function fullName(): Attribute
{
return Attribute::make(
get: fn () => "{$this->first_name} {$this->last_name}",
set: fn (string $value) => [
'first_name' => ($parts = explode(' ', $value, 2))[0],
'last_name' => $parts[1] ?? '',
],
);
}
public function getFullNameAttribute(): string
{
return "{$this->first_name} {$this->last_name}";
}