Laravel Eloquent : with query parameters
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
Laravel Eloquent: Mastering Dynamic Query Parameters in Eager Loading
I've finally worked out how to put together a complex query to get related models. The beauty of Eloquent lies in its ability to handle intricate database relationships gracefully through eager loading. However, when you start nesting with() clauses and need those constraints to be dynamic—dependent on values from the parent model—you run into a common hurdle: passing variables into nested closures.
This post dives into exactly how to solve that problem efficiently, allowing you to build highly optimized queries without resorting to messy manual joins or subqueries in your application logic.
The Challenge of Dynamic Scoping in Eloquent
Consider the scenario where you are loading a Campaign and need to eager load its related buyers, and then for each buyer, you want to load only the emails associated with that specific campaign.
Your initial attempt looks something like this:
$campaign = Campaign::find($campaign_id);
$buyers = $campaign->buyers()->with('notes')->with(['emails' => function($q){
$q->where('campaign_id', '13'); // Problem: Hardcoded ID!
}])->get();The complexity arises when you try to make the where clause inside the closure dynamic. You want $q->where('campaign_id', $campaign_id), but PHP closures often struggle to access variables from the surrounding scope directly without explicit referencing, especially within complex eager loading structures. Hardcoding the ID ('13') defeats the purpose of dynamic querying and leads to incorrect results whenever you load a different campaign.
The Solution: Capturing Scope with Closures
The solution involves ensuring that the variable $campaign_id is accessible within the scope of the closure passed to the with() method. While direct access can sometimes be tricky depending on how Eloquent resolves the query builder, the most robust pattern is to leverage PHP's scoping rules or structure the initial query differently.
In this specific case, since you are querying relationships on an existing model instance (the $campaign), we need to ensure the context of that parent ID is available when building the nested constraints.
Here is the corrected and idiomatic way to achieve dynamic scoping:
$campaign = Campaign::find($campaign_id);
if ($campaign) {
$buyers = $campaign->buyers()
->with('notes') // Eager load notes for buyers
->with(['emails' => function ($query) use ($campaign_id) {
// Now, the closure has access to the outer scope's $campaign_id
$query->where('campaign_id', $campaign_id);
}])
->get();
} else {
$buyers = collect(); // Handle case where campaign is not found
}Breakdown of the Fix
The key to making this work lies in the use keyword within the closure definition: function ($query) use ($campaign_id).
- Accessing Outer Variables: By using
use ($campaign_id), we explicitly tell the closure that it needs access to the$campaign_idvariable defined outside of it (in the main function scope). - Dynamic Constraint: Inside the closure, we can now safely use this variable:
$query->where('campaign_id', $campaign_id);. This ensures that every time you iterate through the eager load, the subquery foremailsis correctly filtered by the specific ID of the parent campaign being loaded.
This technique demonstrates how powerful Eloquent's relationship loading system is, allowing developers to construct complex, multi-level data retrieval using concise syntax. For more advanced database interaction patterns in Laravel, always refer to the official documentation on laravelcompany.com for the deepest dive into ORM capabilities.
Conclusion
Mastering dynamic query parameters within Eloquent closures is a significant step toward writing flexible and performant data access layers. By understanding how PHP closures handle scope—specifically by using the use keyword to capture parent variables—you can transform