Skip to content

Commit 16dd932

Browse files
authored
Merge pull request #845 from meyerbaptiste/disable_eager_only_by_default
Force eager by default
2 parents d130edf + 8efed0b commit 16dd932

File tree

8 files changed

+75
-139
lines changed

8 files changed

+75
-139
lines changed

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

Lines changed: 35 additions & 25 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

@@ -154,28 +159,33 @@ private function joinRelations(QueryBuilder $queryBuilder, string $resourceClass
154159
$associationAlias = $relationAlias.$i++;
155160
$queryBuilder->{$method}($originAlias.'.'.$association, $associationAlias);
156161
++$joinCount;
157-
$select = [];
158-
$targetClassMetadata = $entityManager->getClassMetadata($mapping['targetEntity']);
159-
160-
foreach ($this->propertyNameCollectionFactory->create($mapping['targetEntity']) as $property) {
161-
$propertyMetadata = $this->propertyMetadataFactory->create($mapping['targetEntity'], $property, $propertyMetadataOptions);
162-
163-
if (true === $propertyMetadata->isIdentifier()) {
164-
$select[] = $property;
165-
continue;
166-
}
167162

168-
//the field test allows to add methods to a Resource which do not reflect real database fields
169-
if (true === $targetClassMetadata->hasField($property) && true === $propertyMetadata->isReadable()) {
170-
$select[] = $property;
171-
}
172-
}
163+
$relationAlias .= ++$j;
173164

174-
$queryBuilder->addSelect(sprintf('partial %s.{%s}', $associationAlias, implode(',', $select)));
165+
$this->joinRelations($queryBuilder, $mapping['targetEntity'], $forceEager, $propertyMetadataOptions, $associationAlias, $relationAlias, $method === 'leftJoin', $joinCount);
166+
}
167+
}
175168

176-
$relationAlias .= ++$j;
169+
/**
170+
* Does an operation force eager?
171+
*
172+
* @param string $resourceClass
173+
* @param array $options
174+
*
175+
* @return bool
176+
*/
177+
private function isForceEager(string $resourceClass, array $options): bool
178+
{
179+
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
177180

178-
$this->joinRelations($queryBuilder, $mapping['targetEntity'], $propertyMetadataOptions, $associationAlias, $relationAlias, $method === 'leftJoin', $joinCount);
181+
if (isset($options['collection_operation_name'])) {
182+
$forceEager = $resourceMetadata->getCollectionOperationAttribute($options['collection_operation_name'], 'force_eager', null, true);
183+
} elseif (isset($options['item_operation_name'])) {
184+
$forceEager = $resourceMetadata->getItemOperationAttribute($options['item_operation_name'], 'force_eager', null, true);
185+
} else {
186+
$forceEager = $resourceMetadata->getAttribute('force_eager');
179187
}
188+
189+
return is_bool($forceEager) ? $forceEager : $this->forceEager;
180190
}
181191
}

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" />

0 commit comments

Comments
 (0)