eager loading multi level categories - laravel

i have a categories table with parent_id to store categories and subcategories. this categories can be multi level which means a category can have subcategories and each subcategory can have subcategories and so on. and it is dynamic so the levels number in not limited. i defined a function in Category model with name as below:
public function childs()
{
return $this->hasMany(Category::class, 'parent_id');
}
Now i want to eager load the categories with subcategories. the code
$cats = Category::with('childs')->get();
works greate but it gets just one level of subcategories and i want to eager load all levels. something like
$cats = Category:with('childs')->with('childs')... ->get();
is there any way to do that? to get all category levels?
if there isn't, how to get all levels of subcategories in one collection without eager loading?

You could use this to load all subcategories
class Category extends Model
{
public function children()
{
return $this->hasMany($this, 'parent_id');
}
public function childrenTree()
{
return $this->children()->with('childrenTree');
}
}

Just call your relation withing your relation. I mean recursively.
Try this code:
class Category extends Model
{
public function subCategory()
{
return $this->hasMany(Category::class, 'parent_id')->with('subCategory');
}
}

Related

Laravel apply where clause to belongsToMany query using with() method

I have a products table with brand_id, and a category_products table with, product_id and category_id.
My Products model :
class Product extends Model
{
public function categories() {
return $this->belongsToMany(Category::class)->withTimestamps();
}
}
My Brands Model :
class Brand extends Model
{
public function products() {
return $this->hasMany(Product::class)->with(['categories']);
}
}
My question is, How can I fetch the products from a Brand instance that belongs to certain category?
$brand = App\Brand::find(1);
/*
I want to be able to do something in the likes of :
*/
$brand->products_which_are_under_this_category
Remove with(['categories']) method from inside products() and write a query like that.
$brand->products()->with(['categories' => function ($query) {
$query->where('category_id', CATEGORY_ID);
}])->get();
hope this can help.

Laravel how to make Eloquent relationship to find all data from child_category table with related category & subcategory name?

I have three table:
categories table fields id, category_name
subcategories table fields id, category_id, subcategory_name
child_categories table fields id, category_id, subcategory_id, child_category_name
I have three model Category, Subcategory,
1) =>category model code
class Category extends model {
public function subcategory(){
return $this->hasMany(Subcategory::class);
}
public function Child_category(){
return $this->hasMany(Child_category::class);
}
}
2) =>Subcategory model code
class Subcategory extends model {
public function Category(){
return $this->belongsTo(Category::class);
}
}
3) =>Child_category model code
class Child_category extends model {
public function Category(){
return $this->belongsTo(Category::class);
}
}
how to make Eloquent relationship to find all data from child_categories table with related category & subcategory name?
Once the relationships are defined you can get them by simply calling the property that has the same name as the relation you need.
$category = Category::first();
$subcategory = $category->subcategory;
If you wanted to get all the categories with all subcategories and child categories in one line you can use the with() method to eagerload them efficiently.
$categories = Category::with(['subcategory', 'Child_category'])->get();
This will fetch all the categories, then fetch all the related subcategories and child categories and associate them appropriately.

Laravel | Using Eloquent hasManyThrough

I have a table called invoiceDetails that has item_id as foreign key from another table called items which has category_id as foreign key from table called categories.
I want to do that following using eloquent:
$result = InvoiceDetail::groupBy('item_id')
->selectRaw('sum(qty) as qty, item_id')->with('item', 'category')->get();
but I am getting error:
Call to undefined relationship [category] on model [App\InvoiceDetail].
Here's my relation inside Category model:
public function invoiceDetail() {
return $this->hasManyThrough('App\InvoiceDetail', 'App\Item', 'category_id', 'item_id');
}
Any suggestions?
Not sure you would even need a hasManyThrough relation here, unless you want to fetch all InvoiceDatail objects belonging to all items which in turn belong to the Category. That part is not clear from your question.
But in your example you are fetching items with their category from distinct item_id.
The reason this is not working is because you are trying to fetch the category relation from the InvoiceDetail object, which does not exist.
->with('item', 'category')
You want to load the Category based on the item relation, not based on the InvoiceDetail, try the dot notation (given that you did define the other relations)
->with('item.category')
Relations should be like this:
class InvoiceDetail extends Model
{
public function item()
{
return $this->belongsTo(\App\Item::class);
}
}
class Item extends Model
{
public function invoiceDetails()
{
return $this->hasMany(\App\InvoiceDetail::class);
}
public function category()
{
return $this->belongsTo(\App\Category::class);
}
}
class Category extends Model
{
public function items()
{
return $this->hasMany(\App\Item::class);
}
public function invoiceDetails()
{
return $this->hasManyThrough(\App\InvoiceDetail::class, \App\Item::class, 'category_id', 'item_id');
}
}
You would want to use the hasManyThrough if, for example, you have a Category and you want to load all the InvoiceDetails directly.
dd($category->invoiceDetails);

