Skip to content

Commit 752455d

Browse files
author
abluchet
committed
Disable partial fetch by default
1 parent fd88125 commit 752455d

File tree

8 files changed

+111
-26
lines changed

8 files changed

+111
-26
lines changed

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

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,14 @@ final class EagerLoadingExtension implements QueryCollectionExtensionInterface,
4444
private $serializerContextBuilder;
4545
private $requestStack;
4646

47-
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, int $maxJoins = 30, bool $forceEager = true, RequestStack $requestStack = null, SerializerContextBuilderInterface $serializerContextBuilder = null)
47+
public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, int $maxJoins = 30, bool $forceEager = true, bool $fetchPartial = false, RequestStack $requestStack = null, SerializerContextBuilderInterface $serializerContextBuilder = null)
4848
{
4949
$this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
5050
$this->propertyMetadataFactory = $propertyMetadataFactory;
5151
$this->resourceMetadataFactory = $resourceMetadataFactory;
5252
$this->maxJoins = $maxJoins;
5353
$this->forceEager = $forceEager;
54+
$this->fetchPartial = $fetchPartial;
5455
$this->serializerContextBuilder = $serializerContextBuilder;
5556
$this->requestStack = $requestStack;
5657
}
@@ -67,10 +68,11 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator
6768
}
6869

6970
$forceEager = $this->shouldOperationForceEager($resourceClass, $options);
71+
$fetchPartial = $this->shouldOperationFetchPartial($resourceClass, $options);
7072

7173
$groups = $this->getSerializerGroups($resourceClass, $options, 'normalization_context');
7274

73-
$this->joinRelations($queryBuilder, $queryNameGenerator, $resourceClass, $forceEager, $queryBuilder->getRootAliases()[0], $groups);
75+
$this->joinRelations($queryBuilder, $queryNameGenerator, $resourceClass, $forceEager, $fetchPartial, $queryBuilder->getRootAliases()[0], $groups);
7476
}
7577

