From 49888f546414187dbf9443a8471bb66d23335be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Tvrd=C3=ADk?= Date: Sun, 13 Oct 2024 20:36:00 +0200 Subject: [PATCH 1/8] fix tests compatibility with Doctrine 3.3.0 which reenabled partial --- tests/EntityPreloadBlogManyHasManyTest.php | 24 ++++++++++ tests/EntityPreloadBlogOneHasManyDeepTest.php | 45 ++++++++++++++++++- tests/EntityPreloadBlogOneHasManyTest.php | 28 +++++------- tests/Lib/TestCase.php | 11 +++++ 4 files changed, 90 insertions(+), 18 deletions(-) diff --git a/tests/EntityPreloadBlogManyHasManyTest.php b/tests/EntityPreloadBlogManyHasManyTest.php index 8082ee5..34f82dc 100644 --- a/tests/EntityPreloadBlogManyHasManyTest.php +++ b/tests/EntityPreloadBlogManyHasManyTest.php @@ -23,6 +23,30 @@ public function testManyHasManyUnoptimized(): void ]); } + public function testOneHasManyWithWithManualPreloadUsingPartial(): void + { + $this->skipIfPartialEntitiesAreNotSupported(); + $this->createDummyBlogData(articleInEachCategoryCount: 5, tagForEachArticleCount: 5); + + $articles = $this->getEntityManager()->getRepository(Article::class)->findAll(); + + $this->getEntityManager()->createQueryBuilder() + ->select('PARTIAL article.{id}', 'tag') + ->from(Article::class, 'article') + ->leftJoin('article.tags', 'tag') + ->where('article IN (:articles)') + ->setParameter('articles', $articles) + ->getQuery() + ->getResult(); + + $this->readTagLabels($articles); + + self::assertAggregatedQueries([ + ['count' => 1, 'query' => 'SELECT * FROM article t0'], + ['count' => 1, 'query' => 'SELECT * FROM article a0_ LEFT JOIN article_tag a2_ ON a0_.id = a2_.article_id LEFT JOIN tag t1_ ON t1_.id = a2_.tag_id WHERE a0_.id IN (?, ?, ?, ?, ?)'], + ]); + } + public function testManyHasManyWithFetchJoin(): void { $this->createDummyBlogData(articleInEachCategoryCount: 5, tagForEachArticleCount: 5); diff --git a/tests/EntityPreloadBlogOneHasManyDeepTest.php b/tests/EntityPreloadBlogOneHasManyDeepTest.php index db3886a..247d65d 100644 --- a/tests/EntityPreloadBlogOneHasManyDeepTest.php +++ b/tests/EntityPreloadBlogOneHasManyDeepTest.php @@ -5,6 +5,8 @@ use Doctrine\ORM\Mapping\ClassMetadata; use ShipMonkTests\DoctrineEntityPreloader\Fixtures\Blog\Category; use ShipMonkTests\DoctrineEntityPreloader\Lib\TestCase; +use function array_map; +use function array_merge; class EntityPreloadBlogOneHasManyDeepTest extends TestCase { @@ -28,6 +30,46 @@ public function testOneHasManyDeepUnoptimized(): void ]); } + public function testOneHasManyDeepWithWithManualPreloadUsingPartial(): void + { + $this->skipIfPartialEntitiesAreNotSupported(); + $this->createCategoryTree(depth: 5, branchingFactor: 5); + + $rootCategories = $this->getEntityManager()->createQueryBuilder() + ->select('category') + ->from(Category::class, 'category') + ->where('category.parent IS NULL') + ->getQuery() + ->getResult(); + + $this->getEntityManager()->createQueryBuilder() + ->select('PARTIAL category.{id}', 'subCategory') + ->from(Category::class, 'category') + ->leftJoin('category.children', 'subCategory') + ->where('category IN (:categories)') + ->setParameter('categories', $rootCategories) + ->getQuery() + ->getResult(); + + $subCategories = array_merge(...array_map(static fn(Category $category) => $category->getChildren()->toArray(), $rootCategories)); + $this->getEntityManager()->createQueryBuilder() + ->select('PARTIAL subCategory.{id}', 'subSubCategory') + ->from(Category::class, 'subCategory') + ->leftJoin('subCategory.children', 'subSubCategory') + ->where('subCategory IN (:subCategories)') + ->setParameter('subCategories', $subCategories) + ->getQuery() + ->getResult(); + + $this->readSubSubCategoriesNames($rootCategories); + + self::assertAggregatedQueries([ + ['count' => 1, 'query' => 'SELECT * FROM category c0_ WHERE c0_.parent_id IS NULL'], + ['count' => 1, 'query' => 'SELECT * FROM category c0_ LEFT JOIN category c1_ ON c0_.id = c1_.parent_id WHERE c0_.id IN (?, ?, ?, ?, ?)'], + ['count' => 1, 'query' => 'SELECT * FROM category c0_ LEFT JOIN category c1_ ON c0_.id = c1_.parent_id WHERE c0_.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'], + ]); + } + public function testOneHasManyDeepWithFetchJoin(): void { $this->createCategoryTree(depth: 5, branchingFactor: 5); @@ -37,13 +79,14 @@ public function testOneHasManyDeepWithFetchJoin(): void ->from(Category::class, 'category') ->leftJoin('category.children', 'subCategories') ->leftJoin('subCategories.children', 'subSubCategories') + ->where('category.parent IS NULL') ->getQuery() ->getResult(); $this->readSubSubCategoriesNames($rootCategories); self::assertAggregatedQueries([ - ['count' => 1, 'query' => 'SELECT * FROM category c0_ LEFT JOIN category c1_ ON c0_.id = c1_.parent_id LEFT JOIN category c2_ ON c1_.id = c2_.parent_id'], + ['count' => 1, 'query' => 'SELECT * FROM category c0_ LEFT JOIN category c1_ ON c0_.id = c1_.parent_id LEFT JOIN category c2_ ON c1_.id = c2_.parent_id WHERE c0_.parent_id IS NULL'], ]); } diff --git a/tests/EntityPreloadBlogOneHasManyTest.php b/tests/EntityPreloadBlogOneHasManyTest.php index e5881fd..f28aa60 100644 --- a/tests/EntityPreloadBlogOneHasManyTest.php +++ b/tests/EntityPreloadBlogOneHasManyTest.php @@ -2,13 +2,10 @@ namespace ShipMonkTests\DoctrineEntityPreloader; -use Composer\InstalledVersions; use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Query\QueryException; use ShipMonkTests\DoctrineEntityPreloader\Fixtures\Blog\Article; use ShipMonkTests\DoctrineEntityPreloader\Fixtures\Blog\Category; use ShipMonkTests\DoctrineEntityPreloader\Lib\TestCase; -use function str_starts_with; class EntityPreloadBlogOneHasManyTest extends TestCase { @@ -52,31 +49,26 @@ public function testOneHasManyWithWithManualPreload(): void public function testOneHasManyWithWithManualPreloadUsingPartial(): void { + $this->skipIfPartialEntitiesAreNotSupported(); $this->createDummyBlogData(categoryCount: 5, articleInEachCategoryCount: 5); $categories = $this->getEntityManager()->getRepository(Category::class)->findAll(); - $query = $this->getEntityManager()->createQueryBuilder() + $this->getEntityManager()->createQueryBuilder() ->select('PARTIAL category.{id}', 'article') ->from(Category::class, 'category') ->leftJoin('category.articles', 'article') ->where('category IN (:categories)') ->setParameter('categories', $categories) - ->getQuery(); - - if (str_starts_with(InstalledVersions::getVersion('doctrine/orm') ?? 'unknown', '3.')) { - self::assertException(QueryException::class, null, static fn() => $query->getResult()); - - } else { - $query->getResult(); + ->getQuery() + ->getResult(); - $this->readArticleTitles($categories); + $this->readArticleTitles($categories); - self::assertAggregatedQueries([ - ['count' => 1, 'query' => 'SELECT * FROM category t0'], - ['count' => 1, 'query' => 'SELECT * FROM category c0_ LEFT JOIN article a1_ ON c0_.id = a1_.category_id WHERE c0_.id IN (?, ?, ?, ?, ?)'], - ]); - } + self::assertAggregatedQueries([ + ['count' => 1, 'query' => 'SELECT * FROM category t0'], + ['count' => 1, 'query' => 'SELECT * FROM category c0_ LEFT JOIN article a1_ ON c0_.id = a1_.category_id WHERE c0_.id IN (?, ?, ?, ?, ?)'], + ]); } public function testOneHasManyWithFetchJoin(): void @@ -137,6 +129,8 @@ public function testOneHasManyWithPreload(): void private function readArticleTitles(array $categories): void { foreach ($categories as $category) { + $category->getName(); + foreach ($category->getArticles() as $article) { $article->getTitle(); } diff --git a/tests/Lib/TestCase.php b/tests/Lib/TestCase.php index b4324f3..9b6c5d5 100644 --- a/tests/Lib/TestCase.php +++ b/tests/Lib/TestCase.php @@ -2,6 +2,7 @@ namespace ShipMonkTests\DoctrineEntityPreloader\Lib; +use Composer\InstalledVersions; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Logging\Middleware; use Doctrine\ORM\EntityManager; @@ -23,6 +24,7 @@ use ShipMonkTests\DoctrineEntityPreloader\Fixtures\Blog\User; use Throwable; use function unlink; +use function version_compare; abstract class TestCase extends PhpUnitTestCase { @@ -175,6 +177,15 @@ protected function refreshExistingEntity(object $entity): object return $freshEntity; } + protected function skipIfPartialEntitiesAreNotSupported(): void + { + $ormVersion = InstalledVersions::getVersion('doctrine/orm') ?? '0.0.0'; + + if (version_compare($ormVersion, '3.0.0', '>=') && version_compare($ormVersion, '3.3.0', '<')) { + self::markTestSkipped('Partial entities are not supported in Doctrine ORM versions 3.0 to 3.2'); + } + } + protected function getQueryLogger(): QueryLogger { return $this->queryLogger ??= $this->createQueryLogger(); From fbec4a301aa78a84e8a79253b2dd74035361b2a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Tvrd=C3=ADk?= Date: Thu, 10 Oct 2024 13:14:54 +0200 Subject: [PATCH 2/8] readme: add comparision table --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index d37117a..5782bb9 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,19 @@ - 🔄 **Flexible:** Supports all associations: `#[OneToOne]`, `#[OneToMany]`, `#[ManyToOne]`, and `#[ManyToMany]`. - 💡 **Easy Integration:** Simple to integrate with your existing Doctrine setup. + +## Comparison + +| | Unoptimized | Manual Preload | Fetch Join | setFetchMode | EntityPreloader | +|------------------------------------------------------------------------|-------------|--------------------------|------------------------|--------------|--------------------------------| +| [OneToMany](tests/EntityPreloadBlogOneHasManyTest.php) | 1 + n | impossible in Doctrine 3 | 1, but duplicates rows | 1 + 1 | 1 + 1 | +| [OneToManyDeep](tests/EntityPreloadBlogOneHasManyDeepTest.php) | 1 + n + n² | impossible in Doctrine 3 | 1, but duplicates rows | 1 + 1 + n² | 1 + 1 + 1 | +| [OneToManyAbstract](tests/EntityPreloadBlogOneHasManyAbstractTest.php) | 1 + n + n² | impossible in Doctrine 3 | 1, but duplicates rows | 1 + 1 + n² | 1 + 1 + 1, but duplicates rows | +| [ManyToOne](tests/EntityPreloadBlogManyHasOneTest.php) | 1 + n | 1 + 1 | 1, but duplicates rows | 1 + 1 | 1 + 1 | +| [ManyToOneDeep](tests/EntityPreloadBlogManyHasOneDeepTest.php) | 1 + n + n | 1 + 1 + 1 | 1, but duplicates rows | 1 + 1 + n | 1 + 1 + 1 | +| [ManyToMany](tests/EntityPreloadBlogManyHasManyTest.php) | 1 + n | impossible in Doctrine 3 | 1, but duplicates rows | 1 + 1 | 1 + 1 | + + ## Installation To install the library, use Composer: From 7e28002f7a546c3f072d693dbbe75955b9c1afa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Tvrd=C3=ADk?= Date: Thu, 10 Oct 2024 13:35:42 +0200 Subject: [PATCH 3/8] readme: merge comparison sections --- README.md | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 5782bb9..c43ab05 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,14 @@ | [ManyToOneDeep](tests/EntityPreloadBlogManyHasOneDeepTest.php) | 1 + n + n | 1 + 1 + 1 | 1, but duplicates rows | 1 + 1 + n | 1 + 1 + 1 | | [ManyToMany](tests/EntityPreloadBlogManyHasManyTest.php) | 1 + n | impossible in Doctrine 3 | 1, but duplicates rows | 1 + 1 | 1 + 1 | +Unlike fetch joins, the EntityPreloader does not fetches duplicate data, which slows down both the query and the hydration process, except when necessary to prevent additional queries fired by Doctrine during hydration process. + +Unlike `Doctrine\ORM\AbstractQuery::setFetchMode` it can + +* preload nested associations +* preload `#[ManyToMany]` association +* avoid additional queries fired by Doctrine during hydration process + ## Installation @@ -57,18 +65,6 @@ foreach ($categories as $category) { } ``` -## Comparison vs. Fetch Joins - -Unlike fetch joins, the EntityPreloader does not fetches duplicate data, which slows down both the query and the hydration process, except when necessary to prevent additional queries fired by Doctrine during hydration process. - -## Comparison vs. `Doctrine\ORM\AbstractQuery::setFetchMode` - -Unlike `setFetchMode` it can - -* preload nested associations -* preload `#[ManyToMany]` association -* avoid additional queries fired by Doctrine during hydration process - ## Configuration `EntityPreloader` allows you to adjust batch sizes and fetch join limits to fit your application's performance needs: From 8e2c2bce808a4fb32bfa37a4bf4053f1b56081f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Tvrd=C3=ADk?= Date: Thu, 10 Oct 2024 16:00:19 +0200 Subject: [PATCH 4/8] improve comparison table --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c43ab05..94c415f 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,14 @@ ## Comparison -| | Unoptimized | Manual Preload | Fetch Join | setFetchMode | EntityPreloader | -|------------------------------------------------------------------------|-------------|--------------------------|------------------------|--------------|--------------------------------| -| [OneToMany](tests/EntityPreloadBlogOneHasManyTest.php) | 1 + n | impossible in Doctrine 3 | 1, but duplicates rows | 1 + 1 | 1 + 1 | -| [OneToManyDeep](tests/EntityPreloadBlogOneHasManyDeepTest.php) | 1 + n + n² | impossible in Doctrine 3 | 1, but duplicates rows | 1 + 1 + n² | 1 + 1 + 1 | -| [OneToManyAbstract](tests/EntityPreloadBlogOneHasManyAbstractTest.php) | 1 + n + n² | impossible in Doctrine 3 | 1, but duplicates rows | 1 + 1 + n² | 1 + 1 + 1, but duplicates rows | -| [ManyToOne](tests/EntityPreloadBlogManyHasOneTest.php) | 1 + n | 1 + 1 | 1, but duplicates rows | 1 + 1 | 1 + 1 | -| [ManyToOneDeep](tests/EntityPreloadBlogManyHasOneDeepTest.php) | 1 + n + n | 1 + 1 + 1 | 1, but duplicates rows | 1 + 1 + n | 1 + 1 + 1 | -| [ManyToMany](tests/EntityPreloadBlogManyHasManyTest.php) | 1 + n | impossible in Doctrine 3 | 1, but duplicates rows | 1 + 1 | 1 + 1 | +| | Default | Manual Preload | Fetch Join | setFetchMode | **EntityPreloader** | +|------------------------------------------------------------------------|------------|--------------------------|------------------------|--------------|--------------------------------| +| [OneToMany](tests/EntityPreloadBlogOneHasManyTest.php) | 1 + n | impossible in Doctrine 3 | 1, but duplicates rows | 1 + 1 | 1 + 1 | +| [OneToManyDeep](tests/EntityPreloadBlogOneHasManyDeepTest.php) | 1 + n + n² | impossible in Doctrine 3 | 1, but duplicates rows | 1 + 1 + n² | 1 + 1 + 1 | +| [OneToManyAbstract](tests/EntityPreloadBlogOneHasManyAbstractTest.php) | 1 + n + n² | impossible in Doctrine 3 | 1, but duplicates rows | 1 + 1 + n² | 1 + 1 + 1, but duplicates rows | +| [ManyToOne](tests/EntityPreloadBlogManyHasOneTest.php) | 1 + n | 1 + 1 | 1, but duplicates rows | 1 + 1 | 1 + 1 | +| [ManyToOneDeep](tests/EntityPreloadBlogManyHasOneDeepTest.php) | 1 + n + n | 1 + 1 + 1 | 1, but duplicates rows | 1 + 1 + n | 1 + 1 + 1 | +| [ManyToMany](tests/EntityPreloadBlogManyHasManyTest.php) | 1 + n | impossible in Doctrine 3 | 1, but duplicates rows | 1 + n | 1 + 1 | Unlike fetch joins, the EntityPreloader does not fetches duplicate data, which slows down both the query and the hydration process, except when necessary to prevent additional queries fired by Doctrine during hydration process. From 39f1727542429411468b3e946982bb33b95fd1ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Tvrd=C3=ADk?= Date: Sun, 13 Oct 2024 20:26:58 +0200 Subject: [PATCH 5/8] add emoji colors to comparsion table --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 94c415f..8c89714 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,14 @@ ## Comparison -| | Default | Manual Preload | Fetch Join | setFetchMode | **EntityPreloader** | -|------------------------------------------------------------------------|------------|--------------------------|------------------------|--------------|--------------------------------| -| [OneToMany](tests/EntityPreloadBlogOneHasManyTest.php) | 1 + n | impossible in Doctrine 3 | 1, but duplicates rows | 1 + 1 | 1 + 1 | -| [OneToManyDeep](tests/EntityPreloadBlogOneHasManyDeepTest.php) | 1 + n + n² | impossible in Doctrine 3 | 1, but duplicates rows | 1 + 1 + n² | 1 + 1 + 1 | -| [OneToManyAbstract](tests/EntityPreloadBlogOneHasManyAbstractTest.php) | 1 + n + n² | impossible in Doctrine 3 | 1, but duplicates rows | 1 + 1 + n² | 1 + 1 + 1, but duplicates rows | -| [ManyToOne](tests/EntityPreloadBlogManyHasOneTest.php) | 1 + n | 1 + 1 | 1, but duplicates rows | 1 + 1 | 1 + 1 | -| [ManyToOneDeep](tests/EntityPreloadBlogManyHasOneDeepTest.php) | 1 + n + n | 1 + 1 + 1 | 1, but duplicates rows | 1 + 1 + n | 1 + 1 + 1 | -| [ManyToMany](tests/EntityPreloadBlogManyHasManyTest.php) | 1 + n | impossible in Doctrine 3 | 1, but duplicates rows | 1 + n | 1 + 1 | +| | Default | Manual Preload | Fetch Join | setFetchMode | **EntityPreloader** | +|------------------------------------------------------------------------|-------------------------|---------------------------------------|----------------------------------------|-------------------------|------------------------------------------------| +| [OneToMany](tests/EntityPreloadBlogOneHasManyTest.php) | :red_circle: 1 + n | :red_circle: impossible in Doctrine 3 | :orange_circle: 1, but duplicates rows | :green_circle: 1 + 1 | :green_circle: 1 + 1 | +| [OneToManyDeep](tests/EntityPreloadBlogOneHasManyDeepTest.php) | :red_circle: 1 + n + n² | :red_circle: impossible in Doctrine 3 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + 1 + n² | :green_circle: 1 + 1 + 1 | +| [OneToManyAbstract](tests/EntityPreloadBlogOneHasManyAbstractTest.php) | :red_circle: 1 + n + n² | :red_circle: impossible in Doctrine 3 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + 1 + n² | :orange_circle: 1 + 1 + 1, but duplicates rows | +| [ManyToOne](tests/EntityPreloadBlogManyHasOneTest.php) | :red_circle: 1 + n | :green_circle: 1 + 1 | :orange_circle: 1, but duplicates rows | :green_circle: 1 + 1 | :green_circle: 1 + 1 | +| [ManyToOneDeep](tests/EntityPreloadBlogManyHasOneDeepTest.php) | :red_circle: 1 + n + n | :green_circle: 1 + 1 + 1 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + 1 + n | :green_circle: 1 + 1 + 1 | +| [ManyToMany](tests/EntityPreloadBlogManyHasManyTest.php) | :red_circle: 1 + n | :red_circle: impossible in Doctrine 3 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + n | :green_circle: 1 + 1 | Unlike fetch joins, the EntityPreloader does not fetches duplicate data, which slows down both the query and the hydration process, except when necessary to prevent additional queries fired by Doctrine during hydration process. From c438cc6d671358694a558a7a864fa9548434c7a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Tvrd=C3=ADk?= Date: Sun, 13 Oct 2024 20:27:31 +0200 Subject: [PATCH 6/8] readme: use ascii --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8c89714..dbe4c7c 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ `shipmonk/doctrine-entity-preloader` is a PHP library designed to tackle the n+1 query problem in Doctrine ORM by efficiently preloading related entities. This library offers a flexible and powerful way to optimize database access patterns, especially in cases with complex entity relationships. -- 🚀 **Performance Boost:** Minimizes n+1 issues by preloading related entities with **constant number of queries**. -- 🔄 **Flexible:** Supports all associations: `#[OneToOne]`, `#[OneToMany]`, `#[ManyToOne]`, and `#[ManyToMany]`. -- 💡 **Easy Integration:** Simple to integrate with your existing Doctrine setup. +- :rocket: **Performance Boost:** Minimizes n+1 issues by preloading related entities with **constant number of queries**. +- :arrows_counterclockwise: **Flexible:** Supports all associations: `#[OneToOne]`, `#[OneToMany]`, `#[ManyToOne]`, and `#[ManyToMany]`. +- :bulb: **Easy Integration:** Simple to integrate with your existing Doctrine setup. ## Comparison From 880f22efcf53b71e8aca38ad556e04102b2bea13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Tvrd=C3=ADk?= Date: Sun, 13 Oct 2024 20:31:13 +0200 Subject: [PATCH 7/8] readme: add links to slides --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index dbe4c7c..8116fd2 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,14 @@ ## Comparison -| | Default | Manual Preload | Fetch Join | setFetchMode | **EntityPreloader** | -|------------------------------------------------------------------------|-------------------------|---------------------------------------|----------------------------------------|-------------------------|------------------------------------------------| -| [OneToMany](tests/EntityPreloadBlogOneHasManyTest.php) | :red_circle: 1 + n | :red_circle: impossible in Doctrine 3 | :orange_circle: 1, but duplicates rows | :green_circle: 1 + 1 | :green_circle: 1 + 1 | -| [OneToManyDeep](tests/EntityPreloadBlogOneHasManyDeepTest.php) | :red_circle: 1 + n + n² | :red_circle: impossible in Doctrine 3 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + 1 + n² | :green_circle: 1 + 1 + 1 | -| [OneToManyAbstract](tests/EntityPreloadBlogOneHasManyAbstractTest.php) | :red_circle: 1 + n + n² | :red_circle: impossible in Doctrine 3 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + 1 + n² | :orange_circle: 1 + 1 + 1, but duplicates rows | -| [ManyToOne](tests/EntityPreloadBlogManyHasOneTest.php) | :red_circle: 1 + n | :green_circle: 1 + 1 | :orange_circle: 1, but duplicates rows | :green_circle: 1 + 1 | :green_circle: 1 + 1 | -| [ManyToOneDeep](tests/EntityPreloadBlogManyHasOneDeepTest.php) | :red_circle: 1 + n + n | :green_circle: 1 + 1 + 1 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + 1 + n | :green_circle: 1 + 1 + 1 | -| [ManyToMany](tests/EntityPreloadBlogManyHasManyTest.php) | :red_circle: 1 + n | :red_circle: impossible in Doctrine 3 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + n | :green_circle: 1 + 1 | +| | [Default](https://docs.google.com/presentation/d/1sSlZOxmEUVKt0l8zhimex-6lR0ilC001GXh8colaXxg/edit#slide=id.g30998e74a82_0_0) | [Manual Preload](https://docs.google.com/presentation/d/1sSlZOxmEUVKt0l8zhimex-6lR0ilC001GXh8colaXxg/edit#slide=id.g309b68062f4_0_0) | [Fetch Join](https://docs.google.com/presentation/d/1sSlZOxmEUVKt0l8zhimex-6lR0ilC001GXh8colaXxg/edit#slide=id.g309b68062f4_0_15) | [setFetchMode](https://docs.google.com/presentation/d/1sSlZOxmEUVKt0l8zhimex-6lR0ilC001GXh8colaXxg/edit#slide=id.g309b68062f4_0_35) | [**EntityPreloader**](https://docs.google.com/presentation/d/1sSlZOxmEUVKt0l8zhimex-6lR0ilC001GXh8colaXxg/edit#slide=id.g309b68062f4_0_265) | +|------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| +| [OneToMany](tests/EntityPreloadBlogOneHasManyTest.php) | :red_circle: 1 + n | :red_circle: impossible in Doctrine 3 | :orange_circle: 1, but duplicates rows | :green_circle: 1 + 1 | :green_circle: 1 + 1 | +| [OneToManyDeep](tests/EntityPreloadBlogOneHasManyDeepTest.php) | :red_circle: 1 + n + n² | :red_circle: impossible in Doctrine 3 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + 1 + n² | :green_circle: 1 + 1 + 1 | +| [OneToManyAbstract](tests/EntityPreloadBlogOneHasManyAbstractTest.php) | :red_circle: 1 + n + n² | :red_circle: impossible in Doctrine 3 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + 1 + n² | :orange_circle: 1 + 1 + 1, but duplicates rows | +| [ManyToOne](tests/EntityPreloadBlogManyHasOneTest.php) | :red_circle: 1 + n | :green_circle: 1 + 1 | :orange_circle: 1, but duplicates rows | :green_circle: 1 + 1 | :green_circle: 1 + 1 | +| [ManyToOneDeep](tests/EntityPreloadBlogManyHasOneDeepTest.php) | :red_circle: 1 + n + n | :green_circle: 1 + 1 + 1 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + 1 + n | :green_circle: 1 + 1 + 1 | +| [ManyToMany](tests/EntityPreloadBlogManyHasManyTest.php) | :red_circle: 1 + n | :red_circle: impossible in Doctrine 3 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + n | :green_circle: 1 + 1 | Unlike fetch joins, the EntityPreloader does not fetches duplicate data, which slows down both the query and the hydration process, except when necessary to prevent additional queries fired by Doctrine during hydration process. From 708837c052bdd0b6e1efeb379fa26a0c4f5067df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Tvrd=C3=ADk?= Date: Sun, 13 Oct 2024 21:56:44 +0200 Subject: [PATCH 8/8] readme: update comparision table to reflect that partial is supported again --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8116fd2..b4706ba 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,14 @@ | | [Default](https://docs.google.com/presentation/d/1sSlZOxmEUVKt0l8zhimex-6lR0ilC001GXh8colaXxg/edit#slide=id.g30998e74a82_0_0) | [Manual Preload](https://docs.google.com/presentation/d/1sSlZOxmEUVKt0l8zhimex-6lR0ilC001GXh8colaXxg/edit#slide=id.g309b68062f4_0_0) | [Fetch Join](https://docs.google.com/presentation/d/1sSlZOxmEUVKt0l8zhimex-6lR0ilC001GXh8colaXxg/edit#slide=id.g309b68062f4_0_15) | [setFetchMode](https://docs.google.com/presentation/d/1sSlZOxmEUVKt0l8zhimex-6lR0ilC001GXh8colaXxg/edit#slide=id.g309b68062f4_0_35) | [**EntityPreloader**](https://docs.google.com/presentation/d/1sSlZOxmEUVKt0l8zhimex-6lR0ilC001GXh8colaXxg/edit#slide=id.g309b68062f4_0_265) | |------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| -| [OneToMany](tests/EntityPreloadBlogOneHasManyTest.php) | :red_circle: 1 + n | :red_circle: impossible in Doctrine 3 | :orange_circle: 1, but duplicates rows | :green_circle: 1 + 1 | :green_circle: 1 + 1 | -| [OneToManyDeep](tests/EntityPreloadBlogOneHasManyDeepTest.php) | :red_circle: 1 + n + n² | :red_circle: impossible in Doctrine 3 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + 1 + n² | :green_circle: 1 + 1 + 1 | -| [OneToManyAbstract](tests/EntityPreloadBlogOneHasManyAbstractTest.php) | :red_circle: 1 + n + n² | :red_circle: impossible in Doctrine 3 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + 1 + n² | :orange_circle: 1 + 1 + 1, but duplicates rows | +| [OneToMany](tests/EntityPreloadBlogOneHasManyTest.php) | :red_circle: 1 + n | :green_circle: 1 + 1 | :orange_circle: 1, but duplicates rows | :green_circle: 1 + 1 | :green_circle: 1 + 1 | +| [OneToManyDeep](tests/EntityPreloadBlogOneHasManyDeepTest.php) | :red_circle: 1 + n + n² | :green_circle: 1 + 1 + 1 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + 1 + n² | :green_circle: 1 + 1 + 1 | +| [OneToManyAbstract](tests/EntityPreloadBlogOneHasManyAbstractTest.php) | :red_circle: 1 + n + n² | :orange_circle: 1 + 1 + 1, but duplicates rows | :orange_circle: 1, but duplicates rows | :red_circle: 1 + 1 + n² | :orange_circle: 1 + 1 + 1, but duplicates rows | | [ManyToOne](tests/EntityPreloadBlogManyHasOneTest.php) | :red_circle: 1 + n | :green_circle: 1 + 1 | :orange_circle: 1, but duplicates rows | :green_circle: 1 + 1 | :green_circle: 1 + 1 | | [ManyToOneDeep](tests/EntityPreloadBlogManyHasOneDeepTest.php) | :red_circle: 1 + n + n | :green_circle: 1 + 1 + 1 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + 1 + n | :green_circle: 1 + 1 + 1 | -| [ManyToMany](tests/EntityPreloadBlogManyHasManyTest.php) | :red_circle: 1 + n | :red_circle: impossible in Doctrine 3 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + n | :green_circle: 1 + 1 | +| [ManyToMany](tests/EntityPreloadBlogManyHasManyTest.php) | :red_circle: 1 + n | :green_circle: 1 + 1 | :orange_circle: 1, but duplicates rows | :red_circle: 1 + n | :green_circle: 1 + 1 | + +Unlike manual preload does not require writing custom queries for each association. Unlike fetch joins, the EntityPreloader does not fetches duplicate data, which slows down both the query and the hydration process, except when necessary to prevent additional queries fired by Doctrine during hydration process.