1
votes

Have I stumbled upon some weird edge-case, a legitimate bug in Laravel, or am I just doing something wrong?

I have a Model that has a relationship called details. The relationship returns a different Model depending on the change_type_id attribute - which is never null and is always >= 1 && <= 6

The problem I am having is when trying to Eager Load the details relationship. Here's the details:

  • Laravel v5.7

Change Model

class Change extends Model {
    public function details()
    {
        switch ($this->change_type_id) {
            case 1:
                return $this->hasOne(LineManagerChange::class);
            case 2:
                return $this->hasOne(NameChange::class);
            case 3:
                return $this->hasOne(ContractChange::class);
            case 4:
                return $this->hasOne(PositionChange::class);
            case 5:
                return $this->hasOne(CampaignChange::class);
            case 6:
                return $this->hasOne(StatusChange::class);
        }
    }
}

Change Resource

class ChangeResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,

            // the issue occurs even when I don't wrap the resource here
            'details' => new ChangeDetailsResource($this->details)),
        ];
    }
}

This works

When I wrap the response in the Resource class without eager loading the relation, like this:

return ChangeResource::collection(Change::limit(10)->get());

It works as expected and has no problem returning the details relation.


This doesn't work

However, when I try to eager load the relationship I'm getting an error:

// Both of these fail
return Change::with('details')->limit(10)->get();
return ChangeResource::collection(Change::with('details')->limit(10)->get());

Call to a member function addEagerConstraints() on null


Does anyone know why this might be the case?

PS. I've had a look into Polymorphic relationships, but I'm not sure if they're suitable for my use-case?

1
you dont use conditionals in those methods ... those methods are used on new instances that don't have attributes that is how it can get the builder to do the queries for the relationship ... it doesn't go get all the records then go through each one to look at attributes then do the relationship queries ... and you have a case where you are not returning anything from that method, there are no attributes on a new instance ... this looks like you are trying to go for some polymorphic relationship - lagbox
I agree there is suitable case for polymorphic model/table Changable/changables. It's up to you to decide if project requires one2one or some other kind of polymorphic relation. But definitely polymorphic. - Tpojka

1 Answers

1
votes

Eager loading does a query in the background using ID's of models to go to other table and fetch the related data. If your relation method is conditional, at the time of original query, Laravel has no idea on which table to go with the ID's.

You can check out what is happening by dumping the query log with

DB::connection()->enableQueryLog();

// Do some Eloquent fetching

$queries = DB::getQueryLog();
dd($queries);

That all aside, I don't think this is a really good design. Relationship method should return a consistent relationship. If you have need for switches, it is probably a good idea to revise the overall structure.