Property [group] does not exist on this collection instance - php

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

Related

Eloquent nested complex relationship

I have some trouble with chaining relationships. I want to chain three of them, but this is not working properly:
return UserModel::with('cars.pieces.attributes')
I want to retrieve a user with its cars. He chose a car which have pieces and for each pieces he chose an attribute.
With only cars.pieces. I have my user, then the array of cars then the array of pieces for this car. When I add attributes, I have attributes not for pieces of cars of users but attributes for pieces whatever cars it is.
It seems like the relationship is only looking for the previous relation and not the whole packet.
public function cars(){
return $this->belongsToMany(CarsModel::class, 'user_cars', 'id_user','id_cars');
}
Then
public function pieces(){
return $this->belongsToMany(PiecesModel::class, 'cars_pieces', 'id_cars','id_pieces')
}
And finally :
public function attributes(){
return $this->belongsToMany(AttributeModel::class, 'user_cars_pieces_attributes', 'id_attribute', 'id_piece')
}
The last entity is using 4 fields for the primary key :
id_user, id_car, id_attribute, id_piece
What could be a way to retrieve attributes for pieces of cars of the user?
Thank you for helping!
You can pass a function to your eager loading attributes:
return UserModel::with(['cars.pieces.attributes' => function ($query) {
$query->where('id_user', DB::raw('users.id'))->where('id_car', DB::raw('cars.id'));
}]);
I haven't tested this but I think it should work.
Remember to import DB Facade: use Illuminate\Support\Facades\DB;

How to create relationship between 3 models in laravel?

SQL scheme:
bulletins
id increment
deals
id increment
seller_id
buyer_id
deals_items - items = bulletins
id increment
title
desc
bulletin_id
deal_id
How can I get deal row by bulletin id? In raw SQL it looks like:
select `deals`.* from `deals` inner join `deals_items` on `deals_items`.`deal_id` = `deals`.`id` where `deals_items`.`bulletin_id` = 10572
I tried:
public function deals()
{
return $this->hasManyThrough(DealItem::class,Deal::class, 'bulletin_id','dealid','id');
}
But it seems a wrong way. Can't find right way in laravel doc about relation.
#HCK shows right way.
but when I doing $bulletin->deals() in blade template I got empty collection of deals.
When just $bulletin->deal - all is fine, we have collection of deals.
I using protected $with = ['deals'] in my bulletin model, but what is different call method or property? Why with method empty result?
#Amarnasan was close, but the order of the foreign keys was wrong. Try this:
Deal.php
public function bulletins()
{
return $this
->belongsToMany(Bulletin::class, 'deals_items', 'deal_id', 'bulletin_id')
->withPivot('title','desc');
}
Bulletin.php
public function deals()
{
return $this
->belongsToMany(Deal::class, 'deals_items', 'bulletin_id', 'deal_id')
->withPivot('title','desc');
}
From the docs:
As mentioned previously, to determine the table name of the
relationship's joining table, Eloquent will join the two related model
names in alphabetical order. However, you are free to override this
convention. You may do so by passing a second argument to the
belongsToMany method:
return $this->belongsToMany('App\Role', 'role_user');
In addition to customizing the name of the joining table, you may also
customize the column names of the keys on the table by passing
additional arguments to the belongsToMany method. The third argument
is the foreign key name of the model on which you are defining the
relationship, while the fourth argument is the foreign key name of the
model that you are joining to:
return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'role_id');
Update
When you access the relationship as a method: $bulletin->deals() you are accessing the relationship itself. This will return an instance of \Illuminate\Database\Eloquent\Relations\BelongsToMany (in your case). Here the query is not executed yet, so you could keep adding constrains to your query, for example:
$bulletin
->deals()
->where('seller_id', 45) // <---
->skip(5) // <---
-> ... (And so on)
When you access it as a dynamic property, you are already executing the query, so this will return a Collection instance. Is the same as calling the relationship as a method and then attach the ->get() at the end, so this two are equivalent:
$bulletin->deals()->get()
// equals to:
$bulletin->deals
Check this other answer, it answers your question.
DealClass:
public function bulletins()
return $this->belongsToMany('App\Bulletin', 'deals_items ', 'bulletin_id', 'deal_id')->withPivot('title','desc');
}
BulletinClass:
public function deals()
return $this->belongsToMany('App\Deal', 'deals_items ', 'deal_id', 'bulletin_id')->withPivot('title','desc');
}
deals model -
public function bulletins()
return $this->belongsToMany(Bulletin::class, 'deals_items ', 'bulletin_id', 'deal_id');
}
bulletin model:-
public function deals()
{
return $this
->belongsToMany(Deal::class, 'deals_items', 'deal_id', 'bulletin_id',);
}

