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

onit's avatar
Level 3

Querying nested relations (once more)

I have some models with (nested) relations between each other: a project which has some metadata which is linked to a metadata registry.

Project (id, identifier, ...):

public function metadata() {
    return $this->hasMany(ProjectMetadata::class);
}

ProjectMetadata (id, project_id, metadata_registry_id, content, ...):

public function project() {
    return $this->belongsTo(Project::class);
}
public function metadata_registry() {
    return $this->belongsTo(MetadataRegistry::class);
}

MetadataRegistry (id, identifier, ...):

public function project_metadata() {
        return $this->hasMany(ProjectMetadata::class);
}

I can eager-load all these relations without problems:

projects = $this->project
            ->with('metadata', 'metadata.metadata_registry')
            ->get();
Project {#427 ▼
  #attributes: array:12 [▶]
  #relations: array:1 [▼
    "metadata" => Collection {#435 ▼
      #items: array:5 [▼
        0 => ProjectMetadata {#437 ▼
          #table: "project_metadata"
          #attributes: array:7 [▶]
          #relations: array:1 [▼
            "metadata_registry" => MetadataRegistry {#446 ▼
              #table: "metadata_registry"
              #attributes: array:6 [▼
                "id" => 1
                "namespace" => "project"
                "identifier" => "title"
                "title" => "Project Title"
                "description" => "The title of the research project."
                "content_type_id" => 1

Now I want to use the nested relationship information in a view, preferably with a simple custom attribute like $project->title I tried adding custom Attributes, getTitleAttribute() which called a construct in the Project model like this (because the identifier issits in the metadata_registry model):

$metadata_query = $this->metadata()->whereHas('metadata_registry', function ($q) {
    $q->where('identifier', 'title');
})->get();

but this always results in new queries and I'd like to use the already eager-loaded values. Using a lot of foreach loops for every one of these custom attributes is not nice.

I'm sure I didn't fully understood Eloquent relations and maybe this is not possible. I know I could fall back to a DB:raw or to Joins. But is there a way? My last resort would be to drop the registry table and do everything in the project_metadata table but it seems redundant to me.

Can anyone help me with this? Thanks in advance!

0 likes
3 replies
Snapey's avatar

1st option $project->metadata_registry->title

assuming the obvious answer is not the one you want, in the project model;

public function getTitleAttribute()
{
    return $this->metadata_registry->title;
}

this will cause a failure if the relation is not loaded so you can check first if necessary

public function getTitleAttribute()
{
    if(!$this->relationLoaded('metadata_registry')) {

        $this->load('metadata_registry');

    }
    
    return $this->metadata_registry->title;

}

onit's avatar
Level 3

Thanks for the suggestion, Snapey! But then I get (of course)

Call to undefined relationship [metadata_registry] on model [App\Project]

since the relation is not defined on the project model.

The thing is: I define the name of the project metadatum in the registry model and since I only have its primary key (id) in the project metadata model I have to first query the registry for the metadata identifier in order to get the ID in order to query the actual project metadata model.

Maybe its a faulty ER design to do it this way, but this way the app stays flexible and allows for mapping datasources to the metadata registry (which is part of the app).

PS: And I would be interested in the 2nd option if there's one ;)

Snapey's avatar
public function getTitleAttribute()
{
    if(!$this->relationLoaded('metadata_registry')) {

        $this->load('metadata.metadata_registry');

    }
    
    return $this->metadata->metadata_registry->title;

}

Please or to participate in this conversation.