@@ -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 /**
0 commit comments