Skip to content

Commit 4d6c532

Browse files
committed
Force eager by default
1 parent d130edf commit 4d6c532

File tree

8 files changed

+54
-26
lines changed

8 files changed

+54
-26
lines changed

src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,23 @@
2525
* @author Charles Sarrazin <[email protected]>
2626
* @author Kévin Dunglas <[email protected]>
2727
* @author Antoine Bluchet <[email protected]>
28+
* @author Baptiste Meyer <[email protected]>
2829
*/
2930
final class EagerLoadingExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
3031
{
3132
private $propertyNameCollectionFactory;
3233
private $propertyMetadataFactory;
3334
private $resourceMetadataFactory;
3435
private $maxJoins;
35-
private $eagerOnly;
36+
private $forceEager;
3637

37-
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, int $maxJoins = 30, bool $eagerOnly = true)
38+
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, int $maxJoins = 30, bool $forceEager = true)
3839
{
3940
$this->propertyMetadataFactory = $propertyMetadataFactory;
4041
$this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
4142
$this->resourceMetadataFactory = $resourceMetadataFactory;
4243
$this->maxJoins = $maxJoins;
43-
$this->eagerOnly = $eagerOnly;
44+
$this->forceEager = $forceEager;
4445
}
4546

4647
/**
@@ -82,9 +83,10 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator
8283
$options = ['collection_operation_name' => $operationName];
8384
}
8485

86+
$forceEager = $this->isForceEager($resourceClass, $options);
8587
$groups = $this->getSerializerGroups($resourceClass, $options, 'normalization_context');
8688

87-
$this->joinRelations($queryBuilder, $resourceClass, $groups);
89+
$this->joinRelations($queryBuilder, $resourceClass, $forceEager, $groups);
8890
}
8991

9092
/**
@@ -99,6 +101,8 @@ public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterf
99101
$options = ['item_operation_name' => $operationName];
100102
}
101103

104+
$forceEager = $this->isForceEager($resourceClass, $options);
105+
102106
if (isset($context['groups'])) {
103107
$groups = ['serializer_groups' => $context['groups']];
104108
} elseif (isset($context['resource_class'])) {
@@ -107,14 +111,15 @@ public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterf
107111
$groups = $this->getSerializerGroups($resourceClass, $options, 'normalization_context');
108112
}
109113

110-
$this->joinRelations($queryBuilder, $resourceClass, $groups);
114+
$this->joinRelations($queryBuilder, $resourceClass, $forceEager, $groups);
111115
}
112116

113117
/**
114118
* Joins relations to eager load.
115119
*
116120
* @param QueryBuilder $queryBuilder
117121
* @param string $resourceClass
122+
* @param bool $forceEager
118123
* @param array $propertyMetadataOptions
119124
* @param string $originAlias the current entity alias (first o, then a1, a2 etc.)
120125
* @param string $relationAlias the previous relation alias to keep it unique
@@ -123,7 +128,7 @@ public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterf
123128
*
124129
* @throws RuntimeException when the max number of joins has been reached
125130
*/
126-
private function joinRelations(QueryBuilder $queryBuilder, string $resourceClass, array $propertyMetadataOptions = [], string $originAlias = 'o', string &$relationAlias = 'a', bool $wasLeftJoin = false, int &$joinCount = 0)
131+
private function joinRelations(QueryBuilder $queryBuilder, string $resourceClass, bool $forceEager, array $propertyMetadataOptions = [], string $originAlias = 'o', string &$relationAlias = 'a', bool $wasLeftJoin = false, int &$joinCount = 0)
127132
{
128133
if ($joinCount > $this->maxJoins) {
129134
throw new RuntimeException('The total number of joined relations has exceeded the specified maximum. Raise the limit if necessary.');
@@ -136,7 +141,7 @@ private function joinRelations(QueryBuilder $queryBuilder, string $resourceClass
136141
foreach ($classMetadata->associationMappings as $association => $mapping) {
137142
$propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $association, $propertyMetadataOptions);
138143

139-
if (true === $this->eagerOnly && ClassMetadataInfo::FETCH_EAGER !== $mapping['fetch']) {
144+
if (false === $forceEager && ClassMetadataInfo::FETCH_EAGER !== $mapping['fetch']) {
140145
continue;
141146
}
142147

@@ -175,7 +180,30 @@ private function joinRelations(QueryBuilder $queryBuilder, string $resourceClass
175180

176181
$relationAlias .= ++$j;
177182

178-
$this->joinRelations($queryBuilder, $mapping['targetEntity'], $propertyMetadataOptions, $associationAlias, $relationAlias, $method === 'leftJoin', $joinCount);
183+
$this->joinRelations($queryBuilder, $mapping['targetEntity'], $forceEager, $propertyMetadataOptions, $associationAlias, $relationAlias, $method === 'leftJoin', $joinCount);
184+
}
185+
}
186+
187+
/**
188+
* Does an operation force eager?
189+
*
190+
* @param string $resourceClass
191+
* @param array $options
192+
*
193+
* @return bool
194+
*/
195+
private function isForceEager(string $resourceClass, array $options): bool
196+
{
197+
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
198+
199+
if (isset($options['collection_operation_name'])) {
200+
$forceEager = $resourceMetadata->getCollectionOperationAttribute($options['collection_operation_name'], 'force_eager', null, true);
201+
} elseif (isset($options['item_operation_name'])) {
202+
$forceEager = $resourceMetadata->getItemOperationAttribute($options['item_operation_name'], 'force_eager', null, true);
203+
} else {
204+
$forceEager = $resourceMetadata->getAttribute('force_eager');
179205
}
206+
207+
return is_bool($forceEager) ? $forceEager : $this->forceEager;
180208
}
181209
}

src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ private function handleConfig(ContainerBuilder $container, array $config, array
9393
$container->setParameter('api_platform.error_formats', $errorFormats);
9494
$container->setParameter('api_platform.eager_loading.enabled', $config['eager_loading']['enabled']);
9595
$container->setParameter('api_platform.eager_loading.max_joins', $config['eager_loading']['max_joins']);
96-
$container->setParameter('api_platform.eager_loading.eager_only', $config['eager_loading']['eager_only']);
96+
$container->setParameter('api_platform.eager_loading.force_eager', $config['eager_loading']['force_eager']);
9797
$container->setParameter('api_platform.collection.order', $config['collection']['order']);
9898
$container->setParameter('api_platform.collection.order_parameter_name', $config['collection']['order_parameter_name']);
9999
$container->setParameter('api_platform.collection.pagination.enabled', $config['collection']['pagination']['enabled']);

src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public function getConfigTreeBuilder()
4848
->children()
4949
->booleanNode('enabled')->defaultTrue()->info('To enable or disable eager loading')->end()
5050
->integerNode('max_joins')->defaultValue(30)->info('Max number of joined relations before EagerLoading throws a RuntimeException')->end()
51-
->booleanNode('eager_only')->defaultTrue()->info('Only eager load relations having an EAGER fetch mode')->end()
51+
->booleanNode('force_eager')->defaultTrue()->info('Force join on every relation. If disabled, it will only join relations having the EAGER fetch mode.')->end()
5252
->end()
5353
->end()
5454
->booleanNode('enable_fos_user')->defaultValue(false)->info('Enable the FOSUserBundle integration.')->end()

src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
9393
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
9494
<argument>%api_platform.eager_loading.max_joins%</argument>
95-
<argument>%api_platform.eager_loading.eager_only%</argument>
95+
<argument>%api_platform.eager_loading.force_eager%</argument>
9696

9797
<tag name="api_platform.doctrine.orm.query_extension.item" priority="64" />
9898
<tag name="api_platform.doctrine.orm.query_extension.collection" priority="64" />

tests/Bridge/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public function testApplyToCollection()
9595
$queryBuilderProphecy->getEntityManager()->shouldBeCalled(2)->willReturn($emProphecy->reveal());
9696

9797
$queryBuilder = $queryBuilderProphecy->reveal();
98-
$eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal());
98+
$eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), 30, false);
9999
$eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class);
100100
}
101101

