0

One-to-one relationships — hasOne, belongsTo

Beginner5 min read·lv-12-005
sqlinterview

Concept

Query scopes are reusable query constraints that can be chained with Eloquent's fluent API. They keep query logic inside the model rather than scattered across controllers.

Local scopes: Methods prefixed with scope. Called without the prefix, chainable like other query methods.

php
// Define
public function scopeActive($query) { return $query->where('active', true); }
// Use
User::active()->get();
User::active()->where('role', 'admin')->get();

Scopes with parameters:

php
public function scopeOfRole($query, string $role) { return $query->where('role', $role); }
// Use
User::ofRole('admin')->get();

Global scopes: Applied to ALL queries on the model. Define as a class implementing Illuminate\Database\Eloquent\Scope and register in the model's boot() via addGlobalScope(). Soft deletes (SoftDeletes trait) work via a global scope.

Removing global scopes: User::withoutGlobalScope(ActiveScope::class)->get(). User::withoutGlobalScopes()->get() — removes all global scopes.

Anonymous global scopes: addGlobalScope('active', fn($b) => $b->where('active', true)). Removed by name: withoutGlobalScope('active').

SoftDeletes trait (special global scope): Automatically adds WHERE deleted_at IS NULL. User::withTrashed() includes soft-deleted. User::onlyTrashed() only soft-deleted.

Code Example

php
<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletes;

class Post extends Model
{
    use SoftDeletes;

    // Local scopes — method is scopeXxx, called as ->xxx()
    public function scopePublished(Builder $query): Builder
    {
        return $query->whereNotNull('published_at')
                     ->where('published_at', '<=', now());
    }

    public function scopeDraft(Builder $query): Builder
    {
        return $query->whereNull('published_at');
    }

    public function scopeByAuthor(Builder $query, int $userId): Builder
    {
        return $query->where('user_id', $userId);
    }

    public function scopePopular(Builder $query, int $minViews = 1000): Builder
    {
        return $query->where('views', '>=', $minViews)->orderByDesc('views');
    }

    // Global scope — applies to all queries
    protected static function boot(): void
    {
        parent::boot();
        static::addGlobalScope('approved', function(Builder $builder) {
            $builder->where('is_approved', true);
        });
    }
}

// Usage — scopes chain just like where()
$posts = Post::published()->get();
$posts = Post::published()->byAuthor(5)->popular(500)->paginate(10);

// Combining scopes
$draftsByMe = Post::draft()->byAuthor(auth()->id())->latest()->get();

// Global scope — all queries include 'is_approved = 1' automatically
Post::all(); // only approved
Post::find(1); // only finds approved post

// Bypass global scope
Post::withoutGlobalScope('approved')->where('user_id', 1)->get(); // all posts for user
Post::withoutGlobalScopes()->all(); // completely raw

// SoftDeletes — deleted_at global scope
Post::all();          // excludes deleted (WHERE deleted_at IS NULL)
Post::withTrashed()->all();    // includes deleted
Post::onlyTrashed()->get();    // only deleted

// Restore soft-deleted
$post = Post::withTrashed()->find(42);
$post->restore();

// Force delete
$post->forceDelete();