问题
I found a very bizarre behavior of with
function for overloading relationships. I have Product
and Deal
relationships, such that Product
belongsTo()
Deal
(through product_id
in deals
table). Now, when I try to get all products on sale:
Product::with(['deal' => function($query) {
$query->whereDate('ends_at', '>', Carbon::now()->toDateTimeString());
}])->get()
this returns a collection of all products, even though there are no records in deals
table and all products have deal_id
set to NULL
. At the same time Product::has('deal')->get()
returns an empty collection, as you would expect.
I initially discovered this problem while trying to fetch five random products on sale together with Deal
and Image
relationships:
Product::with(['deal' => function ($query) {
$query->whereDate('ends_at', '>', // promo still active
Carbon::now()->toDateTimeString());
},
'images' => function ($query) {
$query->where('featured', true); // image featured on homepage
}])
->where('status', 'IN_STOCK') // 'In Stock'
->whereNull('deleted_at') // wasn't soft-deleted
->orderByRaw('RAND()')
->take(5)->get())
This yields a collection with 5 random Product
s out of all Product
s. I tried with query->whereNotNull('ends_at')->whereDate('ends_at' ..... );
but got same results.
What am I doing wrong here?
回答1:
Your understanding of the concept is completely wrong here.
If you are saying that a Product belongsTo()
Deal, then lets assume that a Deal hasMany()
Products.
This is the deals table
deals
id | name | ends_at | blah | blah
products
id | deal_id | name | blah | blah
So basically, the Product::with('deal')
should return you all products with their Deals being Eager loaded. But Deal::with('products')
will return you an empty collection, since no products have a valid deal_id
in it.
It is important to note that, since Product can only belongTo
a single Deal, you will always get the Deal Model rather than a collection when you perform Product::with('deal')
query. But when you perform Deal::with('products')
you are bound to get a collection.
So basically, when you say
This returns a collection of all products, even though there are no records in deals table and all products have deal_id set to NULL.
It is pretty obvious.... because the query here is being done on Products and not Deal. If you are trying to find the Deal where ends_at > Carbon::now()
, you'll have to do this.
Deal::with('product')->where('ends_at', '>', Carbon::now()->toDateTimeString())
回答2:
When you use with
then it only eager loads the relations on the constraints provided but if you want to filter the parent model by their relations then whereHas
is your friend. So your query should be as:
Product::whereHas('deal' => function($query) {
$query->whereDate('ends_at', '>', Carbon::now()->toDateTimeString());
})->get();
Now it will fetch only those Product
which satisfy the given constraint.
You can also use the combination of with
and whereHas
as:
Product::whereHas('deal' => function($query) {
$query->whereDate('ends_at', '>', Carbon::now()->toDateTimeString());
})
->with(['deal' => function($query) {
$query->whereDate('ends_at', '>', Carbon::now()->toDateTimeString());
}])
->get();
来源:https://stackoverflow.com/questions/41203604/constraining-eager-loaded-relationship