@@ -182,7 +182,7 @@ public function testApplyToItem()
182182
$queryBuilderProphecy->getEntityManager()->shouldBeCalled(2)->willReturn($emProphecy->reveal());
183183

184184
$queryBuilder = $queryBuilderProphecy->reveal();
185-
$orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal());
185+
$orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), 30, false);
186186

187187
$orderExtensionTest->applyToItem($queryBuilder, new QueryNameGenerator(), Dummy::class, []);
188188
}
@@ -206,7 +206,7 @@ public function testCreateItemWithOperationName()
206206
$emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
207207
$queryBuilderProphecy->getEntityManager()->shouldBeCalled()->willReturn($emProphecy);
208208

209-
$orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal());
209+
$orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), 30, false);
210210
$orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], 'item_operation');
211211
}
212212

@@ -229,15 +229,16 @@ public function testCreateCollectionWithOperationName()
229229
$emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
230230
$queryBuilderProphecy->getEntityManager()->shouldBeCalled()->willReturn($emProphecy);
231231

232-
$eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal());
232+
$eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), 30, false);
233233
$eagerExtensionTest->applyToCollection($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, 'collection_operation');
234234
}
235235

236236
public function testDenormalizeItemWithCorrectResourceClass()
237237
{
238238
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
239239
//Dummy is the correct class for the denormalization context serialization groups, and we're fetching RelatedDummy
240-
$resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata());
240+
$resourceMetadataFactoryProphecy->create(RelatedDummy::class)->willReturn(new ResourceMetadata())->shouldBeCalled();
241+
$resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata())->shouldBeCalled();
241242
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
242243
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
243244