Make Laravel Accessor return query results

UPDATED:
I'm trying to add my own attribute with subquery results to the results of main query.
Now, I have Many-To-Many relation between three tables: Tournaments, Participants and Users.
Here is the defining of relation in Tournaments model:
public function users() {
return $this->belongsToMany('App\User', 'Participants', 'Id_tourn', 'Id_user')->withPivot('Rating');
}
The structure of tables is:
Users:
-Id
-Name
Participants:
-Id
-Id_user
-Id_tournament
-Final
-Final_place
Tournaments:
-Id
-Name
I need to have extra Winners attribute in my final query result where I'll have info of first three places.
Following the documentation, I've created an accessor and tried different variants:
That just freeze the system. Nothing happenes and in 30 second I get timeout error.
public function getWinnersAttribute() {
return Tournaments::where("Id","=",$this->attributes['Id'])->where("Finals","=",1)->limit(3)->orderBy("Final_place","asc")->get();
}
This returns an error that "finals" column is not fount in Tournaments table, so $this doesn't have relation:
public function getWinnersAttribute()
{
return $this->where("Finals","=",1)->limit(3)->orderBy("final_place","asc")->get();
}
This returns blank white page without anything:
public function getWinnersAttribute()
{
return $this->with('users')->where("Finals","=",1)->limit(3)->orderBy("final_place","asc")->get();
}
This return "Winners" attribute empty:
public function getWinnersAttribute()
{
return $this->with("users")->where("Finals","=",1)->limit(3)->orderBy("final_place","asc");
}
I've created $appends variable to apply the accessor: protected $appends = ['Winners'];
However, I've checked the accessor, it works. If I return just:
public function getWinnersAttribute()
{
return "123";
}
it works fine and I get "123" inside "winners" attribute of main query result.
The main query is:
Tournaments::with(['users'])->get();
The Finals column is in pivot table of Many-To-Many relation.
UPDATED:
When I try to return query to that Model without relation:
public function getWinnersAttribute($value)
{
return $this->where("Finals",'=',2);
}
I get nothing in winners attribute as well. Like the subquery is not executed.
And if I add get() in the end of return:
return $this->where("Finals",'=',2)->get();
I get blank white page.
How can I solve that problem?
Thanks a lot.
If the getWinnersAttribute is on the Tournament model that means you already have a Tournament model you are calling by doing for example Tournament::find(1)->winners In your attribute you are trying too find the model again, and that could make it a forever loop trying to find a new one already having one etc. try using $this instead
public function getWinnersAttribute()
{
return $this->where("finals","=",1)->limit(3)->orderBy("final_place","asc")->get();
}

Custom method chaining with Eloquent ORM?

This is my current query:
$cars = Cars::with('brand')->get();
$cars->map(function($cars){
$cars->fullName = $cars->brand->brand." ".$cars->name;
//other manipulation...
return $cars;
});
I want to manipulate my collection in the model so that I can run something like $cars = Cars::with('brand')->getWithBrand();
How can I do this, so I don't have to write map functions for every time I run the query?
In your particular example, you don't need to use map to modify the Collection at all. You can use an Eloquent accessor to define attributes on a Model that don't exist in the database. In your example, you would define the following method on your Cars model:
public function getFullNameAttribute($value)
{
// make sure brand exists first
if ($this->brand) {
return $this->brand->brand.' '.$this->name;
}
// default if brand doesn't exist
return $this->name;
}
By defining that function on your Model, that function will be called whenever you attempt to use the full_name attribute, as shown in the following code:
$car = Cars::with('brand')->first();
// this will echo the result of the getFullNameAttribute method
echo $car->full_name;
Edit
If you would also like this new attribute to automatically show up in your toArray() or toJson() output, you can add the attribute to the $appends property on your Cars model:
class Cars extends Model
{
protected $appends = ['full_name'];
public function getFullNameAttribute($value)
{
// make sure brand exists first
if ($this->brand) {
return $this->brand->brand.' '.$this->name;
}
// default if brand doesn't exist
return $this->name;
}
}
Be aware, however, that your custom attribute depends on a related object. So, if you do something that accidentally calls toArray(), toJson(), __toString(), etc on a Collection of Cars that has not eager loaded the brand relationship, this will cause the N+1 query issue.
For example:
// Bad: N+1 issue because each printed Car will execute a
// separate query to get its brand to output full_name.
echo Cars::get();
// Good: No N+1 issue because all brands are already loaded.
echo Cars::with('brand')->get();

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']);
}
}

Categories