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

vincent15000's avatar

API resources with relationships

Hello,

I need help to understand why I have relationships added in my API ressource although I don't have loaded them.

public function infrastructures()
{
    Gate::authorize('viewAny', Infrastructure::class);

    $infrastructures = Infrastructure::with('ports')->orderBy('name')->get();
    
    return new InfrastructureCollection($infrastructures);
}

...

public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'trash_port' => $this->trash_port,
        'ports' => new PortCollection($this->whenLoaded('ports')),
        'branches' => new BranchCollection($this->whenLoaded('branches')),
        'can_update' => auth()->user()?->can('update', $this->resource) ?? false,
    ];
}

I get a response with the ports but also with all ports' nested relationships.

But I only need the port API resource without any relationship.

If I had eager loaded ('ports.endoint'), I would have understood, but the endpoints are not loaded.

Can you help me understand why I get the endpoints are loaded too ?

Thanks for your help.

V

0 likes
19 replies
vincent15000's avatar

@martinbean I think that you need to know how the relationships are defined.

// Infrastructure model
public function ports()
{
    return $this->hasMany(Port::class)->orderByRaw('natural_sort_key(ports.name)');
}

// Port model

public function infrastructure()
{
    return $this->belongsTo(Infrastructure::class);
}

public function endpoint()
{
    return $this->belongsTo(Endpoint::class);
}

public function branch()
{
    return $this->belongsTo(Branch::class);
}
vincent15000's avatar

@martinbean

class Infrastructure extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
        'description',
    ];

    public function ports()
    {
        return $this->hasMany(Port::class)->orderByRaw('natural_sort_key(ports.name)');
    }
}

#[ObservedBy([PortObserver::class])]
class Port extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
        'config',
        'infrastructure_id',
    ];

    protected $casts = [
        'config' => 'array',
    ];

    public function infrastructure()
    {
        return $this->belongsTo(Infrastructure::class);
    }

    public function endpoint()
    {
        return $this->belongsTo(Endpoint::class);
    }

    public function branch()
    {
        return $this->belongsTo(Branch::class);
    }
}
johndivam's avatar

public function infrastructures() or public function index() !!

1 like
vincent15000's avatar

@johndivam It's the relationship defined in the Port model, so infrastructures() function for the relationship.

MohamedTammam's avatar

Mostly you have your relationship in $appends property inside the model.

1 like
MohamedTammam's avatar

@vincent15000 What if you fix the collection syntax

'ports' => PortCollection::collection($this->whenLoaded('ports')),
'branches' => BranchCollection::collection($this->whenLoaded('branches')),
1 like
vincent15000's avatar

@MohamedTammam I don't have tried this. I try tomorrow and I tell you if it works.

But you probably meant PortResource::collection ?

vincent15000's avatar

@MohamedTammam I don't have changed anything and it works. I will try to understand what happened and I will tell you.

1 like
karim_aouaouda's avatar

after searching in laravel, i found that the toArray function in resources is the same as toArray in the model, it's the same, and this is the definition :

public function toArray()
    {
        return array_merge($this->attributesToArray(), $this->relationsToArray());
    }

that's mean every execution of the toArray method it will call the relation toArray Method through $this->relationsToArray(), see :

public function relationsToArray()
    {
        $attributes = [];

        foreach ($this->getArrayableRelations() as $key => $value) {
            // If the values implement the Arrayable interface we can just call this
            // toArray method on the instances which will convert both models and
            // collections to their proper array form and we'll set the values.
            if ($value instanceof Arrayable) {
                $relation = $value->toArray();
            }
		...
		...
	}

when you call whenLoaded() it will load the relation 'ports' with all of it's relations, then it will call toArray in the collection and that's mean every Port model cann toArray ... and so on

1 like
vincent15000's avatar

@karim_aouaouda I think that what you are saying is false.

->whenLoaded() checks if the relationship is loaded : if it isn't loaded, the attribute is not added to the array.

Check by yourself, you will notice it.

rodrigo.pedra's avatar

What does your PortCollection resource class look like?

Are you using $this->whenLoaded(...) there too? For its direct relations?

1 like
vincent15000's avatar

@rodrigo.pedra No the collection is only useful for me to add additional data.

In the case of the PortCollection, it's empty, I mean I don't have changed it after the creation.

rodrigo.pedra's avatar

@vincent15000 but still its toArray() method needed some customization, right?

Otherwise, how are a Port's related models being returned?

1 like
vincent15000's avatar

@rodrigo.pedra Even if you don't customize the toArray() method, if the relationships are loaded, and only if they are loaded, they are automatically added to the array representation of the model.

Please or to participate in this conversation.