@@ -44,42 +44,28 @@ public function preload(
44
44
?int $ maxFetchJoinSameFieldCount = null ,
45
45
): array
46
46
{
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 );
65
48
66
49
if ($ sourceEntitiesCommonAncestor === null ) {
67
50
return [];
68
51
}
69
52
70
- /** @var ClassMetadata<E> $sourceClassMetadata */
71
53
$ sourceClassMetadata = $ this ->entityManager ->getClassMetadata ($ sourceEntitiesCommonAncestor );
72
54
$ associationMapping = $ sourceClassMetadata ->getAssociationMapping ($ sourcePropertyName );
55
+
73
56
/** @var ClassMetadata<E> $targetClassMetadata */
74
57
$ targetClassMetadata = $ this ->entityManager ->getClassMetadata ($ associationMapping ->targetEntity );
75
58
76
59
if ($ associationMapping ->isIndexed ()) {
77
60
throw new LogicException ('Preloading of indexed associations is not supported ' );
78
61
}
79
62
80
- $ maxFetchJoinSameFieldCount ??= 1 ;
63
+ if ($ associationMapping ->isOrdered ()) {
64
+ throw new LogicException ('Preloading of ordered associations is not supported ' );
65
+ }
81
66
82
- $ this ->loadProxies ($ sourceClassMetadata , $ sourceEntities , $ batchSize , $ maxFetchJoinSameFieldCount );
67
+ $ maxFetchJoinSameFieldCount ??= 1 ;
68
+ $ sourceEntities = $ this ->loadProxies ($ sourceClassMetadata , $ sourceEntities , $ batchSize ?? self ::BATCH_SIZE , $ maxFetchJoinSameFieldCount );
83
69
84
70
return match ($ associationMapping ->type ()) {
85
71
ClassMetadata::ONE_TO_MANY => $ this ->preloadOneToMany ($ sourceEntities , $ sourceClassMetadata , $ sourcePropertyName , $ targetClassMetadata , $ batchSize , $ maxFetchJoinSameFieldCount ),
@@ -90,38 +76,75 @@ public function preload(
90
76
}
91
77
92
78
/**
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
96
112
* @param non-negative-int $maxFetchJoinSameFieldCount
113
+ * @return list<T>
97
114
* @template T of E
98
115
*/
99
116
private function loadProxies (
100
- ClassMetadata $ sourceClassMetadata ,
101
- array $ sourceEntities ,
102
- ? int $ batchSize ,
117
+ ClassMetadata $ classMetadata ,
118
+ array $ entities ,
119
+ int $ batchSize ,
103
120
int $ maxFetchJoinSameFieldCount ,
104
- ): void
121
+ ): array
105
122
{
106
- $ sourceIdentifierReflection = $ sourceClassMetadata ->getSingleIdReflectionProperty ();
123
+ $ identifierReflection = $ classMetadata ->getSingleIdReflectionProperty (); // e.g. Order::$id reflection
124
+ $ identifierName = $ classMetadata ->getSingleIdentifierFieldName (); // e.g. 'id'
107
125
108
- if ($ sourceIdentifierReflection === null ) {
126
+ if ($ identifierReflection === null ) {
109
127
throw new LogicException ('Doctrine should use RuntimeReflectionService which never returns null. ' );
110
128
}
111
129
112
- $ proxyIds = [];
130
+ $ uniqueEntities = [];
131
+ $ uninitializedIds = [];
113
132
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 ;
117
140
}
118
141
}
119
142
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 );
124
145
}
146
+
147
+ return array_values ($ uniqueEntities );
125
148
}
126
149
127
150
/**
@@ -214,39 +237,23 @@ private function preloadToOne(
214
237
): array
215
238
{
216
239
$ sourcePropertyReflection = $ sourceClassMetadata ->getReflectionProperty ($ sourcePropertyName ); // e.g. Item::$order reflection
217
- $ targetIdentifierReflection = $ targetClassMetadata -> getSingleIdReflectionProperty (); // e.g. Order::$id reflection
240
+ $ targetEntities = [];
218
241
219
- if ($ sourcePropertyReflection === null || $ targetIdentifierReflection === null ) {
242
+ if ($ sourcePropertyReflection === null ) {
220
243
throw new LogicException ('Doctrine should use RuntimeReflectionService which never returns null. ' );
221
244
}
222
245
223
- $ targetIdentifierName = $ targetClassMetadata ->getSingleIdentifierFieldName (); // e.g. 'id'
224
-
225
- $ batchSize ??= self ::BATCH_SIZE ;
226
-
227
- $ targetEntities = [];
228
- $ uninitializedIds = [];
229
-
230
246
foreach ($ sourceEntities as $ sourceEntity ) {
231
247
$ targetEntity = $ sourcePropertyReflection ->getValue ($ sourceEntity );
232
248
233
249
if ($ targetEntity === null ) {
234
250
continue ;
235
251
}
236
252
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 ;
247
254
}
248
255
249
- return array_values ( $ targetEntities );
256
+ return $ this -> loadProxies ( $ targetClassMetadata , $ targetEntities, $ batchSize ?? self :: BATCH_SIZE , $ maxFetchJoinSameFieldCount );
250
257
}
251
258
252
259
/**
0 commit comments