Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

mankowitz's avatar

Getting a child of child relation

I have three tables which are related.

A site has many patients which have many complaints. Here are abbreviated models.

class Site extends Model
{
    public function patients() 
    {
        return $this->hasMany('App\Patient');
    }
}

patient:

class Patient extends Model
{

    public function site()
    {
        return $this->belongsTo('App\Site');
    }

    public function complaints()
    {
        return $this->hasMany('App\Complaint');
    }
}

And finally, Complaint

class Complaint extends Model
{
    public function patient()
    {
        return $this->belongsTo('App\Patient');
    }

    public function site()
    {
        return $this->patient->site;
    }
}

So, my problem is that when I query complaints, I want to be able to get the site that the patient was in when that was generated.

Complaint::whereNull('resolved_at')->with('site')->get()

Gives me this:

Illuminate\Database\Eloquent\RelationNotFoundException with message 'Call to undefined relationship [site] on model [App\Complaint].'

One way to workaround was to add this to Complaint.php:

    protected $appends = ['site']; 

    public function getSiteAttribute()
    {
        return $this->patient->site;
    }

Which gives me this:

>>> Complaint::whereNull('resolved_at')->first()
=> App\Complaint {#211
     id: 1,
     reported_by: 7,
     patient_id: 4,
     problem: "Medicine made patient itchy",
     provider_id: 2,
     resolved_by: null,
// NOTE THAT site IS MISSING!!!
   }

Surprisingly, however, this does work:

>>> Complaint::whereNull('resolved_by')->first()->site
=> App\Site {#217
     id: 3,
     name: "Nursing Home 3 (pro)",
     site_info: null,
   }

So, what is the best way to make sure that site is listed in the reply?

0 likes
5 replies
bobbybouwmann's avatar
Level 88

Well, you only use with on relationships, not on random methods.

Instead you can fetch both relations here

$complaint = Complaint::with('patient.site')->findOrFail(3);

$site = $complain->patient->site;

Another solution is just like you do use a method, which should already work! However it will still do the same amount of queries

// Complaint
public function site()
{
    return $this->patient->site;
}

$complaint = Complaint::findOrFail(3);
$site = $complaint->site();
Cronix's avatar

Also, return $this->patient->site; returns a value. You need to return a query builder instance so the query can continue to be built upon.

I think return return $this->patient()->site(); would work.

mankowitz's avatar

Is there a way to restrict the number of fields that are generated in both the parent and the child relation? For example, I want

provider:name reporter:name site:name patient:first_name,last_name

When I do this,

Complaint::whereNull('resolved_at')->with('provider', 'reporter', 'patient.site')->get();

I get the whole record for provider, reporter, patient and site. I can limit those with

However, when I try this:

>>> Complaint::whereNull('resolved_at')->with('provider:id,name', 'reporter:id,name', 'patient:id,first_name', 'patient.site:name,id')->get();
=> Illuminate\Database\Eloquent\Collection {#1421
     all: [
       App\Complaint {#1392
         id: 1,
         reported_by: 7,
         created_at: "2018-03-27 04:59:56",
         updated_at: "2018-03-27 04:59:56",
         patient_id: 4,
         source: "patient",
         problem: "Medicine made patient itchy",
         provider_id: 2,
         resolution: null,
         resolved_by: null,
         resolved_at: null,
         provider: App\User {#1418
           id: 2,
           name: "Scott",
         },
         reporter: App\User {#1420
           id: 7,
           name: "External NH Two",
         },
         patient: App\Patient {#1406
           id: 4,
           first_name: "Patient",
           site: null,  // I WANT THIS TO HAVE DATA.
         },
       },
     ],
   }
Jose13000's avatar

@mankowitz in this way can help you

Complaint::whereNull('resolved_at')->with('provider', 'reporter', 'patient' => function($query){ $query->with('site'); })->get();

bobbybouwmann's avatar

@mankowitz You can select only a few fields when you use a raw query or just the query builder. When using relationships, you can do it but it doesn't add anything and makes the logic difficult! Also some fields are required to make the relationships work.

Anyway, is your question answered?

Please or to participate in this conversation.