talel's avatar
Level 16

Lazy loading - Clarification

General discuesion on lazy loading, I am looking for some clarifications.

I have the following structure for a customer page:

        return Inertia::render('customer/show/page', [
            'customer' => CustomerResource::make($customer->load([
                'quotes.items',
                'quotes.venue',
                'events.quote.items',
                'events.employees',
                'events.venue',
                'notes.author',
                'documents',
            ])),
        ]);

CustomerResource has the following key

'events' => EventCollection::make($this->events->map(fn (Event $event) => $event->setRelation('customer', $this->resource))),

The EventResource under EventCollection has the following key

'address' => AddressResource::make($this->address()->first()),

The address relationship on the Event model structure is:

    public function address(): MorphOne
    {
        if ($this->quote_id) {
            return $this->quote->address();
        }

        if ($this->venue_id) {
            return $this->venue->address();
        }

        return $this->morphOne(Address::class, 'addressable');
    }

Let's take an example for when an Event has an address through a venue. The Venue address returns:

    public function address(): MorphOne
    {
        return $this->morphOne(Address::class, 'addressable');
    }
  • I know the address structure might be confusing, but that's the business logic at the moment.

If I would not load 'events.venue' initially I would get a lazy-loading error when trying to access venue on the address. But, If I would to query the same relationship like that using Tinkerwell for example I would not get a lazy-loading error:

use App\Models\Customer;

$customer = Customer::first();

$event = $customer->events()->first();

$event->address;

Why is that? In both cases I end up accessing the same Address model through the Venue model. In the first example I load the Venue relationship and in the second example I access it after I already retrieved the Event model from the Customer.

Is it related to a serialization of the model when it is passed to the Resource class?

0 likes
1 reply
aleahy's avatar

I assume the lazy loading error you're talking about is due to the use of Model::preventLazyLoading() to avoid N+1 problems.

When you call $event->address from tinkerwell, you've only got 1 event. Try and get all the events and access the address in a loop:

use App\Models\Customer;

$customer = Customer::first();

$events = $customer->events;

foreach($events as $event) {
    $event->address;
}

This will generate the exception for lazy loading because there is more than one query being made to get all the addresses.

If you only access one event and get its address, there is no disadvantage in lazy loading vs eager loading so there is no need to throw the exception.

Please or to participate in this conversation.