Laravel collection get occurrences of a MtoM relation - php

this is my situation:
I have a collection of products and i want all the categories of those products.
Between Product and Category there is a many-to-many relation already created, and i want to do something like this:
$prods = Product::where(something)->get();
$categories = $prods->categories();
But obviously this doesn't work, and i would avoid getting all categories for each products, and add it to a collection only if is not alredy in it... something like this:
select *
from categories join pivot on categories.id = pivot.id
where pivot.product_id IN (1,2,3,4,5)
where 1,2,3,4,5 are the ids of the products
Is there any way to do it without QueryBuilder (using Eloquent)?

You should define a many-to-many relationship in your Product model
public function categories()
{
return $this->belongsToMany(Category::class);
}
And then you can do this
$ids = [1,2,3,4,5];
$prods = Product::with('categories')->findOrFail($ids);
$categories = $prods->flatMap->categories;
The with call eager loads your categories, and then you retrieve them easily + flatten (to avoid multi-dimensional collection) thanks to flatMap method.

Related

Laravel Relationship Query to load hasMany + BelongsToMany

I need to get brands data in specific collection page but only has more than 1 product.
Here are relations between models.
Brand -> HasMany -> Product
Product <= BelongsToMany => Collection
I was able to get brands data that have more than 1 products for all collections as following:
$brands = Brand::has("products")->get(); //this will return all brands that have more than 1 product.
Now I need to add collection limitation here.
I can get collection from $slug for specific page.
$collection = Collection::where("slug", $slug)->first();
Can anyone please help me how to get brands for specific collection page?
Try this:
$brands = Brand::has("products")
->whereHas('products.collections',function($q) use ($slug){ // your relation to product model
$q->where("slug", $slug);
})
->get();

Laravel gather data by relational id

In my application, I have a posts table, categories tables and category_post table as a post and category have a many to many relationship.
I want to get all posts with their attached categories, but also group them together as on my frontend I want to be a loop over an object and show a list like this,
Cooking
Post Number 1
Post Number 3
Post Number 4
Music
Post Number 2
Post Number 5
Languages
Post Number 6
Post Number 7
I know in my controller I can do this,
$posts = Post::with('categories')->get();
But I don't how to groupBy a relational attribute or if I can structure the returned data in such a way that I can loop over it to form the list below.
you can get the data from db and then group them the way you want:
$posts = Post::with('categories')->get();
$result = $posts->groupBy([
'categories',
function ($item) {
return $item['category_name'];
},
], $preserveKeys = true);
more details about grouping by relation fields in:
https://laravel.com/docs/7.x/collections#method-groupby
As per given your table: posts and categories tables are the main tables and category_post table is a pivot table which contains the relation between posts and categories table :
So, Your relation should be posts->category_post->categories
$posts = Posts::with('category_post')->get();
$grouped = $posts->groupBy('category_post.category_id');
The category_post also related to the categories table for categories data.
In category model you cam setup and belongsToMany relation like the following
function posts()
{
return $this->beongsToMany(Post::class, 'category_post', 'category_id', 'post_id');
}
Then do as Arun A S suggested.

How to Join Eloquent and Query Builder in one query statement in laravel

I have 2 queries that needed to join 1st is eloquent and 2nd is query builder,
1st Query
$products = Product::all();
2nd Query
$inventory = DB::table('product_warehouse')
->where('product_id', $product_id)
->where('warehouse_id', $warehouse_id)
->first();
How to merge this 2 queries into elouquent way ?
From your usage of the query builder it seems like you have an intermediate table to store which product to which warehouse exist, but if it is a one to many relationship you should not have that table, instead in your products table you should have a warehouse_id which will reference the id on the warehouses table, as you said the relationship is one to many, not many to many.
So in your Warehouse model you can add:
public function products()
{
return $this->hasMany(Product::class);
}
And in your Product model:
public function warehouse()
{
return $this->belongsTo(Warehouse::class);
}
Based on your table name, you might need to set the $table in your warehouse model to match that:
protected $table = 'product_warehouse';
Then you have many ways to fetch it, one of which is :
Warehouse::find($warehouse_id)->products;
// or
Warehouse::with('products')->where('id', $warehouse_id)->get();
// to get the warehouse to which the product belongs to
Product::find($product_id)->warehouse;

Inner join single column using Eloquent

I am trying to get a single column of an inner joined model.
$items = Item::with('brand')->get();
This gives me the whole brand object as well, but I only want brand.brand_name
$items = Item::with('brand.brand_name')->get();
DidnĀ“t work for me.
How can I achieve this?
This will get related models (another query) with just the column you want (an id, see below):
$items = Item::with(['brand' => function ($q) {
$q->select('id','brand_name'); // id is required always to match relations
// it it was hasMany/hasOne also parent_id would be required
}])->get();
// return collection of Item models and related Brand models.
// You can call $item->brand->brand_name on each model
On the other hand you can simply join what you need:
$items = Item::join('brands', 'brands.id', '=', 'items.brand_id')
->get(['items.*','brands.brand_name']);
// returns collection of Item models, each having $item->brand_name property added.
I'm guessing Item belongsTo Brand, table names are items and brands. If not, edit those values accordingly.
Try this:
$items = Item::with(array('brand'=>function($query){
$query->select('name');
}))->get();

Laravel 4 Eloquent many-to-many condition parameter

For a Laravel application I'm working on, I would like to do something like this with my Eloquent models:
$product = Product::with('terms')->find(1);
$brand = $product->terms('brand');
$color = $product->terms('color');
The terms is a many to many relationship. Terms in this context, is terms of taxonomies. So a products terms could be: Nike, Red, Boys, etc.
And if I do $product->terms I get all terms, and when I do $product->terms('brand') I get 'Nike' for instance.
Right now my Product model is like this:
class Product extends Eloquent {
protected $guarded = array();
public static $rules = array();
public function terms($taxonomy)
{
return $this->belongsToMany('Term', 'productterms');
}
}
Is it even possible to do what I'm trying to achieve?
Why not just use a one-to-many relationship? Have a table of colours and a table of brands and then products can have belong to a colour and brand.
If you must have many-to-many (such that a product can have multiple brands or colours) then you can have a table for colours and brands again and then use joining tables for the relationship.
This is all described in the Laravel 4 documentation.

Categories