Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eager loading includes #670

Open
yalsicor opened this issue Apr 3, 2022 · 8 comments
Open

Eager loading includes #670

yalsicor opened this issue Apr 3, 2022 · 8 comments
Assignees
Projects

Comments

@yalsicor
Copy link
Contributor

yalsicor commented Apr 3, 2022

Description:
When we are including relations in Apiato, the relationship data is lazy loaded. This means the relationship data is loaded at the Transporter include method. This will cause a n+1 problem on get all rotes, because there is a new query for every parent model entity. This is not the desired behaviour in this case and the prefered methodwould be eager loading the data before hitting the Transporter. While eager loading, the relationship data is loaded in a single query after the parent model data is queried, resulting in much less queries especially with many includes and more so with nested includes.

Expected Behavior:
Maybe add an option to choose between eager loading and lazy loading the includes?

Additional Context:

Versions:
Apiato Version: master branch (v11)
PHP Version: 8.1.0

Steps To Reproduce:
include a relation

@Mohammad-Alavi Mohammad-Alavi added this to To do in v11 via automation Apr 3, 2022
@Mohammad-Alavi
Copy link
Member

Note to anyone who is interested to implement this:

In here search for Eager-Loading vs Lazy-Loading.

@Elshaden
Copy link

Elshaden commented Apr 16, 2022

I use an extra method in all Tasks that require eager-loading and call it before the run();

  public function With(array $with){
        $this->repository->with($with);
        return $this;
    }

in Action I call

 app(SomeFindTask::class)->With(['relation'])->run($id)


 app(SomeGetAllTask::class)->With(['relation'])->run()

Maybe this helps..

@cybsom
Copy link

cybsom commented Nov 8, 2022

    <?php
    
    namespace App\Ship\Criterias;
    
    use App\Ship\Parents\Criterias\Criteria;
    
    class ValidIncludeCriteria extends Criteria
    {
        public function __construct(
            protected array $include,
            protected array $allowedIncludes
        ){
        }
    
        public function apply($model, $repository)
        {
            $this->include = array_intersect($this->include, $this->allowedIncludes);
    
            return $model->with($this->include);
        }
    }

In Task

    ->pushCriteria(new ValidIncludeCriteria($include, ['some_available_relation'])) 

@yalsicor
Copy link
Contributor Author

yalsicor commented Nov 8, 2022

I have something like that as well on task layer, even with include-relation mapping, if the two dont match, but I think its not very clean and elegant and it does not work for nested includes.

@Mohammad-Alavi Mohammad-Alavi self-assigned this Jun 6, 2023
@karelvanzijl
Copy link

karelvanzijl commented Jun 25, 2023

I haven't tested the following, but I think another option you can use is the query parameter l5_with= to tell which lazy load(s) you want to apply:

?l5_with=company;developer

The semicolon is used as the separator.

@davidreyg
Copy link

Hi. I've reading this thread.. is posible to tell a Transformer class which relationship(s) are eager-loaded?
Because if you do not specify them it lazy loads the relationship(s) automatically.

In Laravel API Resources there are many ways to tell a Resource which relationship(s) are eager-loaded.

This is an example from laravel documentation.

use App\Http\Resources\PostResource;
 
/**
 * Transform the resource into an array.
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'posts' => PostResource::collection($this->whenLoaded('posts')),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

@innoflash
Copy link

I think for now we need to do a work around.
Here is how I did mine.

I created a criteria to load relationships first.

class WithRelationshipsCriteria extends Criteria
{
    public function __construct(private readonly array $relationships = []) {
    }

    public function apply($model, RepositoryInterface $repository) {
        $requestIncludes = [];
        if (app('request')->has('include')) {
            $include = app('request')->get('include');
            $requestIncludes = explode(',', $include);
        }

        return $model->with(
            array_merge($this->relationships, $requestIncludes)
        );
    }
}

In the app\Ship\Parents\Repositories\Repository.php I overrode the boot method and pushed the criteria.

    public function boot() {
        parent::boot();

        $this->pushCriteria(WithRelationshipsCriteria::class);
    }

Since all repositories base off this parent then eager loading relations will happen when relationships are requested

@Mohammad-Alavi
Copy link
Member

@innoflash This is a clever workaround 😎

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
v11
To do
Development

No branches or pull requests

7 participants