Reverse ordering Laravel eloquent eager loaded collection - laravel

I have a collection of products for a particular order like this ->
$products = $order->products()->with('store.seller')->get();
What I want to get is a $sellersCollection where,
each $seller has $stores
each $store has $products
[$seller - stores - products]
How can it be done efficiently without making another db call.

using eloquent relationship you can access this.use appropriate relationship hasOne() or hasMany() in your model.Ex. Seller->hasOne(store) and store->belongsTo(seller) like that.

Doing with eloquent way you could get the desired results as
$sellers = Seller::with('stores.products')->get();
Above will return you all sellers with their related stores and each store with their related products
class Seller extends Model{
public function stores(){
return $this->hasMany('App\Store', 'seller_id')
}
}
class Store extends Model{
public function products(){
return $this->hasMany('App\Product', 'product_id')
}
}
I believe order and products are related as m:m via pivot table so you could define their relation as belongsToMany
class Product extends Model{
public function orders(){
return $this->belongsToMany('App\Order')
}
}
Edit I want the particular $sellers who are associated with the order just made and with all the info of the products buyer ordered like quantity
$sellers = Seller::with(['stores.products.orders' => function ($query) use ($orderId) {
$query->where('id', '=', $orderId);
}])
->whereHas('stores.products.orders', function ($query) use ($orderId) {
$query->where('id', '=', $orderId);
})
->get();

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.).

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

Laravel - How to get data from a table related with pivot table

I have this model for notifications table:
class Notification extends Model
{
public function users()
{
return $this->belongsToMany(User::class, 'notification_user', 'notification_id', 'user_id');
}
}
And this method in controller for getting data from notifications where id of notifications is related to a pivot table named notification_user:
$myNotifications = DB::table('notification_user')
->join('notifications', 'notifications.id', 'notification_user.notification_id')
->where('notification_user.user_id', $userId)
->where('notification_user.seen', 0)
->get();
the result of $myNotifications is correct but I want to use Model and its relationship instead of DB.
How can I get all records in notifications where each notification related to a specific user which is not seen by the user.
You need to add ->withPivot('seen') to the relationship:
public function users()
{
return $this
->belongsToMany(User::class, 'notification_user', 'notification_id', 'user_id')
->withPivot('seen');
}
Then you can do:
Notification::whereHas('users', function ($q) use ($userId) {
$q->where('id', $userId)->where('seen', 0);
})->get();
To avoid joining users, your other option is whereExists:
Notification::whereExists(function ($q) use ($userId) {
$q
->selectRaw(1)
->table('notification_user')
->whereRaw('notifications.id = notification_user.notification_id')
->where('user_id', $userId)
->where('seen', 0);
})->get();
Should still be more performant, but not much more elegant.
You will have to define the same relation in User model as notifications and then:
$notifications = User::where('id', $user_id)->notifications()->where('seen', 0)->get();
You can use with keyword for eager loading inside your controller.
like if you have any relation defined inside your model, just add a with('modelRelation') before your get() statement in eloquent.
Happy Coding.

getting pivot columns in collection in laravel

how to return collection of eloquent models with pivot column? For example, there are M:N relationship between users and vats. I want to retrieve all users data( with vats and with pivot column (costOfDelivery) , which is in pivot table user_vat).
In my code I have:
$vats = Vat::whereHas('users', function($query) use ($user) {
$query->where('user_id', $user->id);
})->with('country')
->get();
but this return data from vats and country, not from pivot table "user_vat", how to retrieve also costOfDelivery?
To retrieve the data from pivot table, you must use pivot attribute
Something like this
foreach($users->roles as $role){
echo $role->pivot->created_at;
}
To return json, you can use the toJson() method like
$vat->toJson();
<?php
/**
* The model class with the belongsToMany user class relation.
*/
class Vat extends Model
{
public function user()
{
return $this->belongsToMany(\App\User::class)
->withPivot(['cost_of_delivery', /**Specific columns for the pivot.*/]);
}
}
<?php
/*
* Your query (which I think is a little bit complicated that it should be.
*/
$vats = Vat::whereHas('users', function($query) use ($user) {
$query->where('user_id', $user->id);
})->with(['country', 'users'])
->get();
I would use sth. like:
<?php
$user->load(['vats.country']);
$vats = $user->getRelationValue('vats');
And
$vats->first()->pivot->cost_of_delivery
should give you the cost of delivery of the first vat.

