Get indirect relationship from collection - laravel

I have:
User->hasMany('Order')
...
Order->hasMany('Product')
...
$users=User::with('orders','orders.product')->...
how can I retrieve all products bought from users in $users taking advantage of eager loading instead of doing other queries?

You could use a hasManyThrough relationship on your User model.
https://laravel.com/docs/5.4/eloquent-relationships#has-many-through
public function products()
{
return $this->hasManyThrough('App\Product', 'App\Order');
}
or, using a collection, as your already eager loading.
$products = User::with('orders','orders.product')->flatMap(function(User $user) {
return $user->orders->flatMap(function(Order $order) {
return $order->products;
});
});

Related

Laravel how to get only relation data

this is my User.php relation code
public function activities()
{
return $this->hasMany(Activities::class, 'builder');
}
and this is my query to get only relation data
return User::whereHas('activities', fn($query) => $query->where('user_id', 1))
->paginate();
but it returns only user data without appling any relation, and its pagination not get to use pluck
i have also tried this
User::where('username', request()->username)->has('activities')->paginate();
but i need only get relation data not user with relation, and i prefer do it with whereHas
You need to create reverse relation for Activities model:
class Activities extends Model
{
// ...
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
}
And get all the activities using Activities builder:
$paginated = Acitivities::query()
->whereHas('user', static function(Builder $userQuery) {
$userQuery->where('name', request()->username); // Think about security!
})
->paginate();
Note: All the models must be named in singular (e.g. Activity, User, Product and etc.).

orderBy on hasMany relationship laravel

I made a "hasMany" relationship from Category model to Product model using ProductCatRel model.
I am trying to ordering my products form Category model. The "where" condition is fine, But "orderBy" is not working. Here is my code:
public function Products(){
return $this->hasMany(ProductCatRel::class,'category')
->with('Product')
->whereHas('Product', function($q){
$q->where('status', 1)->orderBy('position');
});
}
Use the following snippet may works
public function products(){
return $this->hasMany(ProductCatRel::class,'category')
->with('Product')
->whereHas('Product', function($q){
$q->where('status', 1)
});
}
$products = App\Category::find(1)->products()->orderBy('position')->get();
whereHas() only check existence and don't affect on retrieved relation data.
You should apply orderBy() in with() method. Also you need to duplicate status checking in with() method.
public function Products(){
return $this
->hasMany(ProductCatRel::class, 'category')
->with(['Product' => function ($q) {
$q->where('status', 1)->orderBy('position');
}])
->whereHas('Product', function($q) {
$q->where('status', 1);
});
}

Get all pages under a root category ID in belongsToMany relationship

Here is a challenge I'm trying to solve for the last few hours.
I have a Page model and a Category model.
a page can be under many categories and each category can have multiple pages so they have a many-to-many relationship between them.
On top of that, each category has a parent category so we have a one-to-many between each category.
So, with all that in mind, here are the models:
Page Model
// table ['id', 'title', 'content', 'created_at', ...]
class Page extends Model
{
public function categories()
{
return $this->belongsToMany(Category::class);
}
}
Category Model
// table ['id', 'category_id', 'name', 'created_at', ...]
class Category extends Model
{
public function pages()
{
return $this->belongsToMany(Page::class)
}
public function categories()
{
return $this->hasMany(Category::class);
}
public function parent()
{
return $this->belongsTo(Category::class, 'category_id');
}
}
I do have a category_page table with category_id and page_id to link that relationship.
Now comes the tricky part.
I'm trying to create an Eloquent query that given a category_id I'd be able to get all pages under this category and all the pages under this category's children categories.
I can probably achieve that with multiple joins in a nested SQL query or maybe a simple foreach loop to recursively get children pages (but that would cost plenty of unnecessary time).
Any ideas on a nice elegant way of doing that?
Thanks in advance.
Edit:
There is a way in Laravel to do a recursive relationship and have all the sub-categories with pages in a single line of code.
// in Category model
public function childrenCategories()
{
return $this->hasMany(Category::class)->with('childrenCategories');
}
Now I can do
Category::whereId($rootCategoryId)->with('childrenCategories')->first();
And this way I'll have a nested tree of all categories, then I can load the pages relationship to it and I'm done.
But, there is a huge problem with it, the amount of queries it performs is crazy (a query for each nested level) and doesn't make sense to have it in a production application.
So I do have a solution, it is terrible, so I'm open to other suggestions.
If you are working with id, you could use nested whereHas():
$pages = Page
::whereHas('categories', function ($query) use ($categoryId) {
$query
->where('categories.id', $categoryId)
->orWhereHas('parent', function ($query) use ($categoryId) {
$query->where('id', $categoryId);
});
})
->get();
If you are working with model you have 2 options:
1) Nested whereHas() method:
$pages = Page
::whereHas('categories', function ($query) use ($category) {
$query
->where('id', $category->id)
->orWhereHas('parent', function ($query) use ($category) {
$query->where('id', $category->id);
});
})
->get();
2) Collect current category id and her children ids, then use one whereHas() method.
$categoryIds = $category->categories->pluck('id')->push($category->id);
$pages = Page
::whereHas('categories', function ($query) use ($categoryIds) {
$query->whereIn('categories.id', $categoryIds);
})
->get();
More: https://laravel.com/docs/6.x/eloquent-relationships#querying-relationship-existence

Weird behaviour of Eloquent One To One relationship

I need to get User with related Profile, but In case below the profile field is null:
Route::get('/user', function (Request $request) {
return $request->user()->load('profile'); // { id: 1, ... profile: null }
});
But in this case the profile field is filled:
Route::get('/user', function (Request $request) {
$user = $request->user();
$profile = $user->profile;
return $user; // { id: 1, ... profile: { name: 'alex', ... } }
});
How can you explain this behavior and what is the correct way to load Profile in my case?
Relations:
public function user(){
return $this->belongsTo('App\Models\User');
}
public function profile(){
return $this->role == 'model' ? $this->hasOne('App\Models\Model\Profile') : $this->hasOne('App\Models\Client\Profile');
}
load() is Lazy Eager Loading
Using load(), you nedd to run the initial query first, and then eager load the relation at some later point. This is "Lazy" eager loading. lazy eager load a relationship after the parent model has already been retrieved.
laravel with() method versus load() method
Go through this for a clear view Eager loading
So to get relationship using with() it will run both queries at same time and will have relation attached to model collection but while using load() first we get the model then on some condition we use load to get relational data. e.g. :
$users = User::all(); //runs first query
if($condition) {
$users = $users->load('organisations'); //lets assume organisations is relation here.
//here runs the 2nd query
}
Hope this helps.

How to call scope in model?

I have the following method in model:
public function announcements()
{
return $this->categories()->with("announcements");
}
And in the same model:
public function scopeActive($query)
{
return $query->where('votes', '>', 100);
}
Hot to call this local scope in model for:
return $this->categories()->with("announcements")->active(); ?
I assume the categories is an relation on this model and the announcements is a relation on the categories model.
Then you can try:
return $this->active()->categories->with("announcements")
$this->active() will return the active records of this model.
->categories will get the related categories
->with("announcements") will eager load the announcements of all the categories.
This will return an eloquent query builder instance.

Resources