Master Eloquent query scopes and accessors to write reusable, expressive database logic. Learn to clean up your controllers by shifting data handling to models.
Previously in this course, we covered Project Board Domain Modeling, where we established the relationships between users, projects, and tasks. Now that our schema is in place, we need to ensure our interaction with this data remains maintainable as the project grows.
In this lesson, we’re moving beyond basic CRUD. We’ll focus on how to use eloquent features to keep our domain logic expressive and our controllers thin. By leveraging query scopes and accessors, we stop repeating filtering logic and data formatting across our services and controllers.
A common pain point in growing applications is the repetition of database constraints. If you find yourself writing ->where('status', 'completed') in five different controllers, you’ve created a maintenance burden. If the definition of "completed" changes, you have to hunt down every instance.
Query scopes allow you to define these constraints as reusable methods on your model.
A local scope is defined by prefixing a method name with scope. Laravel automatically handles the translation, so calling Task::completed() triggers scopeCompleted().
PHPnamespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Builder; class Task extends Model { #6A9955">/** * Scope a query to only include completed tasks. */ public function scopeCompleted(Builder $query): Builder { return $query->where('status', 'completed'); } #6A9955">/** * Scope a query to only include tasks due soon. */ public function scopeDueSoon(Builder $query): Builder { return $query->where('due_date', '<=', now()->addDays(3)) ->where('status', '!=', 'completed'); } }
Now, instead of writing raw where clauses, your service layer reads like English:
PHP#6A9955">// Clean, expressive, and easily testable $upcomingTasks = Task::dueSoon()->get();
As discussed in Mastering Laravel Local Scopes for Cleaner Database Filtering, these methods are chainable, allowing you to build complex queries dynamically without bloating your controller.
While scopes handle retrieval, accessors and mutators handle data transformation. An accessor transforms a value when it is retrieved from the database, while a mutator modifies it before it is saved.
Let's say our Task model has a title column, but we want to ensure it’s always returned in title case when accessed via our API.
PHPuse Illuminate\Database\Eloquent\Casts\Attribute; protected function title(): Attribute { return Attribute::make( get: fn(string $value) => ucfirst($value), ); }
This is particularly useful for formatting dates, concatenating name fields, or calculating values on the fly. If you want to dive deeper into these patterns, Laravel Eloquent Accessors and Mutators: A Practical Guide provides a comprehensive look at the modern Attribute syntax introduced in recent Laravel versions.
Mutators ensure data is sanitized before it hits the database. If we want to ensure all task titles are stored in lowercase to allow for case-insensitive searching, we add a set method to our attribute definition:
PHPprotected function title(): Attribute { return Attribute::make( get: fn(string $value) => ucfirst($value), set: fn(string $value) => strtolower($value), ); }
Task model.scopeOverdue method that filters tasks where the due_date is in the past and the status is not 'completed'.is_overdue attribute that returns a boolean based on the due_date.Task::overdue()->get() returns the correct items and $task->is_overdue works as expected.where clause into a scope. Only create scopes for logic you actually reuse or that significantly improves readability.By moving database constraints into query scopes and data formatting into accessors, we keep our business logic encapsulated within the model. This makes our controllers cleaner and ensures that our data handling rules are applied consistently across the entire application.
Up next: We will implement the TaskService to handle complex task creation logic and manage user-task assignments, tying these model improvements into a cohesive service layer.
Mastering Laravel Eloquent model state with exists and wasRecentlyCreated helps you handle database records reliably. Learn to distinguish new from old.
Advanced Eloquent Scopes and Accessors
Introduction to Laravel Events and Listeners
Asynchronous Processing with Queues
Job Chaining and Batching
Feature Testing Fundamentals
Mocking Services and Repositories in Tests
Testing Events and Jobs
Database Factories and Seeding
API Versioning Strategies
Advanced Request Filtering and Sorting
Handling File Uploads in REST APIs
Real-time Notifications with Broadcasting
Using Observers for Model Lifecycle Hooks
Implementing Policies for Authorization
Customizing Authentication Guards
Rate Limiting API Endpoints
Eloquent Performance Optimization
Caching Strategies for Performance
Using Traits for Code Reuse
Advanced Dependency Injection with Service Providers
Command Line Tools with Artisan
Scheduled Tasks and Cron Jobs
Integrating Third-Party Services
Handling Webhooks
Logging and Monitoring
Database Migrations Best Practices
Advanced Testing: Integration Tests
Testing API Authentication
Code Quality and Static Analysis
Project Structure for Large Applications
Environment and Configuration Management
Deploying Laravel Applications
Database Indexing Strategies
Using Value Objects
Strategy Pattern for Business Rules
Advanced Queue Monitoring
Building a Search API
Handling Concurrency and Race Conditions
API Documentation with OpenAPI
Testing with Test Doubles
Implementing Multi-Tenancy
Refactoring Legacy Code
Using Middleware for Feature Flags
Building Reusable Packages
Performance Profiling
Secure API Design
Event Sourcing Concepts