Eloquent relationship in custom pivot table - laravel

I might be overcomplicating things, but here's relationship I'm trying to write in Eloquent (Laravel 5):
I have a list of products and price lists. Each product can have many price lists. This is easy to do, but here's another thing - for every product <> price list assignment I can have many prices based on quantity, so it's like:
productID: 1010
priceListId: 1
min quantity: 1 -> price 10.00
min quantity: 5 -> price 9.50
min quantity: 10 -> price 9.00
productID: 1010
priceListId: 2
min quantity: 1 -> price 15.00
min quantity: 40 -> price 14.00
min quantity: 90 -> price 12.00
I think I know how to create custom pivot table although I don't know how to use it. I followed this link now I'm not sure if my code is correct and how to use it.
At the moment I have:
Product model:
class Product extends Model {
public function pricelists()
{
return $this->belongsToMany('PriceList');
}
public function newPivot(Model $parent, array $attributes, $table, $exists)
{
if ($parent instanceof PriceList)
{
return new ProductPricePivot($parent, $attributes, $table, $exists);
}
return parent::newPivot($parent, $attributes, $table, $exists);
}
}
PriceList model:
class PriceList extends Model {
public function products()
{
return $this->belongsToMany('Product');
}
public function newPivot(Model $parent, array $attributes, $table, $exists)
{
if ($parent instanceof Product)
{
return new ProductPricePivot($parent, $attributes, $table, $exists);
}
return parent::newPivot($parent, $attributes, $table, $exists);
}
}
Pivot:
class ProductPricePivot extends Pivot {
public function product()
{
return $this->belongsTo('Product');
}
public function pricelist()
{
return $this->belongsTo('PriceList');
}
public function prices()
{
return $this->hasMany('ProductPrice');
}
}
Now ProductPrice extends Model again and is just a standard model without any additional methods.
Is this correct? If so, then following example from above, how can I add new quantity/price level to price list 1 on product 1010?
At the moment, when creating relationships, I'm doing:
$productID = 1010;
$priceListID = 1;
$priceList = PriceList::find($priceListID);
$priceList->products()->attach($productID);
$productToPriceList = $priceList->products()->find($productID);
And I'm lost here... I find this relationship, but how can I now attach next quantity <> price level to it?
Can someone please give an example of how to use such relationship or links to pages where I can find something more about it? And yes, I check Laravel documentation, and yes, I googled it as well.
Thank you!

As you described the case, I see here two types of relationships:
Many-to-Many relationship between Product & PriceList
Many-to-One relationship between PriceList & ProductPrice (which holds the "min-quantity" and "price") values.
I'm assuming that you don't need the same price condition for many "price-list"s. Every list will have it's price conditions.
Many-to-one relationship doesn't require pivot table.
Follow this link, you'll find that laravel Eloquent system helps you much.
You need 4 tables: products, price_lists, product_prices, products_to_pricelists (the pivot)
the products_to_pricelists table look like that:
*====*============*===============*
| id | product_id | price_list_id |
*====*============*===============*
Now the models:
Products:
class Product extends Eloquent {
protected $table = 'products';
public function price_lists()
{
return $this->belongsToMany('PriceList', 'products_to_pricelists', 'price_list_id');
}
}
Price lists:
class PriceList extends Eloquent {
protected $table = 'price_lists';
public function prices()
{
return $this->hasMany('ProductPrice');
}
}
Prices:
class ProductPrice extends Eloquent {
protected $table = 'product_prices';
}
Now it's easy:
$productID = 1010;
$list_id = 1;
$theProduct = Product::find( $productID );
$theProductPriceLists = $theProduct->price_lists; // Don't use parens
$theList = Product::find( $productID )->price_lists()->where('price_lists.id', $list_id)->first(); // Here you use parens
$theListPrices = $theList->prices; // Again no parens
Laravel just manage for you the pivot-connection!
Hope that's help

Related

How to get a total amount of products in children categories of single parent

I have a Category model in which has children categories which has products... getting the total amount of products along with the children is easy. But what about getting the parent categories total amount which is the sum of all children categories of this single parent?
Here is the App\Category model:
public function products()
{
return $this->hasMany(Product::class, 'category_id');
}
public function parent()
{
return $this->hasOne(Category::class, 'id', 'parent_id');
}
public function children()
{
return $this->hasMany(Category::class, 'parent_id', 'id');
}
public function scopeTotalShirts($query)
{
return $query->where('slug', 'shirts')->first()->children->each->products()->count();
}
A shirt would have child categories such as T-Shirt, Long Sleeve, Graphic, etc.
I want to get the total of those children categories so it all adds up in the Shirts (parent) category.
Is there some elegant way to do this besides querying up the children categories and getting the count?
Thank you!
Based on your answer you can remove the extra queries in the loop, at the least:
$children = $category->children()->withCount('products')->get();
return $children->sum('products_count');
Or you can do the actual query from the inverse direction and remove the need for all of this:
return Product::whereHas('child.category', function ($q) {
$q->where('slug', 'shirts');
})->count();
Though, I don't know how you setup these relationships in the inverse.
Based on the relationships you have you probably need to do more querying here as I would suppose a Product could belong to a parent or child category:
return Product::whereHas('category', function ($q) {
$q->where('slug', 'shirts')
->orWhereHas('parent', fn ($q) => $q->where('slug', 'shirts'));
})->count();
The Product model should have a category relationship (belongsTo). The parent relationship on Category should be a belongsTo not hasOne.
Here is the solution I came up with:
public static function getTotalShirts()
{
$category = Category::where('slug', 'shirts')->first();
$children = $category->children()->get();
$count = 0;
foreach ($children as $child) {
$count += $child->products()->count();
}
return $count;
}
I was wondering if there was some neater way to do it? And is having public static functions safe?