7678
/**
@@ -86,6 +88,7 @@ public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterf
8688
}
8789

8890
$forceEager = $this->shouldOperationForceEager($resourceClass, $options);
91+
$fetchPartial = $this->shouldOperationFetchPartial($resourceClass, $options);
8992

9093
if (isset($context['groups'])) {
9194
$groups = ['serializer_groups' => $context['groups']];
@@ -95,7 +98,7 @@ public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterf
9598
$groups = $this->getSerializerGroups($resourceClass, $options, 'normalization_context');
9699
}
97100

98-
$this->joinRelations($queryBuilder, $queryNameGenerator, $resourceClass, $forceEager, $queryBuilder->getRootAliases()[0], $groups);
101+
$this->joinRelations($queryBuilder, $queryNameGenerator, $resourceClass, $forceEager, $fetchPartial, $queryBuilder->getRootAliases()[0], $groups);
99102
}
100103

101104
/**
@@ -112,7 +115,7 @@ public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterf
112115
*
113116
* @throws RuntimeException when the max number of joins has been reached
114117
*/
115-
private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, bool $forceEager, string $parentAlias, array $propertyMetadataOptions = [], bool $wasLeftJoin = false, int &$joinCount = 0)
118+
private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, bool $forceEager, bool $fetchPartial, string $parentAlias, array $propertyMetadataOptions = [], bool $wasLeftJoin = false, int &$joinCount = 0)
116119
{
117120
if ($joinCount > $this->maxJoins) {
118121
throw new RuntimeException('The total number of joined relations has exceeded the specified maximum. Raise the limit if necessary.');
@@ -152,18 +155,23 @@ private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInt
152155
$queryBuilder->{$method}(sprintf('%s.%s', $parentAlias, $association), $associationAlias);
153156
++$joinCount;
154157

155-
try {
156-
$this->addSelect($queryBuilder, $mapping['targetEntity'], $associationAlias, $propertyMetadataOptions);
157-
} catch (ResourceClassNotFoundException $resourceClassNotFoundException) {
158-
continue;
158+
if (true === $fetchPartial) {
159+
try {
160+
$this->addSelect($queryBuilder, $mapping['targetEntity'], $associationAlias, $propertyMetadataOptions);
161+
} catch (ResourceClassNotFoundException $resourceClassNotFoundException) {
162+
continue;
163+
}
164+
} else {
165+
$queryBuilder->addSelect($associationAlias);
159166
}
160167

168+
// Avoid recursion
161169
if ($mapping['targetEntity'] === $resourceClass) {
162170
$queryBuilder->addSelect($associationAlias);
163171
continue;
164172
}
165173

166-
$this->joinRelations($queryBuilder, $queryNameGenerator, $mapping['targetEntity'], $forceEager, $associationAlias, $propertyMetadataOptions, $method === 'leftJoin', $joinCount);
174+
$this->joinRelations($queryBuilder, $queryNameGenerator, $mapping['targetEntity'], $forceEager, $fetchPartial, $associationAlias, $propertyMetadataOptions, $method === 'leftJoin', $joinCount);
167175
}
168176
}
169177

@@ -184,7 +192,7 @@ private function addSelect(QueryBuilder $queryBuilder, string $entity, string $a
184192
}
185193

186194
//the field test allows to add methods to a Resource which do not reflect real database fields
187-
if (true === $targetClassMetadata->hasField($property) && true === $propertyMetadata->isReadable()) {
195+
if (true === $targetClassMetadata->hasField($property) && (true === $propertyMetadata->getAttribute('fetchable') || true === $propertyMetadata->isReadable())) {
188196
$select[] = $property;
189197
}
190198
}

src/Bridge/Doctrine/Orm/Util/EagerLoadingTrait.php

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
trait EagerLoadingTrait
2525
{
2626
private $forceEager;
27+
private $fetchPartial;
2728
private $resourceMetadataFactory;
2829

2930
/**
@@ -35,18 +36,46 @@ trait EagerLoadingTrait
3536
* @return bool
3637
*/
3738
private function shouldOperationForceEager(string $resourceClass, array $options): bool
39+
{
40+
return $this->getBooleanOperationAttribute($resourceClass, $options, 'force_eager', $this->forceEager);
41+
}
42+
43+
/**
44+
* Checks if an operation has a `fetch_partial` attribute.
45+
*
46+
* @param string $resourceClass
47+
* @param array $options
48+
*
49+
* @return bool
50+
*/
51+
private function shouldOperationFetchPartial(string $resourceClass, array $options): bool
52+
{
53+
return $this->getBooleanOperationAttribute($resourceClass, $options, 'fetch_partial', $this->fetchPartial);
54+
}
55+
56+
/**
57+
* Get the boolean attribute of an operation or the resource metadata.
58+
*
59+
* @param string $resourceClass
60+
* @param array $options
61+
* @param string $attributeName
62+
* @param bool $default
63+
*
64+
* @return bool
65+
*/
66+
private function getBooleanOperationAttribute(string $resourceClass, array $options, string $attributeName, bool $default): bool
3867
{
3968
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
4069

4170
if (isset($options['collection_operation_name'])) {
42-
$forceEager = $resourceMetadata->getCollectionOperationAttribute($options['collection_operation_name'], 'force_eager', null, true);
71+
$attribute = $resourceMetadata->getCollectionOperationAttribute($options['collection_operation_name'], $attributeName, null, true);
4372
} elseif (isset($options['item_operation_name'])) {
44-
$forceEager = $resourceMetadata->getItemOperationAttribute($options['item_operation_name'], 'force_eager', null, true);
73+
$attribute = $resourceMetadata->getItemOperationAttribute($options['item_operation_name'], $attributeName, null, true);
4574
} else {
46-
$forceEager = $resourceMetadata->getAttribute('force_eager');
75+
$attribute = $resourceMetadata->getAttribute($attributeName);
4776
}
4877

49-
return is_bool($forceEager) ? $forceEager : $this->forceEager;
78+
return is_bool($attribute) ? $attribute : $default;
5079
}
5180

5281
/**

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ private function handleConfig(ContainerBuilder $container, array $config, array
115115
$container->setParameter('api_platform.error_formats', $errorFormats);
116116
$container->setParameter('api_platform.eager_loading.enabled', $config['eager_loading']['enabled']);
117117
$container->setParameter('api_platform.eager_loading.max_joins', $config['eager_loading']['max_joins']);
118+
$container->setParameter('api_platform.eager_loading.fetch_partial', $config['eager_loading']['fetch_partial']);
118119
$container->setParameter('api_platform.eager_loading.force_eager', $config['eager_loading']['force_eager']);
119120
$container->setParameter('api_platform.collection.order', $config['collection']['order']);
120121
$container->setParameter('api_platform.collection.order_parameter_name', $config['collection']['order_parameter_name']);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public function getConfigTreeBuilder()
5050
->addDefaultsIfNotSet()
5151
->children()
5252
->booleanNode('enabled')->defaultTrue()->info('To enable or disable eager loading')->end()
53+
->booleanNode('fetch_partial')->defaultFalse()->info('Fetch only partial data according to serialization groups')->end()
5354
->integerNode('max_joins')->defaultValue(30)->info('Max number of joined relations before EagerLoading throws a RuntimeException')->end()
5455
->booleanNode('force_eager')->defaultTrue()->info('Force join on every relation. If disabled, it will only join relations having the EAGER fetch mode.')->end()
5556
->end()

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
112112
<argument>%api_platform.eager_loading.max_joins%</argument>
113113
<argument>%api_platform.eager_loading.force_eager%</argument>
114+
<argument>%api_platform.eager_loading.fetch_partial%</argument>
114115
<argument type="service" id="request_stack" />
115116
<argument type="service" id="api_platform.serializer.context_builder" />
116117

0 commit comments

Comments
 (0)