Has many through many-to-many
There is a Laravel 5.5 composer package that can perform multi-level relationships (deep)
Package: https://github.com/staudenmeir/eloquent-has-many-deep
Example:
User
→ belongs to many → Role
→ belongs to many → Permission
class User extends Model
{
use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
public function permissions()
{
return $this->hasManyDeep(
'App\Permission',
['role_user', 'App\Role', 'permission_role'], // Pivot tables/models starting from the Parent, which is the User
);
}
}
Example if foreign keys need to be defined:
https://github.com/staudenmeir/eloquent-has-many-deep/issues/7#issuecomment-431477943
If you want to have a custom relation, you can create your own extends to Relation abstract class. For example: BelongsToManyThought
.
But if you don't want to implement a Relation, I think that it can fulfill your needs :
In App\Deal.php, you can combine the solution of @thomas-van-der-veen
public function metrics()
{
return Metric
::join('metric_product', 'metric.id', '=', 'metric_product.metric_id')
->join('products', 'metric_product.product_id', '=', 'products.id')
->join('deal_product', 'products.id', '=', 'deal_product.product_id')
->join('deals', 'deal_product.deal_id', '=', 'deal.id')
->where('deal.id', $this->id);
}
// you can access to $deal->metrics and use eager loading
public function getMetricsAttribute()
{
if (!$this->relationLoaded('products') || !$this->products->first()->relationLoaded('metrics')) {
$this->load('products.metrics');
}
return collect($this->products->lists('metrics'))->collapse()->unique();
}
You can refer to this post to see how you can simply use nested relations.
This solution can do the trick for querying relation with method and access to metrics attribute.
I don't have enough rep to post a comment so I'll post an answer instead.
A few days ago I posted a similar question and was able to answer it myself. I think the solution also works for this question.
By joining the tables manually and add a where clause you are able to retrieve the desired Metrics.
Possible solution:
// Deal.php
public function metrics()
{
return Metric
::join('metric_product', 'metric.id', '=', 'metric_product.metric_id')
->join('products', 'metric_product.product_id', '=', 'products.id')
->join('deal_product', 'products.id', '=', 'deal_product.product_id')
->join('deals', 'deal_product.deal_id', '=', 'deal.id')
->where('deal.id', $this->id);
}
// How to retrieve metrics
$deal->metrics()->get();
// You can still use other functions like (what I needed) pagination
$deal->metrics()->paginate(24);
// Or add more where clauses
$deal->metrics()->where(blablabla)->get();
Hope this helps :)
Edit after @ntzm comment
If you only need some properties of the metrics table and none of the Model functionalities you can add a select('*') before the joins.
Like this:
return Metric
::select('*')
->join(...........
It will return a Metric model with all the attributes of the joined tables.
This example will return an object like:
Metric (
....
attributes: [
id => 0, // From metrics table
name => 'somename', // Metrics table
metric_id => 0, // metric_product table
product_id => 0, // metric_product table
....
]
....
)
Since there are a lot of columns with the same name you won't be able to access these values. However you can multiple selects and rename the columns in your results.
Like this:
retrun Metric
::select([
*,
DB::raw('products.name as product_name'),
....
])
->join(....
This will result in something like:
Metric (
....
attributes: [
id => 0, // From metrics table
name => 'somename', // Metrics table
metric_id => 0, // metric_product table
product_id => 0, // metric_product table
product_name => 'somename' // products table
....
]
....
)
I hope this will solve something :) Of course there are much cleaner ways to solve your problem.