HasMany Relation through BelongsToMany Relation

Is it possible to make Laravel relation through belongsToMany relations?
I have 4 tables:
1)Restaurants (id , name) - uses hasManyRelation with Workers table
2)Directors (id , name)
3)Directors_Restaurants (id, director_id, restaurant_id) - pivot table for connecting belongsToMany Restaurants with Directors
3)Workers (id, name, restaurant_id)
With this function in Directors model i can get all connected restaurants
public function restaurants()
{
return $this->belongsToMany('App\Restaurant','director_restaurant');
}
With this function in my code i can get all workers of all restaurants of one director
$director = Director::find(1);
$director->load('restaurants.workers');
$workers = $director->restaurants->pluck('workers')->collapse();
So my question is : can i declare similar relation in my Director model to get all its workers of all its restaurants?
Of course you can have hasMany relationship method on Director model with Eager Loading
just like below
public function restaurants()
{
return $this->hasMany(Restaurant::class)->with('restaurants.workers');
}
i can suggest a solution like this:
Director Model OPTION 1
public function getAllRestaurants(){
return $this->hasMany(Restaurant::class)->with('restaurants.workers');
}
Director Model OPTION 2
public function getAllRestaurants(){
$this->load('restaurants.workers');
return $this->restaurants->pluck('workers')->collapse();
}
You can get all restaurants anywhere
$all_restaurants = Director::find(1)->getAllRestaurants();
You can define a direct relationship by "skipping" the restaurants table:
class Director extends Model
{
public function workers()
{
return $this->belongsToMany(
Worker::class,
'director_restaurant',
'director_id', 'restaurant_id', null, 'restaurant_id'
);
}
}
You can define an accessor method in your model to hide some of the logic
# App/Director.php
// You'll need this line if you want this attribute to appear when you call toArray() or toJson()
// If not, you can comment it
protected $appends = ['workers'];
public function getWorkersAttribute()
{
return $this->restaurants->pluck('workers')->collapse();
}
# Somewhere else
$director = Director::with('restaurants.workers')->find(1);
$workers = $director->workers;
But ultimately, you still have to load the nested relationship 'restaurants.workers' for it to work.
Given your table attributes you could also define a custom HasMany relationship that looks like this
# App/DirectorRestaurant.php
public function workers()
{
return $this->hasMany(Worker::class, 'restaurant_id', 'restaurant_id');
}
# Somewhere else
$director = Director::find(1);
$workers = DirectorRestaurant::where('director_id', $director->id)->get()->each(function($q) { $q->load('workers'); });
But I don't recommend it because it's not very readable.
Lastly, there's the staudenmeir/eloquent-has-many-deep package where you can define that sort of nested relationship.
https://github.com/staudenmeir/eloquent-has-many-deep

Laravel 5.5: How to get top selling items of a given shop?

My tables are like:
shops
[id]
inventories
[id, shop_id]
orders
[id, shop_id]
order_item
[order_id, inventory_id, quantity]
Models:
//Shop
class Shop extends Model
{
public function inventories()
{
return $this->hasMany(Inventory::class);
}
public function orders()
{
return $this->hasMany(Order::class);
}
}
//Inventory
class Inventory extends Model
{
public function shop()
{
return $this->belongsTo(Shop::class);
}
public function orders()
{
return $this->belongsToMany(Order::class, 'order_items')
->withPivot('quantity');
}
}
//Order
class Order extends Model
{
public function shop()
{
return $this->belongsTo(Shop::class);
}
public function inventories()
{
return $this->belongsToMany(Inventory::class, 'order_items')
->withPivot('quantity');
}
}
Now I want 5 top selling inventories of a given shop, What will be the best possible way to do that?
I'm on Laravel 5.5
select s.id,sum(oi.quantity) as total from munna.shops as s
join munna.inventories as iv on s.id=iv.shop_id
join munna.orders as o on iv.shop_id=o.shop_id
join munna.order_items as oi on o.id=oi.order_id
group by s.id
order by total desc limit 5
First, by looking at your tables on order_item, the order_id and inventory_id will bellong to the same shop for sure? I guess yes because if not you would have 2 different shops with same top order. I dont know why you are doing it like this but it's a bit confusing can't figure out why but I would try this:
public function topOrders()
{
$items = DB::table('shops')
->join('orders', 'shops.id', '=', 'orders.shop_id')
->join('inventories', 'shops.id', '=', 'inventories.shop_id')
->join('order_items', 'orders.id', '=', 'order_items.order_id')
->orderBy('quantity', 'desc')
->take(5)
->get();
return $items;
}
What I wrote should select everything from all 3 rows, if you want to select only the items or whatever you want to select you can specify it adding a select clause
Though this was my own question I found the solution on my own and I want to share the solution with the community. I wanted to solve it using Eloquent because I need the model on the view and didn't want to query the model again.
Inventory::where('shop_id', \Auth::user()->shop_id)
->select(
'inventories.*',
\DB::raw('SUM(order_items.quantity) as quantity')
)
->join('order_items', 'inventories.id', 'order_items.inventory_id')
->groupBy('inventory_id')
->get();
I hope this'll help someone with similar issue. Thanks