Querying has-many-though in Laravel

My Laravel application has three models
Category
Subcategory
Channels
Category - Subcategory (one category has many subcategories)
Subcategory - Channels (many to many relation)
I have the following relations:
class Category extends Model
{
public function subcategories()
{
return $this->hasMany('App\Subcategory');
}
public function channels()
{
return $this->hasManyThrough('App\Channel','App\Subcategory');
}
}
class Subcategory extends Model
{
public function Category()
{
return $this->belongsTo('App\Category');
}
public function channels()
{
return $this->belongsToMany(Channel::class, 'channel_subcategory', 'channel_id', 'subcategory_id')
->withTimestamps();
}
}
class Channel extends Model
{
public function subcategories()
{
return $this->belongsToMany(Subcategory::class, 'channel_subcategory', 'channel_id', 'subcategory_id')->withTimestamps();
}
}
I have 4 tables:
categories
subcategories
channel_subcategory
channels
I want to query all channels inside a category, organized in subcategories. Something like:
Category A
Subcategory A-1
Channel A-1-1
Channel A-1-2
Subcategory A-2
Channel A-2-1
Channel A-2-2
Given the Category.slug I have made the following Query in the Category Controller:
public function show($slug)
{
$category = Category::with('subcategories')->where('categories.slug', '=', $slug)->first();
$channels = $category->channels;
return view('categories.show')->withCategory($category)->withChannels($channels);
}
And I get an error stating:
PDOException in Connection.php line 319:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'channels.subcategory_id' in 'on clause'
(SQL: select `channels`.*, `subcategories`.`category_id`
from `channels`
inner join `subcategories` on `subcategories`.`id` = `channels`.`subcategory_id` where `subcategories`.`category_id` = 1)
Eloquent assumes that there is a one-to-many between channels and subcategories and ignores the pivot table
My questions are:
Is there a solution using Eloquent? Many-throught relations with a pivot table in the middle?
Using plain MySQL, how just got the list of channels ALL togethere, but I have not idea how to separate them in groups of subcategories. Any help?
EDIT TO CLARIFY:
I am trying to query through FOUR tables here:
CATEGORIES, SUBCATEGORIES, CHANNEL_SUBCATEGORY AND CHANNELS
I want to find the channels in a Category grouped by subcategories. (channels and subcategories has a many-to-many relation)
I think that your channels table does'nt have a column name subcategory_id but category_id ?
So that you have to tell laravel how to handle your relationship :
In your Category Model:
public function channels()
{
return $this->hasManyThrough(
'App\Channel','App\Subcategory',
'category_id', 'subcategory_id', 'id'
);
}
Edit after reply :
Ok then the relationship is not a hasManyThrough, but a belongs to :
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_subcategory', 'channel_id','subcategory_id');
}
By default, laravel will search for a category_id foreign_key, but as you rename it subcategory_id you have to specify all the relationship

Using relations on a collection

I have a Collection that holds the root category and all descendants. In my Category model, I have established that there can be many posts in relation to the category. I retrieve the category and it's descendants with this code:
$category = Category::findOrFail($categoryID);
$categoryAndDescendants = $category->getDescendantsAndSelf();
$categoryAndDescendants is a Collection object that holds Category models. Is it possible to retrieve all of the posts at once?
I basically want to do something like:
$posts = $categoryAndDescendants->posts()->orderBy('timestamp', 'DESC');
Which would retrieve all of the posts for all of the categories and their descendants in that particular collection.
Thanks for the help and I apologise for the awful wording.
I think that's not possible.
But you could write a custom Collection, and implements this function. Something like this:
<?php
use Illuminate\Support\Collection;
class CategoryCollection extends Collection
{
public function posts()
{
$posts = new Collection();
foreach ($this->items as $category) {
foreach ($category->posts() as $post) {
$posts->add($post);
}
}
return $posts;
}
}
And then, you just need to set this custom collection to your Category model.
class Category extends Eloquent
{
public function newCollection(array $models = array())
{
return new CategoryCollection($models);
}
}

Resources