Laravel - Making a HasManyThrough Relationship provide a unique collection - php

I am having an issue getting a hasManyThrough to work:
public function deliveryContainers() : HasManyThrough
{
return $this->hasManyThrough(
DeliveryContainer::class, // Final
StockMovement::class, // Intermediate
'product_id', // Foreign key on Intermediate
'id', // Foreign key on Final
'product_id', // Local key on Current
'location_id' // Local key on Intermediate
)->where('delivery_id', $this->delivery_id);
}
Because the stockMovements table returns multiple results, my resulting delivery containers collection contains duplicate entries. If I could somehow put a group/unique on the intermediate table query then this would be resolved.
I can get a collection with the correct deliveryContainers eager loaded using the following:
public function deliveryContainers()
{
return $this->hasMany(StockMovement::class, 'entity_id', 'delivery_id')
->with('deliveryContainer')
->where('product_id', $this->product_id)
->get()
->unique('location_id');
}
However, to access the deliveryContainer I now have the following:
foreach($this->deliveryContainers() as $row){
$row->deliveryContainer->id;
}
And what I would like to have...
foreach($this->deliveryContainers() as $row){
$row->id;
}
Is there any way to push the eager loaded relationship up a level (if that can be used to describe it), or even better add some kind of unique filter to the hasManyThrough relationship?
Table Structure
delivery_exceptions (where this relationship originates)
product_id
delivery_id
delivery_containers
id
delivery_id
stock_movements
entity_id (linked to delivery id)
product_id
Relationships
A delivery exception belongsTo a product
A product hasMany stock_movements
A stock movement belongsTo a delivery container
A delivery exception hasMany delivery containers... (indirectly, through a combination of the product and the stock movements)

You've got a really tough setup there and I'm not entirely sure that I got the full idea behind it (also because of you using entity_id at some place instead of delivery_id). But nonetheless, I gave it a shot.
The hasManyThrough relationship you defined looks actually not too bad, but in my opinion there is a better way to get to the result. But first let's have a look at your relationships:
3
+-------------------------------------+
4 v |
+-------------> Delivery <----------+ |
| | 1 |
+ + +
DeliveryException +---> Product <---+ StockMovement +---> DeliveryContainer
+ ^
+---------------------------------------------------------+
2
As a StockMovement already belongs to a DeliveryContainer, which in return belongs to a Delivery, the relation from StockMovement to Delivery (marked as 1) seems obsolete to me. Anyway, to get relation 2 on your model, you can use the paths 3 and 4 to your advantage:
class DeliveryException
{
public function deliveryContainers(): HasMany
{
return $this->hasMany(DeliveryContainer::class, 'delivery_id', 'delivery_id');
}
}
Obviously, this will give you all the DeliveryContainers, unfiltered by the Product. Therefore I suggest adding a second function:
public function deliveryContainersByProduct(): HasMany
{
return $this->deliveryContainers()
->whereHas('stockMovements', function ($query) {
$query->where('product_id', $this->product_id);
});
}

The accepted answer is far more elegant, but this is another way to do this too:
public function deliveryContainers1()
{
return $this->hasManyThrough(
DeliveryContainer::class, // Final
StockMovement::class, // Intermediate
'product_id', // Foreign key on Intermediate
'id', // Foreign key on Final
'product_id', // Local key on Current
'location_id' // Local key on Intermediate
)
->where('delivery_id', $this->delivery_id)
->distinct();
}

Related

In Laravel Eloquent how to define relationship through secondary table? (Always returning 0 relations)

I feel like this should work. I have a list of products and categories (types).
Tables:
Products
- id
- name
- etc
Types
- id
- name
- etc
ProductTypes
- product_id
- type_id
Now, I feel like in the Type model in Laravel, I should be able to define this relationship:
public function products()
{
return $this->hasManyThrough(Product::class, ProductType::class, 'type_id', 'id');
}
I've tried other variations with the secondary ids in the additional parameters but no luck, always an empty list. Is ProductTypes a pivot table and therefore should be dealt with differently?
Edit: What's weird is that for the final 2 parameters ($localKey = null, $secondLocalKey = null) even if I enter complete garbage no error is thrown but these 2 parameters $firstKey = null, $secondKey = null have to be correct).
You are using the wrong relationship. Based on your database structure, a product can belong to many type. Therefore, it should be a BelongsToMany instead of a HasManyThrough.
You can achieve what you want with the following method, by passing the table name of your ProductTypes as the second parameter:
public function products()
{
return $this->belongsToMany(Product::class, 'product_types');
}
If your ProductType model extends Illuminate\Database\Eloquent\Relations\Pivot, you can do:
public function products()
{
return $this->belongsToMany(Product::class, 'product_types')
->using(ProductType::class);
}
For more information about Many to Many relationships: https://laravel.com/docs/6.x/eloquent-relationships#many-to-many

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

hasManyThrough - Not returning the right data