How to do multiple aggregations in a "has through" relationship in a single query using Laravel?

I have the following tables:
Venues
id
Offers
id
venue_id
Orders
id
offer_id
quantity
Venues can have many offers, and offers can have many orders. This is all modelled in the appropriate Eloquent models like so:
class Venue extends Model
{
public function offers()
{
return $this->hasMany(Offer::class);
}
}
class Offer extends Model
{
public function orders()
{
return $this->hasMany(Order::class);
}
}
I want to run a single query which allows me to see every venue and the number of orders it has. e.g.
venue_id | number_of_orders
---------------------------
5 | 20
15 | 0
8 | 123
I was easily able to do this using raw SQL like so:
select venues.id, sum(offersGrouped.number_of_orders) as total_number_of_orders
from venues
left outer join (
select offers.id, offers.venue_id, sum(orders.quantity) as number_of_orders
from offers
left outer join orders on offers.id = orders.offer_id
group by offers.id
) offersGrouped on venues.id = offersGrouped.venue_id
group by venues.id;
However, how can I do the same thing using Eloquent?
I am using Laravel 5.1, but I don't mind if answers uses features from newer versions of Laravel if required.
Add a HasManyThrough relationship to your Venue model:
public function orders()
{
return $this->hasManyThrough(Order::class, Offer::class);
}
Then you can do this:
$venues = Venue::with('orders')->get();
foreach($venues as $venue) {
// $venue->orders->sum('quantity')
}
Or an in-database solution:
public function orders()
{
return $this->hasManyThrough(Order::class, Offer::class)
->groupBy('venue_id')->selectRaw('SUM(quantity) sum');
}
$venues = Venue::with('orders')->get();
foreach($venues as $venue) {
if($venue->orders->isNotEmpty()) {
// $venue->orders->first()->sum
} else {
// 0
}
}

How to get top 10 category list in laravel eloquent query

i have three table one is category table and another is product table and one more product_to_category table, and it has only product_id and category_id column.
Now i want to get top 10 categories with maximum number of product, with details of 10 products from each category.
What i wrote is
$result = ProductToCategory::groupBy('category_id')->with(['product',function($q){
$q->take(10);
}])->orderBy('category_id)->take(10);
But this is not working.How to write this query properly
Can anyone please help. ty
Model relations
For Product model
public function category(){
return $this->belongsTo(ProductToCategory::class);
}
For Category model
public function products()
{
return $this->hasMany(ProductToCategory::class);
}
For ProductToCategory model
public function product()
{
return $this->hasMany(Product::class);
}
public function category()
{
return $this->belongsTo(Category::class);
}
The most efficient way would be using a raw SQL query because you can't filter products by using eager loading constraint.
But if you want an Eloquent solution anyway, define the relationships:
In the Product model:
public function categories()
{
return $this->belongsToMany(Category::class, 'product_to_category');
}
And in the Category model:
public function products()
{
return $this->belongsToMany(Product::class, 'product_to_category');
}
Then you'll have two options, both have their pros and cons:
1. This code will execute just 2 queries but will use more memory. You could get top ten categories with their products:
$categories = Category::withCount('products')->latest('products_count')->take(10)->with('products')->get();
And then keep only first ten products:
$categories->transform(function($category) {
$topProducts = $category->products->take(10);
unset($category->products);
$category->products = $topProducts;
return $category;
});
2. This solution will create 12 queries but will save the memory:
$categories = Category::withCount('products')->latest('products_count')->take(10)->get();
$categories->transform(function($category) {
$category->products = Product::whereHas('categories', function($q) use($category) {
$q->where('id', $category->id);
})
->take(10)
->get();
return $category;
});
Here is the DB facade version:
$tenPopularTags = DB::table('product_to_category')
->join('category', 'product_to_category.category_id', '=', 'category.id')
->select(DB::raw('count(product_to_category.category_id) as repetition, question_tag.tag_id'))
->groupBy('product_to_category.category_id')
->orderBy('repetition', 'desc')->take(10)
->get();
However I like #Alexey Mezenin way of doing it. Because that is the cleaner way have customized it a bit:
$tenCategories = Category::withCount('products')->orderBy('questions_count', 'DESC')->take(10)->get();
Have used both in my project blog with post and categories relationship and it works!

Resources