@@ -249,14 +250,15 @@ public function testDenormalizeItemWithCorrectResourceClass()
249250
$queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
250251
$queryBuilderProphecy->getEntityManager()->shouldBeCalled()->willReturn($emProphecy);
251252

252-
$eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal());
253+
$eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), 30, false);
253254
$eagerExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), RelatedDummy::class, ['id' => 1], 'item_operation', ['resource_class' => Dummy::class]);
254255
}
255256

256257
public function testDenormalizeItemWithExistingGroups()
257258
{
258259
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
259260
//groups exist from the context, we don't need to compute them again
261+
$resourceMetadataFactoryProphecy->create(RelatedDummy::class)->willReturn(new ResourceMetadata())->shouldBeCalled();
260262
$resourceMetadataFactoryProphecy->create(Dummy::class)->shouldNotBeCalled();
261263
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
262264
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
@@ -269,7 +271,7 @@ public function testDenormalizeItemWithExistingGroups()
269271
$queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
270272
$queryBuilderProphecy->getEntityManager()->shouldBeCalled()->willReturn($emProphecy);
271273

272-
$eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal());
274+
$eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), 30, false);
273275
$eagerExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), RelatedDummy::class, ['id' => 1], 'item_operation', ['groups' => 'some_groups']);
274276
}
275277

@@ -321,11 +323,11 @@ public function testMaxDepthReached()
321323
$queryBuilderProphecy->innerJoin(Argument::type('string'), Argument::type('string'))->shouldBeCalled();
322324
$queryBuilderProphecy->addSelect(Argument::type('string'))->shouldBeCalled();
323325

324-
$eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal());
326+
$eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), 30, false);
325327
$eagerExtensionTest->applyToCollection($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class);
326328
}
327329

328-
public function testFetchNotOnlyEager()
330+
public function testForceEager()
329331
{
330332
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
331333
$resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata());
@@ -362,9 +364,7 @@ public function testFetchNotOnlyEager()
362364

363365
$queryBuilderProphecy->getEntityManager()->shouldBeCalled(2)->willReturn($emProphecy->reveal());
364366

365-
$queryBuilder = $queryBuilderProphecy->reveal();
366-
367-
$orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), 30, false);
367+
$orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), 30, true);
368368
$orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, []);
369369
}
370370
}

tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ private function getContainerBuilderProphecy()
206206
'api_platform.version' => 'version',
207207
'api_platform.eager_loading.enabled' => Argument::type('bool'),
208208
'api_platform.eager_loading.max_joins' => 30,
209-
'api_platform.eager_loading.eager_only' => true,
209+
'api_platform.eager_loading.force_eager' => true,
210210
];
211211
foreach ($parameters as $key => $value) {
212212
$containerBuilderProphecy->setParameter($key, $value)->shouldBeCalled();

tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public function testDefaultConfig()
7474
'eager_loading' => [
7575
'enabled' => true,
7676
'max_joins' => 30,
77-
'eager_only' => true,
77+
'force_eager' => true,
7878
],
7979
'collection' => [
8080
'order' => null,

tests/Fixtures/TestBundle/Entity/CircularReference.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
*
2222
* @author Kévin Dunglas <[email protected]>
2323
*
24-
* @ApiResource(attributes={"normalization_context"={"groups": {"circular"}}})
24+
* @ApiResource(attributes={"normalization_context"={"groups": {"circular"}}, "force_eager"=false})
2525
* @ORM\Entity
2626
*/
2727
class CircularReference

0 commit comments

Comments
 (0)