Camkode
Camkode

Power of Laravel Eloquent: Advanced Techniques for Clean, Scalable Apps

Posted by Kosal

Laravel’s Eloquent ORM isn’t just a convenient way to interact with your database—it’s a powerhouse capable of driving highly maintainable, high-performance applications. When you understand its deeper capabilities, you can move beyond basic CRUD operations and into building robust, expressive, and scalable data layers.

This guide explores advanced Eloquent techniques with real-world examples, actionable tips, and best practices to help you get the most out of it.


1. Advanced Query Scopes

Why use them?

Query scopes allow you to package reusable filtering logic directly into your models, keeping them DRY and easy to read.

Example:

// App\Models\Order.php
class Order extends Model
{
    public function scopeActive(Builder $query)
    {
        return $query->where('status', 'active');
    }

    public function scopeForCustomer(Builder $query, $customerId)
    {
        return $query->where('customer_id', $customerId);
    }
}

// Usage
Order::active()->forCustomer(42)->get();

Pro Tip: Scopes are chainable, so you can mix and match them for complex queries without bloating your controllers.


2. Custom Collections

Why use them?

Custom collections let you add domain-specific methods directly to your model collections. This keeps your business logic neatly packaged and your code more expressive.

Example:

// App\Models\Post.php
class Post extends Model
{
    public function newCollection(array $models = [])
    {
        return new PostCollection($models);
    }
}

// App\Collections\PostCollection.php
class PostCollection extends Illuminate\Database\Eloquent\Collection
{
    public function published()
    {
        return $this->filter(fn($post) => $post->is_published);
    }
}

// Usage
$posts = Post::all()->published();

Pro Tip: Keep complex computations in the database whenever possible—filtering huge datasets in memory can kill performance.


3. Database Transactions

Why use them?

Transactions ensure that a set of operations either all succeed or all fail—critical for financial or multi-step processes.

Example:

DB::transaction(function () use ($order, $user) {
    $order->save();
    $user->update(['credit' => $user->credit - $order->total]);

    Payment::create([
        'order_id' => $order->id,
        'amount' => $order->total,
    ]);
});

4. Polymorphic Relationships

Why use them?

They allow different models to share a single relationship table, reducing redundancy.

Example – Comments on both posts and videos:

// Comment.php
class Comment extends Model
{
    public function commentable()
    {
        return $this->morphTo();
    }
}

// Post.php
class Post extends Model
{
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

// Video.php
class Video extends Model
{
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

Usage:

$comments = $post->comments;

Pro Tip: Use morph maps to avoid storing raw class names in the database:

Relation::morphMap([
    'posts' => Post::class,
    'videos' => Video::class,
]);

5. Attribute Casting

Why use it?

Casting ensures your attributes are always in the correct format when retrieved or stored.

Example:

class User extends Model
{
    protected $casts = [
        'settings' => 'array',
        'registered_at' => 'datetime:Y-m-d',
    ];
}

Custom Cast Example:

// App\Casts\Money.php
class Money implements CastsAttributes
{
    public function get($model, string $key, $value, array $attributes)
    {
        return number_format($value / 100, 2);
    }

    public function set($model, string $key, $value, array $attributes)
    {
        return $value * 100;
    }
}

// In Model
protected $casts = [
    'price' => Money::class,
];

6. Query Optimization

Avoiding N+1 Problems:

// Bad
foreach (Post::all() as $post) {
    echo $post->user->name;
}

// Good
$posts = Post::with('user')->get();

Selecting Specific Columns:

Post::with('user:id,name')
    ->select('id', 'title', 'user_id')
    ->get();

Chunking Large Results:

Post::chunk(500, function ($posts) {
    $posts->each->doHeavyTask();
});

Monitor performance: Tools like Laravel Telescope or Debugbar can help you track query counts, time, and duplicates.


7. Model Observers

Why use them?

Observers let you separate model lifecycle events from the model itself, making your code cleaner.

Example:

// App\Observers\UserObserver.php
class UserObserver
{
    public function created(User $user)
    {
        Mail::to($user)->send(new WelcomeEmail($user));
    }
}

// In AppServiceProvider or EventServiceProvider
User::observe(UserObserver::class);

Pro Tip: Observers are for side effects—avoid modifying model data within them to prevent infinite loops.


8. Custom Builder Methods

Why use them?

For complex queries that go beyond what scopes can easily handle.

Example:

// App\Models\Builders\UserBuilder.php
class UserBuilder extends Builder
{
    public function active()
    {
        return $this->where('status', 'active');
    }
}

// App\Models\User.php
class User extends Model
{
    public function newEloquentBuilder($query)
    {
        return new UserBuilder($query);
    }
}

// Usage
User::query()->active()->get();

Pro Tip:

  • Keep transactions short.
  • Avoid API calls inside them.
  • Always handle exceptions to prevent partial commits.

Final Words

When you go beyond the basics and apply these advanced Eloquent patterns, your Laravel projects can scale more easily, run faster, and remain far easier to maintain. These techniques not only boost performance but also make your codebase more consistent, expressive, and enjoyable to work with—something every developer appreciates.