I have an intermediate table titled
app_services
It consists of the following columns :
app_service_id (PK)
app_id (FK)
service_id (FK)
The final table is the services table :
Services
service_id (PK)
service_name
service_icon
I am trying a hasManyThrough on app_services. But it's returning the wrong results..
For app_id = 1 it has the service id's of 1 & 3. But it is returning back the service_id's pf 1 & 4.
In my app_model I am defining the relationship as follows :
public function services()
{
return $this->hasManyThrough(
'App\Service', 'App\AppService',
'service_id', 'service_id', 'app_id'
);
}
Now that looks right to me? Would someone mine checking if it's correct or not.
Thank You!
public function services()
{
return $this->belongsToMany('App\Service');
}
Now, with such declaration of relationships Laravel “assumes” that pivot table name obeys the rules and is app_service.But, if it’s actually different (for example, it’s plural), you can provide it as a second parameter:
public function services()
{
return $this->belongsToMany('App\Service','app_services');
}
'app_services' is an intermediate table name
Moreover, you can specify the actual field names of that pivot table, if they are different than default app_id and service_id. Then just add two more parameters – first, the current model field, and then the field of the model being joined
public function services()
{
return $this->belongsToMany('App\Service','app_services','app_id','service_id');
}
Now,using this relationship you can retrieved data
$app = App::find($app_id);
$app->services();

Laravel Relaton Return null

I have two table
one is products another is offers
products
---------
id | offer_id
offers
---------
id | product_id
Now I want to get all offers against a product
In my product model I wrote
public function getOfferDetails()
{
return $this->belongsTo('App\Offer');
}
but it return null.
You want to get all offers that belong to a product, right? Have you tried this:
public function getOfferDetails()
{
return $this->hasMany('App\Offer');
}
In your Product model?
Need to define how your relationships are. Based on what you have
A Product can have 0 - many offers and an Offer belongs to 1 Product.
You will need some foreign key to match the models. OTB Laravel will attempt to use the method name appended with _id as the foreign key. Since your method name is different, you need to pass the foreign key as the second parameter in your relationship method.
Products Model should have
public function getOfferDetails()
{
return $this->hasMany('App\Offer', 'product_id');
}
Offer Model should have
public function getProduct()
{
return $this->belongsTo('App\Product', 'product_id');
}
Laravel Documentation that might help out as well.
This is what you're looking for I think:
public function getOfferDetails()
{
return $this->hasMany('App\Offer', 'id', 'offer_id');
// as ceejayoz mentioned in comments, Laravel should be able to detect this themselves. The below would work the exact same as above
// return $this->hasMany('App\Offer', 'id', 'offer_id')
}
$product->getOfferDetails()->get();

Get specified record from belongsToMany relation Laravel

If I have properties table, and 2 other tables:
*property_characteristics
- property_id (i.e. 1)
- characteristic_id (i.e. 5 - join with default_characteristics)
- value (i.e. 3 - aka 3 rooms)
*default_characteristics
- id (i.e. 5)
- name (i.e. rooms)
In the Property.php model I have:
public function characteristics()
{
return $this->belongsToMany('Proactiv\DefaultCharacteristic', 'property_characteristics', 'property_id', 'characteristic_id');
}
How can I get the number of rooms (value from property_characteristics) for a property starting from:
$property = Properties::find(1);
I would need something like this in view:
$property->characteristics->rooms // should return 3 which is the value columns on property_characteristics table
Since the value is on your pivot table, you need to tell Laravel about this extra field. Add to your belongsToMany line to make:
return $this->belongsToMany('Proactiv\DefaultCharacteristic', 'property_characteristics', 'property_id', 'characteristic_id')
->withPivot('value');
Then select characteristics with the name you want, rooms, and get the value:
echo $property->characteristics()->with('name', 'rooms')->first()->pivot->value;
Alternatively, add a getter to your Property model which does this for you (you'll still need to add that withPivot part to the relationship):
public function getRoomsAttribute()
{
return $this->characteristics()
->where('name', 'rooms')
->first()
->pivot
->value;
}
Then you can get the number of rooms in a similar way to how you originally wanted to, with $property->rooms.
Or you could generalize this to get any characteristic:
public function getCharacteristic($name)
{
return $this->characteristics()
->where('name', $name)
->first()
->pivot
->value;
}
And then get the number of rooms with $property->getCharacteristic('rooms').
First, you have to tell your relationship to make your additional field available. You do this using the withPivot() method:
public function characteristics() {
return $this->belongsToMany('Proactiv\DefaultCharacteristic', 'property_characteristics', 'property_id', 'characteristic_id')
->withPivot('value');
}
Now you can access your value on the pivot table. You do this like so:
$property = Properties::find(1);
foreach ($property->characteristics as $characteristic) {
echo $characteristic->pivot->value;
}
You can read more about this in the documentation here, under the Retrieving Intermediate Table Columns heading.

Categories