Laravel: One to Many to Many, retrieve distinct() values

Laravel 4 Project, using Eloquent ORM.
I have three tables: customers, orders and products (+ 1 pivot table order_product). Customers are linked one-to-many to Orders. Orders are linked many-to-many to Products.
Customers 1-->N Orders N<-->N Products
I would like to have a method on Customer model that retrieves a list of products that customer is buying.
To better understand this, assume products are consumable.
For example Customer #1 can place:
Order #1 for Products A, B and C;
Order #2 for Products A, C and D;
Order #3 for Products C and E;
...and the result I want to retrieve is a Collection with Products A, B, C, D and E.
Models are (pseudo-coded on the fly):
class Product extends Eloquent {
public function orders()
{
return $this->belongsToMany('Order');
}
}
class Orders extends Eloquent {
public function customer()
{
return $this->belongsTo('Customer', 'customer_id');
}
public function products()
{
return $this->belongsToMany('Product');
}
}
class Customers extends Eloquent {
public function orders()
{
return $this->hasMany('Orders', 'customer_id');
}
public function products()
{
// What to put here ???
}
}
Thanks to #deczo's answer, I was able to put up a single query method to retrieve items:
public function items()
{
$query = DB::table('items')->select('items.*')
->join('item_order', 'item_order.component_id', '=', 'items.id')
->leftJoin('orders', 'item_order.order_id', '=', 'orders.id')
->leftJoin('customers', 'customers.id' , '=', 'orders.customer_id')
->where('customers.id', $this->id)
->distinct()
->orderBy('items.id');
$eloquent = new Illuminate\Database\Eloquent\Builder( $query );
$eloquent->setModel( new Item );
return $eloquent->get();
}
This is a Many-to-Many relationship, but with the Orders table as the pivot table.
class Customers extends Eloquent {
public function orders()
{
return $this->hasMany('Orders', 'customer_id');
}
public function products()
{
return $this->belongsToMany('Products', 'orders', 'customer_id', 'product_id');
}
}
I've included the last two parameters, but if you follow the singular_id pattern they can be left out.
It's possible to receive distinct Product models like this:
public function products()
{
return $this->belongsToMany('Products', 'orders', 'customer_id', 'product_id')
->distinct();
}
#deczo's answer probably works fine, and is probably a lot more performant as all the data reduction is done in the database itself, but here's a 'pure Laravel' way that's undoubtedly more readable:
use Illuminate\Database\Eloquent\Collection;
class Customer extends Eloquent
{
...
public function products()
{
$products = new Collection;
foreach ($this->orders as $order) {
$products = $products->merge($order->products);
}
return $products;
}
}
Note that this method will not act like normal relationship methods - to get the resulting collection you call the method (i.e. $products = $customer->products();) and you can't access it as a property like you can with relationships (i.e. you can't do $products = $customer->products;).
Also, I'm kinda going on my understanding of the Illuminate\Database\Eloquent\Collection#merge() method here that it automatically does a DISTINCT-like thing. If not, you'll have to do a $collection->unique() kinda thing.
I can't think of easy relation method for this one, but here's a workaround:
$productsIds = DB::table('customers')
->leftJoin('orders', 'orders.customer_id', '=', 'customers.id')
->join('order_item', 'order_item.order_id', '=', 'orders.id')
->leftJoin('items', 'order_item.item_id' , '=', 'items.id')
->distinct()
->get(['items.id']);
$productsIds = array_fetch($productsIds, 'id');
$productsCollection = Product::whereIn('id', $productsIds);

Resources