Convert Eloquent HasMany relationship to a HasOne?
Asked Answered
L

2

5

Ok, i have model A that is linked to many model B, so model A have a hasMany relationship to model B and model B has a belongsTo relationship to model A.

But, amongst all those B there is a particular one, e.g. the one with the higher value, that i want model A to have a dedicated relationship with.

I know i can create a second relationship in model A that re-use the old one and add all the scopes and condition i want, but since i'm building this new relationship from a hasMany, it will return always, no matter what, a collection of results.

Is there a way to have it return a single result instead than a collection? I know i can add a first(), but this involve in using this new relationship as a function, i.e. always with parenthesis, and if possible i would like to still use it the eloquent way.

Is it possible to do that? if yes, how?

Lawabiding answered 5/9, 2019 at 13:48 Comment(0)
G
12

How about this:

public function Bs()
{
    return $this->hasMany(B::class);
}

public function B()
{
    $builder = $this->Bs()->latest(); // Add your own conditions etc...

    $relation = new HasOne($builder->getQuery(), $this, 'a_id', 'id');

    return $relation; // or return $relation->withDefault();
}

Update:

A new one-of-many relationship shipped with Laravel 8.42. You probably want to use this instead:

public function B(): HasOne
{
    return $this->hasOne(B::class)->latestOfMany();
}

Gerlachovka answered 14/1, 2020 at 14:15 Comment(1)
Thanks @Mustafa, this is really interesting, i'll keep it in mind the next time; maybe with more complex use cases this will came in help.Lawabiding
M
1

Not really no. You have 2 models mapping tables with some attributes as follow

+-----+-----+
|  A  |  B  |
+-----+-----+
| id  | id  |
| ... | a_id|
|     | ... |
+-----+-----+

When you call onto the hasMany relationship, the builder executes a query. Something like

SELECT * FROM `B` WHERE `B`.`a_id` = ?

If you make a different relationship for it, you will only duplicate the query (if you load both relationships). You could make an accessor, or just use first().

EDIT Accessor code:

# A model
public function getXAttribute()
{
    // Query only if necessary
    if(!this->relationLoaded('b')) $this->load('b');
    // Use Collection methods to filter
    return $this->b->firstWhere(...);
}
Mica answered 5/9, 2019 at 20:2 Comment(2)
thanks for the answer; so you mean i need to make a second hasOne relatioship fro A to B with the correct conditions to get the exact b i want?Lawabiding
No, just ab accessor something likegetXAttribute() { if(!this->relationLoaded('b'){ $this->load('b'); } returns $this->b->firstWhere(...); }Mica

© 2022 - 2024 — McMap. All rights reserved.