Constraining eager loaded relationship

自作多情 提交于 2019-12-25 08:15:10

问题


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 Products out of all Products. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!