YII2 how do i handle many to many relationships? - php

I am new to YII2 and I have a problem with my relationships:
I have Users and Category. They have a m-m relationship. Now i would like to see the Categories a user has. For that I made a table named "user_category" which looks as follows:
In my models i have the following code as suggested in
How do I work with many-to-many relations in Yii2 :
public function getUsers(){
return $this->hasMany(TabUser::className(), ['intUserID' => 'intUserID'])
->viaTable('user_category', ['intCategoryID' => 'intCategoryID']);
}
public function getCategories(){
return $this->hasMany(TabCategory::className(), ['intCategoryID' => 'intCategoryID'])
->viaTable('user_category', ['intUserID' => 'intUserID']);
}
Then i linked them together:
if($user->validate()) {
$user->link('categories', $category);
}
var_dump($user->getCategories());
But this does not return my categories it returns the following:
Does anybody know what I do wrong?
Thanks for your time and help!!

Try to divide your expression like this, should work:
$categories = $user->categories;
var_dump($categories);

Method getCategories() returns ActiveQuery object instead of models. If you need to get an array of category models you must use magical property categories. For example:
var_dump($user->categories);

Related

Get model which has all specific related models

I have no idea how to solve my problem.
I have Model1 with relation:
class Model1 extends BaseModel
{
public function details(): BelongsToMany
{
return $this->belongsToMany(
Detail::class,
'model1_details',
'model1_id',
'detail_id',
);
}
}
model1_details is my pivot table.
Also I have array of details ids like [1, 2, 3].
I need to fetch Model1 that belongs to ALL Detail with given ids.
I need to do this inside my filter.
That's what I have inside my controller:
$builder = Model1::filter(new ModelListFilter($request));
and inside ModelListFilter:
protected function filter(): Builder
{
$request = $this->request;
$request->whenFilled('details', function ($query) {
//
});
return $this->builder;
}
I've tried:
$request->whenFilled('details', function ($query) {
foreach($query as $detailId) {
$this->builder->whereHas('details', function (Builder $innerQuery) use ($detailId) {
$innerQuery->where('detail_id', $detailId);
});
}
});
But it returns all models Model1 even without any details.
UPD
So the problem wasn't there =) 'details' just wasn't filled in my Request.
also my $query was a string, not array, so I called json_decode on it.
Code above retrieves Models belonging to detail with id=1 AND to detail with id=2 and so on.
But I think there might be better solution so I'll leave this question open
UPD 2
also changed this
$innerQuery->where('detail_id', $detailId);
to this
$innerQuery->where('id', $detailId);
so here is needed to pass columns we have in 'details' table, not columns from pivot table
I can't see where you load the relationship with details. Not sure if you're missing a file, but in case you're not, load the relationship in the first part of the query with the eager loading with method:
$builder = Model1::with('details')->filter(new ModelListFilter($request));
You are almost there but you still missing two things,
WhereIn list
to get model whereHas their details ids in a list you need to use WhereIn instead of looping the query
Eager load the details relationship
to get the details list with each model you have to use the with keyword to eager load the relationship
Solution
// builder
$builder = Model1::with('details')->filter(new ModelListFilter($request));
// filter
$request->whenFilled('details', function (array $details) {
$this->builder->whereHas('details', function (Builder
$innerQuery) use ($details) {
$innerQuery->whereIn('detail_id', $details);
}
});
Found solution to my problem here. So it seems like there is no better solution(

Property [group] does not exist on this collection instance

I have retrieved an instance of a product item at blade, so when I do dd($product), I get this result:
And the Product Model is connected to GroupProduct Model like this:
public function groupproducts()
{
return $this->hasMany(GroupProduct::class,'product_id','id');
}
So at group_product table, the product has a custom group_id like this:
So when I do dd($product->groupproducts); I get this result properly:
Then at the Model GroupProduct, I've added these two relationships:
public function product()
{
return $this->belongsTo(Product::class,'product_id','id');
}
public function group()
{
return $this->belongsTo(Group::class,'group_id','id');
}
Now I need to access the name of the group that comes with the retrieved product_id, but when I try this:
dd($product->groupproducts->group->name);
I get this error:
Property [group] does not exist on this collection instance.
However the relationships seems to be applied and it should be showing the name of group...
So what's going wrong here? How can I fix this issue?
UPDATE #1:
Controller:
public function addAttribute(Product $product)
{
return view('admin.products.addAttribute', compact('product'));
}
$product->groupproducts is a collection(array) of products.
You can do:
dd($product->groupproducts->first()->group->name);
This will get first element from collection which is instance of Group class.
Your relation from your product model to the group products model is a one to many. This means, that even if there's only one result, you will be receiving a collection.
You can see in your own screenshot that the return value of $product->groupproducts is an instance of a collection with 1 item.
If you only ever want the first, you should change your relation to a one to one.
However, if you just want the first in this particular case, you should call for the first on the query instance, not on the collection instance.
So $product->groupproducts()->first()->group->name. This way you don't load x amounts, but instead only 1, from your database, which is much faster.
you have to use nested relationship:
1- you can use it in your Model via:
public function groupproducts()
{
return $this->hasMany(GroupProduct::class,'product_id','id')->with('group');;
}
2- you can use it in your controller:
Products::with('groupproducts.group')->get()
REFRENCE

yii gridview filter with external api and one to many

I am struggling with gridview filter. In my code I have two getters.
public function getPhone()
{
return UserPhone::find()->where(['user_id' => $this->id])->orderBy('updated_at DESC')->one();
}
public function getDevice()
{
return ExternalAPiHelper::getDeviceInfo($this->id); // this will make a call to external db, and fetching result from there.
}
I am trying to do a filter for $user->phone->country and $user->device->type I am not sure how I can get the results from both of these in a clean manner. Currently, I am fetching the id from the above 2 and then using the result array in $query->where(['in', 'id', $ids]);
Is there any better way to do this?
use hasMany or hasOne method for relation as per requirement
public function getPhone(){
return $this->hasOne(UserPhone::className(),['user_id' => 'id'])
->orderBy('updated_at DESC');
}
than add this relation in main query where you want to apply filter
$query = ....Model query where you define above relations
$query->joinWith('phone')
$query->andWhere(['LIKE','country',$countryName])
explore more about yii2 relations

Yii2. Models related

I have 3 models: Items, Serials and SerialsCategories. When I show Item form (to create or update) I need to show the serials which belongs to a categoryId selected in a previous step. A serial can belong to more than one category.
Right now I have on my Item model:
public function getSerialsTypeByCategory() {
return (new SerialType)->getByCategory($this->itemCategoryId);
}
On my SerialType model:
public function getByCategory($itemCategoryId) {
return SerialTypeItemCategory::find()->select(['serialTypeId'])->where(['itemCategoryId' => $itemCategoryId])->all();
}
This is working, it does what I need but ... Is this the proper way? is there a better way?
it's not wrong what you are doing. but there is something more -
check this link:
Working with Relational Data
if you use ->hasOne and ->hasMany to define relations, your model gains some extra benefits, like joining with lazy or eager loading:
Item::findOne($id)->with(['categories'])->all();
with a relation, you can also use ->link and ->unlink, to add/delete related data without having to think about linked fields.
Further, it is easy to define relations via junction table:
class Order extends ActiveRecord
{
public function getItems()
{
return $this->hasMany(Item::className(), ['id' => 'item_id'])
->viaTable('order_item', ['order_id' => 'id']);
}
}

Laravel simplify a database query with relations

I'm just started to work with Laravel and think its a pretty good framework.
But there is a lot to learn and i can mostly find everything in the user guide except this part:
I'm trying to get items from my database they are sorted with a category id that relates to a other table item_catagories in this table are stored:
id
name
parent
In my url of the website I use the name of the category instead of the id.
http://example.com/catagory/subcatagory
when subcatagory has a value I want to search for the related items.
I now have it like this:
if($subcategory){
$foo = ItemCategories::where(['group' => $category, 'name'=> $subcategory])
->get()[0]->id;
$data['products'] = Items::where('category_id', $foo)->get();
}
but there must be a much simpler way to get the same results.
I hope someone can help me to understand how I can do it better
Edit
I forgot to add the relation code:
The item class:
public function categorie(){
return $this->hasOne('App\ItemCategories');
}
The categorie class:
public function items(){
return $this->belongsTo('App\Items');
}
You can use Laravel Eloquent's relationships for this. On your category model:
public function items() { return $this->hasMany('Items'); }
Once you've done that, on a category, you can do $category->items to fetch all of its related items.
Incidentally, when you do this:
->get()[0]
you can just do this:
->first()
If you wish to bring your results already populated, one way to do this is:
$foo = ItemCategories::where(['group' => $category, 'name'=> $subcategory])->with('items')->first();

Categories