robinsonryan's avatar

Eager loaded propety derived from related model

I have a model Device and I need to return a property target_firmware that is eager loaded. The value of the property is determined through two related models DeviceFirmwareTarget and Asset. Device has one or none DeviceFirmwareTarget which has two columns asset_id and target_branch. Either asset_id will have a value or target_branch, but not both. Asset has a column called version.

The property target_firmware should be determined as follows. If a related DeviceFirmwareTarget exists, the value is Asset->version if DeviceFirmareTarget->asset_id is not null, otherwise it is DeviceFirmwareTarget->target_branch if its value is not null, otherwise the default value is published.

The code below will give me the correct value, but the property is not eager loaded, so the query runs for every device returned. Is there a way to eager load the property?

Models/Device.php

public function targetFirmware()
{
    return $this->hasOne(DeviceFirmwareTarget::class, 'device_id');
}

public function getTargetFirmwareAttribute()
{
    $targetFirmware = $this->targetFirmware()->leftJoin('binary_assets', 'device_firmware_targets.asset_id', '=', 'binary_assets.id')
        ->select(\DB::raw('COALESCE(binary_assets.version, device_firmware_targets.target_branch) AS target_firmware'))
        ->value('target_firmware');

    return $targetFirmware ?? 'published';
 }

DeviceController.php@index

// php debug bar shows a query for each device returned to populate the 'target_firmware" property
return Device::all();
0 likes
1 reply
tisuchi's avatar

@robinsonryan IIUC, you can achieve this way:

Models/Device.php

public function targetFirmware()
{
    return $this->hasOne(DeviceFirmwareTarget::class, 'device_id');
}

public function asset()
{
    return $this->hasOneThrough(Asset::class, DeviceFirmwareTarget::class, 'device_id', 'id', 'id', 'asset_id');
}

public function getTargetFirmwareAttribute()
{
    $targetFirmware = optional($this->asset)->version ?? optional($this->targetFirmware)->target_branch;
    return $targetFirmware ?? 'published';
}

Now in the DeviceController.php:

public function index()
{
    $devices = Device::with('asset')->get();
    return $devices;
}

Please or to participate in this conversation.