Stop N+1 issues and unauthorized data access in their tracks. Learn to configure strict Eloquent modes and global scopes for a rock-solid production architecture.
Previously in this course, we discussed the Service Layer Pattern to decouple our business logic from the infrastructure. While that pattern protects our domain, the underlying data access layer remains vulnerable to runtime errors like lazy loading and cross-tenant data leakage. This lesson adds a layer of "defensive database programming" to your toolkit, ensuring that your application fails loudly during development rather than silently failing in production.
In a high-traffic SaaS, performance is often lost in small, silent increments. An N+1 query might be undetectable during local development with a small dataset but can cripple your database under load. Similarly, forgetting a where('tenant_id', ...) clause in a complex query is a catastrophic security vulnerability.
Laravel’s "Strict" modes are designed to turn these silent killers into immediate exceptions. By enabling them, you shift the burden of catching these bugs from your QA team to your test suite.
Strict loading prevents the "lazy loading" of relationships, which is the primary driver of the N+1 problem. When enabled, any attempt to access a relationship that hasn't been eager-loaded will throw an Illuminate\Database\LazyLoadingViolationException.
To enable this globally, you typically do this in the boot method of your AppServiceProvider:
PHPuse Illuminate\Database\Eloquent\Model; public function boot(): void { #6A9955">// Enable strict mode in local/testing environments Model::preventLazyLoading(!app()->isProduction()); #6A9955">// Optional: Log violations in production instead of throwing #6A9955">// Model::preventLazyLoading(app()->isProduction()); }
By setting this to !app()->isProduction(), you ensure that your developers are forced to use with() or load() during the development lifecycle. If a developer writes $user->profile->avatar without eager loading profile, the test suite will fail immediately.
In our SaaS platform project, data leakage between tenants is a non-negotiable failure. While you could manually add a where('tenant_id', ...) to every query, human error makes this approach risky. Global Scopes allow you to inject this constraint automatically into every Eloquent query for a specific model.
Here is how we implement a TenantScope for our Subscription model:
PHPnamespace App\Models\Scopes; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; use Illuminate\Support\Facades\Auth; class TenantScope implements Scope { public function apply(Builder $builder, Model $model): void { #6A9955">// Ensure we only retrieve records for the authenticated tenant if (Auth::check()) { $builder->where('tenant_id', Auth::user()->tenant_id); } } }
Now, apply it to the model:
PHPnamespace App\Models; use App\Models\Scopes\TenantScope; use Illuminate\Database\Eloquent\Model; class Subscription extends Model { protected static function booted(): void { static::addGlobalScope(new TenantScope); } }
With this in place, Subscription::all() now automatically executes SELECT * FROM subscriptions WHERE tenant_id = ?. You’ve effectively eliminated the possibility of accidentally leaking data from other tenants in your controllers or service classes.
AppServiceProvider and add the Model::preventLazyLoading() call as shown above.Project model in our SaaS project to ensure users only ever see their own projects.LazyLoadingViolationException is thrown.Project belonging to a different tenant. Assert that the record is not found, even without an explicit where clause.Model::preventLazyLoading(true) in production if you haven't audited your entire codebase. It will crash your site for users the moment they hit a page with a missing with() call. Use the environment check or a custom log handler.Model::withoutGlobalScope(TenantScope::class) when you intentionally need to bypass these constraints.exists() checks or count(). Ensure your tenant_id column has an index to prevent these automated queries from becoming slow.By leveraging preventLazyLoading and GlobalScopes, you transform Eloquent from a helpful abstraction into a strict guardian of your data integrity. This approach prevents common N+1 performance bottlenecks and provides a declarative way to handle multi-tenancy. You are now essentially "baking in" security and performance at the framework level.
Up next: We will dive into Advanced Subqueries and Joins to handle complex reporting requirements that go beyond simple model relationships.
Mass assignment is a critical security vulnerability where attackers inject unauthorized fields into your database. Learn to harden your Laravel models today.
Read moreMaster multi-tenant security by implementing robust row-level isolation in Laravel. Learn to build tenant-aware Eloquent scopes that prevent cross-tenant leaks.
Querying with Strict Eloquent
Custom Middleware Development
Database Connection Pooling
Handling Large Data Exports
Security Header Configuration
Database Sharding Concepts
Real-time Data Synchronization
Database Deadlock Prevention
Managing Third-Party API Integrations