Skip to content

Nested relationships not being loaded correctly #991

@toyi

Description

@toyi

Scribe version

5.2.1

PHP version

8.4

Laravel version

12.12.0

Scribe config

postman.enabled => false
strategies.headers => added [
    'Knuckles\\Scribe\\Extracting\\Strategies\\StaticData',
    [
        'only' => [],
        'except' => [],
        'data' => [],
    ],
]: removed [
    'Knuckles\\Scribe\\Extracting\\Strategies\\StaticData',
    [
        'only' => [],
        'except' => [],
        'data' => [
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ],
]

What happened?

Currently, we can load nested relationships in the response using the following argument on the ResponseFromApiResource attribute:

with: ['posts.categories']

However, when we want to load multiple BelongsTo nested relationships that all have the same first level model, only the last one is effectively loaded.

Consider the following example:

with: [
    'order.status',
    'order.delivery'
]

Here, only order.delivery will be loaded and order.status will be null.

This is probably due to the current way these relationships are loaded, iterating through the with array provided in the attribute and adding a new ->for(XXXXX::factory()) for each relationships in the list.

The problem with this implementation is:

  1. Create the base model factory (let's say, User)
  2. It looks at the with array and add ->for(Order::factory())
  3. Order didn't come alone, it was followed by .status, so it adds ->for(Status::factory()) to the Order factory it just created.

At this stage, we have something like this:

User::factory()->for(
    Order::factory()->for(
        Status::factory()
    )
)

The current implementation stops here and considers order.status done. It jumps to the next relationship. Which is... order.delivery.

  1. It repeats the steps 2 and 3, which gives the final factory for the base User model:
User::factory()->for(
    Order::factory()->for(
        Status::factory()
    )
)->for(
    Order::factory()->for(
        Delivery::factory()
    )
)

Since there is twice Order::factory, the latter takes precedences and order.status ends up null.

All that is happening here https://github.com/knuckleswtf/scribe/blob/v5/src/Tools/Utils.php#L259-L284

The correct final result should be:

User::factory()->for(
    Order::factory()->for(
        Status::factory()
    )->for(
        Delivery::factory()
    )
)

Docs

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions