Skip to content

Conversation

carlobeltrame
Copy link
Contributor

@carlobeltrame carlobeltrame commented Aug 9, 2021

Q A
Branch? main for features
Tickets #2444, #1959, CC @soyuka @teohhanhui
License MIT
Doc PR None, since this is more or less a refactoring, with only very technical changes

This PR works towards #2444. I refactored the EagerLoadingExtension a bit. Namely I changed 3 things:

  • Avoid eager joining back to the parent that we just came from, for ToOne relations: When eager joining author.books, the nested joinRelations(..., Book::class, ...) call will ignore the book.author relation, because we already know the author.
  • Remove unnecessary duplicate select statement: In Invalid DQL when using selfreferenced entity #1959 it was noticed that this exact select statement is already added a few lines above. The only exception is when fetchPartial is active, and in that case the current implementation was wrong anyways, because it always adds the full select. The fix from Fix #1959 do not join same association twice #1961 is really not needed for any conceivable case. So to clean up, I removed it.
  • Avoid joining unnecessary recursive relations: This is the only commit that required adjusting the tests. The ultimate goal was: If a relation is readableLink: true, it should be eager loaded, including it's transitive relations (which are readable etc.). Otherwise, if the relation is at least readable: true, it should still be eager loaded (for generating the IRI), but not its relations. E.g. an author has many books and each book has many chapters. When eager loading the author's relations, we want to eager load the books as soon as author.books is readable, but unless author.books is also readableLink, we don't need to eager load book.chapters. This PR introduces the ability to addSelect('books') without calling the recursive $this->joinRelations(..., 'books', ...).

Since the implementation of the joinRelations method takes into account a lot of options, I visualized the behaviour here. Left is before this PR, right is after. Green is the first commit, red the second, blue the third, where I basically only moved the readableLink check later. This move resulted in some more (correct) joins in some Behat tests.

graphviz(2)
editor link for later reference

These changes all don't break backward compatibility because of Doctrine's awesome ability to fetch missing relations when they are accessed.

Motivation This PR is motivated by our application which uses the following pattern for serialization groups:
#[ApiResource(
  normalization_context: ['groups' => ['read']],
  denormalization_context: ['groups' => ['write']],
)]
class Book {
  #[Groups(['read', 'write'])]
  public string $title = '';

  /*
   * @ORM\ManyToOne(targetEntity="Person", inversedBy="books")
   */
  #[ApiProperty(readableLink: false, writableLink: false)]
  #[Groups(['read', 'write'])]
  public ?Person $author = null;
}

#[ApiResource(
  normalization_context: ['groups' => ['read']],
  denormalization_context: ['groups' => ['write']],
)]
class Person {
  #[Groups(['read', 'write'])]
  public string $name = '';

  /*
   * @ORM\OneToMany(targetEntity="Book", mappedBy="author")
   */
  #[ApiProperty(readableLink: false, writableLink: false)]
  #[Groups(['read', 'write'])]
  public Collection $books;
}

The idea is that we always want to present full representations in the API, never partial. And we always want to list all relations as links, except in a few cases where we embed them (using a quite elegant mechanism also based on serialization groups).

Unfortunately, with these groups, the current implementation of the EagerLoadingExtension will eager join absolutely everything, running into the join limit very quickly.

carlobeltrame added a commit to pmattmann/ecamp3 that referenced this pull request Aug 9, 2021
@soyuka
Copy link
Member

soyuka commented Aug 10, 2021

I like this, please rebase against main if we can merge this it'd be a must

@carlobeltrame carlobeltrame force-pushed the refactor-eager-loading branch 6 times, most recently from c73cc65 to 3d48bbb Compare September 4, 2021 15:15
@carlobeltrame
Copy link
Contributor Author

@soyuka rebased onto main. We also had to add another commit to make sure each alias is added to the select clause only once. Doctrine would cause nasty errors otherwise.

carlobeltrame added a commit to carlobeltrame/ecamp3 that referenced this pull request Sep 7, 2021
The EagerLoadingExtension is only temporarily in our codebase, until
api-platform/core#4377 is merged and released.

There are tests for this extension in API Platform core, so we shouldn't
need to write our own test suite for it. However, because of our limited
use cases so far, this class lowers the coverage too much. Therefore, I
commented out some code of which I am certain we won't need it in the
near future. Less untested code = higher coverage.
carlobeltrame and others added 4 commits September 27, 2021 16:03
This exact select statement is already added a few lines above. The only
exception is when fetchPartial is active, and in that case the current
implementation is wrong anyways, because it always adds the full select.

When this "temporary" solution for avoiding recursion was implemented,
the duplicated line above was not yet there:
https://github.com/api-platform/core/blob/5ba518014e2770e1ad5686b0124b2db45245fee5/src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php#L148

But later, the duplicated statement was added, and the select statement
inside the "Avoid recursion" case was made obsolete:
api-platform@e34427a
@alanpoulain alanpoulain force-pushed the refactor-eager-loading branch from 3d48bbb to 4c6ae9e Compare September 27, 2021 14:03
@carlobeltrame
Copy link
Contributor Author

Not sure why the Behat Rector installation fails. But given that currently all PRs as well as the main branch have the same error, and I didn't touch anything composer.json-related, I think there is nothing to fix in this PR.

@alanpoulain
Copy link
Member

You're right. I think @soyuka would like to review your PR before merging it. Nice work.

@soyuka soyuka merged commit 2625c81 into api-platform:main Oct 29, 2021
@soyuka
Copy link
Member

soyuka commented Oct 29, 2021

Thank you very much for the work @carlobeltrame !

@carlobeltrame carlobeltrame deleted the refactor-eager-loading branch October 29, 2021 07:48
FloVnst pushed a commit to FloVnst/core that referenced this pull request Nov 9, 2021
* feat: Avoid eager joining back to the just visited parent

* fix: Remove unnecessary duplicate select statement

This exact select statement is already added a few lines above. The only
exception is when fetchPartial is active, and in that case the current
implementation is wrong anyways, because it always adds the full select.

When this "temporary" solution for avoiding recursion was implemented,
the duplicated line above was not yet there:
https://github.com/api-platform/core/blob/5ba518014e2770e1ad5686b0124b2db45245fee5/src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php#L148

But later, the duplicated statement was added, and the select statement
inside the "Avoid recursion" case was made obsolete:
api-platform@e34427a

* refactor(eager loading): Avoid joining unnecessary recursive relations

* fix: prevent adding same alias twice; doctrine does not like it

Co-authored-by: Pirmin Mattmann <pimattmann@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants