Skip to content

Commit e78a8c6

Browse files
authored
Misc refactorings (#3)
* EntityPreloader: extract getCommonAncestor() * EntityPreloader: refactor loadProxies() to be usable in preloadToOne() * EntityPreloader: disallow ordered associations * QueryLogger: improve getAggregatedQueries()
1 parent b191caa commit e78a8c6

File tree

2 files changed

+80
-70
lines changed

2 files changed

+80
-70
lines changed

src/EntityPreloader.php

Lines changed: 65 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -44,42 +44,28 @@ public function preload(
4444
?int $maxFetchJoinSameFieldCount = null,
4545
): array
4646
{
47-
$sourceEntitiesCommonAncestor = null;
48-
49-
foreach ($sourceEntities as $sourceEntity) {
50-
$sourceEntityClassName = $sourceEntity::class;
51-
52-
if ($sourceEntitiesCommonAncestor === null) {
53-
$sourceEntitiesCommonAncestor = $sourceEntityClassName;
54-
continue;
55-
}
56-
57-
while (!is_a($sourceEntityClassName, $sourceEntitiesCommonAncestor, true)) {
58-
$sourceEntitiesCommonAncestor = get_parent_class($sourceEntitiesCommonAncestor);
59-
60-
if ($sourceEntitiesCommonAncestor === false) {
61-
throw new LogicException('Source entities must have a common ancestor');
62-
}
63-
}
64-
}
47+
$sourceEntitiesCommonAncestor = $this->getCommonAncestor($sourceEntities);
6548

6649
if ($sourceEntitiesCommonAncestor === null) {
6750
return [];
6851
}
6952

70-
/** @var ClassMetadata<E> $sourceClassMetadata */
7153
$sourceClassMetadata = $this->entityManager->getClassMetadata($sourceEntitiesCommonAncestor);
7254
$associationMapping = $sourceClassMetadata->getAssociationMapping($sourcePropertyName);
55+
7356
/** @var ClassMetadata<E> $targetClassMetadata */
7457
$targetClassMetadata = $this->entityManager->getClassMetadata($associationMapping->targetEntity);
7558

7659
if ($associationMapping->isIndexed()) {
7760
throw new LogicException('Preloading of indexed associations is not supported');
7861
}
7962

80-
$maxFetchJoinSameFieldCount ??= 1;
63+
if ($associationMapping->isOrdered()) {
64+
throw new LogicException('Preloading of ordered associations is not supported');
65+
}
8166

82-
$this->loadProxies($sourceClassMetadata, $sourceEntities, $batchSize, $maxFetchJoinSameFieldCount);
67+
$maxFetchJoinSameFieldCount ??= 1;
68+
$sourceEntities = $this->loadProxies($sourceClassMetadata, $sourceEntities, $batchSize ?? self::BATCH_SIZE, $maxFetchJoinSameFieldCount);
8369

8470
return match ($associationMapping->type()) {
8571
ClassMetadata::ONE_TO_MANY => $this->preloadOneToMany($sourceEntities, $sourceClassMetadata, $sourcePropertyName, $targetClassMetadata, $batchSize, $maxFetchJoinSameFieldCount),
@@ -90,38 +76,75 @@ public function preload(
9076
}
9177

9278
/**
93-
* @param ClassMetadata<T> $sourceClassMetadata
94-
* @param list<T> $sourceEntities
95-
* @param positive-int|null $batchSize
79+
* @param list<S> $entities
80+
* @return class-string<S>|null
81+
* @template S of E
82+
*/
83+
private function getCommonAncestor(array $entities): ?string
84+
{
85+
$commonAncestor = null;
86+
87+
foreach ($entities as $entity) {
88+
$entityClassName = $entity::class;
89+
90+
if ($commonAncestor === null) {
91+
$commonAncestor = $entityClassName;
92+
continue;
93+
}
94+
95+
while (!is_a($entityClassName, $commonAncestor, true)) {
96+
/** @var class-string<S>|false $commonAncestor */
97+
$commonAncestor = get_parent_class($commonAncestor);
98+
99+
if ($commonAncestor === false) {
100+
throw new LogicException('Given entities must have a common ancestor');
101+
}
102+
}
103+
}
104+
105+
return $commonAncestor;
106+
}
107+
108+
/**
109+
* @param ClassMetadata<T> $classMetadata
110+
* @param list<T> $entities
111+
* @param positive-int $batchSize
96112
* @param non-negative-int $maxFetchJoinSameFieldCount
113+
* @return list<T>
97114
* @template T of E
98115
*/
99116
private function loadProxies(
100-
ClassMetadata $sourceClassMetadata,
101-
array $sourceEntities,
102-
?int $batchSize,
117+
ClassMetadata $classMetadata,
118+
array $entities,
119+
int $batchSize,
103120
int $maxFetchJoinSameFieldCount,
104-
): void
121+
): array
105122
{
106-
$sourceIdentifierReflection = $sourceClassMetadata->getSingleIdReflectionProperty();
123+
$identifierReflection = $classMetadata->getSingleIdReflectionProperty(); // e.g. Order::$id reflection
124+
$identifierName = $classMetadata->getSingleIdentifierFieldName(); // e.g. 'id'
107125

108-
if ($sourceIdentifierReflection === null) {
126+
if ($identifierReflection === null) {
109127
throw new LogicException('Doctrine should use RuntimeReflectionService which never returns null.');
110128
}
111129

112-
$proxyIds = [];
130+
$uniqueEntities = [];
131+
$uninitializedIds = [];
113132

114-
foreach ($sourceEntities as $sourceEntity) {
115-
if ($sourceEntity instanceof Proxy && !$sourceEntity->__isInitialized()) {
116-
$proxyIds[] = $sourceIdentifierReflection->getValue($sourceEntity);
133+
foreach ($entities as $entity) {
134+
$entityId = $identifierReflection->getValue($entity);
135+
$entityKey = (string) $entityId;
136+
$uniqueEntities[$entityKey] = $entity;
137+
138+
if ($entity instanceof Proxy && !$entity->__isInitialized()) {
139+
$uninitializedIds[$entityKey] = $entityId;
117140
}
118141
}
119142

120-
$batchSize ??= self::PRELOAD_COLLECTION_DEFAULT_BATCH_SIZE;
121-
122-
foreach (array_chunk($proxyIds, $batchSize) as $idsChunk) {
123-
$this->loadEntitiesBy($sourceClassMetadata, $sourceIdentifierReflection->getName(), $idsChunk, $maxFetchJoinSameFieldCount);
143+
foreach (array_chunk($uninitializedIds, $batchSize) as $idsChunk) {
144+
$this->loadEntitiesBy($classMetadata, $identifierName, $idsChunk, $maxFetchJoinSameFieldCount);
124145
}
146+
147+
return array_values($uniqueEntities);
125148
}
126149

127150
/**
@@ -214,39 +237,23 @@ private function preloadToOne(
214237
): array
215238
{
216239
$sourcePropertyReflection = $sourceClassMetadata->getReflectionProperty($sourcePropertyName); // e.g. Item::$order reflection
217-
$targetIdentifierReflection = $targetClassMetadata->getSingleIdReflectionProperty(); // e.g. Order::$id reflection
240+
$targetEntities = [];
218241

219-
if ($sourcePropertyReflection === null || $targetIdentifierReflection === null) {
242+
if ($sourcePropertyReflection === null) {
220243
throw new LogicException('Doctrine should use RuntimeReflectionService which never returns null.');
221244
}
222245

223-
$targetIdentifierName = $targetClassMetadata->getSingleIdentifierFieldName(); // e.g. 'id'
224-
225-
$batchSize ??= self::BATCH_SIZE;
226-
227-
$targetEntities = [];
228-
$uninitializedIds = [];
229-
230246
foreach ($sourceEntities as $sourceEntity) {
231247
$targetEntity = $sourcePropertyReflection->getValue($sourceEntity);
232248

233249
if ($targetEntity === null) {
234250
continue;
235251
}
236252

237-
$targetEntityId = (string) $targetIdentifierReflection->getValue($targetEntity);
238-
$targetEntities[$targetEntityId] = $targetEntity;
239-
240-
if ($targetEntity instanceof Proxy && !$targetEntity->__isInitialized()) {
241-
$uninitializedIds[$targetEntityId] = true;
242-
}
243-
}
244-
245-
foreach (array_chunk(array_keys($uninitializedIds), $batchSize) as $idsChunk) {
246-
$this->loadEntitiesBy($targetClassMetadata, $targetIdentifierName, $idsChunk, $maxFetchJoinSameFieldCount);
253+
$targetEntities[] = $targetEntity;
247254
}
248255

249-
return array_values($targetEntities);
256+
return $this->loadProxies($targetClassMetadata, $targetEntities, $batchSize ?? self::BATCH_SIZE, $maxFetchJoinSameFieldCount);
250257
}
251258

252259
/**

tests/Lib/QueryLogger.php

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,29 +84,32 @@ static function (string $query) use (
8484
/**
8585
* @return list<array{count: int, query: string}>
8686
*/
87-
public function getAggregatedQueries(): array
87+
public function getAggregatedQueries(
88+
bool $omitSelectedColumns = true,
89+
bool $omitDiscriminatorConditions = true,
90+
bool $multiline = false,
91+
): array
8892
{
89-
$queries = $this->getQueries();
93+
$queries = $this->getQueries(
94+
omitSelectedColumns: $omitSelectedColumns,
95+
omitDiscriminatorConditions: $omitDiscriminatorConditions,
96+
multiline: $multiline,
97+
);
9098

9199
$aggregatedQueries = [];
92100

93101
foreach ($queries as $query) {
94-
$found = false;
95-
96102
foreach ($aggregatedQueries as &$aggregatedQuery) {
97103
if ($aggregatedQuery['query'] === $query) {
98104
$aggregatedQuery['count']++;
99-
$found = true;
100-
break;
105+
continue 2;
101106
}
102107
}
103108

104-
if (!$found) {
105-
$aggregatedQueries[] = [
106-
'count' => 1,
107-
'query' => $query,
108-
];
109-
}
109+
$aggregatedQueries[] = [
110+
'count' => 1,
111+
'query' => $query,
112+
];
110113
}
111114

112115
return $aggregatedQueries;

0 commit comments

Comments
 (0)