One-to-one relationships — hasOne, belongsTo
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.
// Define
public function scopeActive($query) { return $query->where('active', true); }
// Use
User::active()->get();
User::active()->where('role', 'admin')->get();Scopes with parameters:
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
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();