1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Doctrine\ORM\Mapping; |
||
6 | |||
7 | use Doctrine\Common\EventManager; |
||
8 | use Doctrine\DBAL\Platforms; |
||
9 | use Doctrine\DBAL\Platforms\AbstractPlatform; |
||
10 | use Doctrine\ORM\EntityManagerInterface; |
||
11 | use Doctrine\ORM\Event\LoadClassMetadataEventArgs; |
||
12 | use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs; |
||
13 | use Doctrine\ORM\Events; |
||
14 | use Doctrine\ORM\Exception\ORMException; |
||
15 | use Doctrine\ORM\Mapping\Exception\InvalidCustomGenerator; |
||
16 | use Doctrine\ORM\Mapping\Exception\TableGeneratorNotImplementedYet; |
||
17 | use Doctrine\ORM\Mapping\Exception\UnknownGeneratorType; |
||
18 | use Doctrine\ORM\Sequencing; |
||
19 | use Doctrine\ORM\Sequencing\Planning\AssociationValueGeneratorExecutor; |
||
20 | use Doctrine\ORM\Sequencing\Planning\ColumnValueGeneratorExecutor; |
||
21 | use Doctrine\ORM\Sequencing\Planning\CompositeValueGenerationPlan; |
||
22 | use Doctrine\ORM\Sequencing\Planning\NoopValueGenerationPlan; |
||
23 | use Doctrine\ORM\Sequencing\Planning\SingleValueGenerationPlan; |
||
24 | use Doctrine\ORM\Sequencing\Planning\ValueGenerationExecutor; |
||
25 | use InvalidArgumentException; |
||
26 | use ReflectionException; |
||
27 | use function array_map; |
||
28 | use function class_exists; |
||
29 | use function count; |
||
30 | use function sprintf; |
||
31 | |||
32 | /** |
||
33 | * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the |
||
34 | * metadata mapping information of a class which describes how a class should be mapped |
||
35 | * to a relational database. |
||
36 | */ |
||
37 | class ClassMetadataFactory extends AbstractClassMetadataFactory |
||
38 | { |
||
39 | /** @var EntityManagerInterface|null */ |
||
40 | private $em; |
||
41 | |||
42 | /** @var AbstractPlatform */ |
||
43 | private $targetPlatform; |
||
44 | |||
45 | /** @var Driver\MappingDriver */ |
||
46 | private $driver; |
||
47 | |||
48 | /** @var EventManager */ |
||
49 | private $evm; |
||
50 | |||
51 | /** |
||
52 | * {@inheritdoc} |
||
53 | */ |
||
54 | 390 | protected function loadMetadata(string $name, ClassMetadataBuildingContext $metadataBuildingContext) : array |
|
55 | { |
||
56 | 390 | $loaded = parent::loadMetadata($name, $metadataBuildingContext); |
|
57 | |||
58 | 365 | array_map([$this, 'resolveDiscriminatorValue'], $loaded); |
|
59 | |||
60 | 365 | return $loaded; |
|
61 | } |
||
62 | |||
63 | 2280 | public function setEntityManager(EntityManagerInterface $em) |
|
64 | { |
||
65 | 2280 | $this->em = $em; |
|
66 | 2280 | } |
|
67 | |||
68 | /** |
||
69 | * {@inheritdoc} |
||
70 | * |
||
71 | * @throws ORMException |
||
72 | */ |
||
73 | 456 | protected function initialize() : void |
|
74 | { |
||
75 | 456 | $this->driver = $this->em->getConfiguration()->getMetadataDriverImpl(); |
|
76 | 456 | $this->evm = $this->em->getEventManager(); |
|
77 | 456 | $this->initialized = true; |
|
78 | 456 | } |
|
79 | |||
80 | /** |
||
81 | * {@inheritdoc} |
||
82 | */ |
||
83 | 12 | protected function onNotFoundMetadata( |
|
84 | string $className, |
||
85 | ClassMetadataBuildingContext $metadataBuildingContext |
||
86 | ) : ?ClassMetadata { |
||
87 | 12 | if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) { |
|
88 | 10 | return null; |
|
89 | } |
||
90 | |||
91 | 2 | $eventArgs = new OnClassMetadataNotFoundEventArgs($className, $metadataBuildingContext, $this->em); |
|
92 | |||
93 | 2 | $this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs); |
|
94 | |||
95 | 2 | return $eventArgs->getFoundMetadata(); |
|
96 | } |
||
97 | |||
98 | /** |
||
99 | * {@inheritdoc} |
||
100 | * |
||
101 | * @throws MappingException |
||
102 | * @throws ORMException |
||
103 | */ |
||
104 | 378 | protected function doLoadMetadata( |
|
105 | string $className, |
||
106 | ?ClassMetadata $parent, |
||
107 | ClassMetadataBuildingContext $metadataBuildingContext |
||
108 | ) : ClassMetadata { |
||
109 | 378 | $classMetadata = new ClassMetadata($className, $metadataBuildingContext); |
|
110 | |||
111 | 378 | if ($parent) { |
|
112 | 97 | $classMetadata->setParent($parent); |
|
113 | |||
114 | 97 | foreach ($parent->getDeclaredPropertiesIterator() as $fieldName => $property) { |
|
115 | 96 | $classMetadata->addInheritedProperty($property); |
|
116 | } |
||
117 | |||
118 | 97 | $classMetadata->setInheritanceType($parent->inheritanceType); |
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
119 | 97 | $classMetadata->setIdentifier($parent->identifier); |
|
120 | |||
121 | 97 | if ($parent->discriminatorColumn) { |
|
122 | 71 | $classMetadata->setDiscriminatorColumn($parent->discriminatorColumn); |
|
123 | 71 | $classMetadata->setDiscriminatorMap($parent->discriminatorMap); |
|
124 | } |
||
125 | |||
126 | 97 | $classMetadata->setLifecycleCallbacks($parent->lifecycleCallbacks); |
|
127 | 97 | $classMetadata->setChangeTrackingPolicy($parent->changeTrackingPolicy); |
|
128 | |||
129 | 97 | if ($parent->isMappedSuperclass) { |
|
130 | 27 | $classMetadata->setCustomRepositoryClassName($parent->getCustomRepositoryClassName()); |
|
131 | } |
||
132 | } |
||
133 | |||
134 | // Invoke driver |
||
135 | try { |
||
136 | 378 | $this->driver->loadMetadataForClass($classMetadata->getClassName(), $classMetadata, $metadataBuildingContext); |
|
137 | 6 | } catch (ReflectionException $e) { |
|
138 | throw MappingException::reflectionFailure($classMetadata->getClassName(), $e); |
||
139 | } |
||
140 | |||
141 | 372 | $this->completeIdentifierGeneratorMappings($classMetadata); |
|
142 | |||
143 | 372 | if ($parent) { |
|
144 | 97 | if ($parent->getCache()) { |
|
145 | 3 | $classMetadata->setCache(clone $parent->getCache()); |
|
146 | } |
||
147 | |||
148 | 97 | if (! empty($parent->entityListeners) && empty($classMetadata->entityListeners)) { |
|
149 | 7 | $classMetadata->entityListeners = $parent->entityListeners; |
|
150 | } |
||
151 | } |
||
152 | |||
153 | 372 | $this->completeRuntimeMetadata($classMetadata, $parent); |
|
154 | |||
155 | 372 | if ($this->evm->hasListeners(Events::loadClassMetadata)) { |
|
156 | 6 | $eventArgs = new LoadClassMetadataEventArgs($classMetadata, $this->em); |
|
157 | |||
158 | 6 | $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs); |
|
159 | } |
||
160 | |||
161 | 371 | $this->buildValueGenerationPlan($classMetadata); |
|
162 | 371 | $this->validateRuntimeMetadata($classMetadata, $parent); |
|
163 | |||
164 | 366 | return $classMetadata; |
|
165 | } |
||
166 | |||
167 | 372 | protected function completeRuntimeMetadata(ClassMetadata $class, ?ClassMetadata $parent = null) : void |
|
168 | { |
||
169 | 372 | if (! $parent || ! $parent->isMappedSuperclass) { |
|
170 | 372 | return; |
|
171 | } |
||
172 | |||
173 | 27 | if ($class->isMappedSuperclass) { |
|
174 | 1 | return; |
|
175 | } |
||
176 | |||
177 | 27 | $tableName = $class->getTableName(); |
|
178 | |||
179 | // Resolve column table names |
||
180 | 27 | foreach ($class->getDeclaredPropertiesIterator() as $property) { |
|
181 | 27 | if ($property instanceof FieldMetadata) { |
|
182 | 27 | $property->setTableName($property->getTableName() ?? $tableName); |
|
183 | |||
184 | 27 | continue; |
|
185 | } |
||
186 | |||
187 | 12 | if (! ($property instanceof ToOneAssociationMetadata)) { |
|
188 | 12 | continue; |
|
189 | } |
||
190 | |||
191 | // Resolve association join column table names |
||
192 | 9 | foreach ($property->getJoinColumns() as $joinColumn) { |
|
193 | /** @var JoinColumnMetadata $joinColumn */ |
||
194 | 9 | $joinColumn->setTableName($joinColumn->getTableName() ?? $tableName); |
|
195 | } |
||
196 | } |
||
197 | 27 | } |
|
198 | |||
199 | /** |
||
200 | * Validate runtime metadata is correctly defined. |
||
201 | * |
||
202 | * @throws MappingException |
||
203 | */ |
||
204 | 371 | protected function validateRuntimeMetadata(ClassMetadata $class, ?ClassMetadata $parent = null) : void |
|
205 | { |
||
206 | 371 | if (! $class->getReflectionClass()) { |
|
207 | // only validate if there is a reflection class instance |
||
208 | return; |
||
209 | } |
||
210 | |||
211 | 371 | $class->validateIdentifier(); |
|
212 | 369 | $class->validateAssociations(); |
|
213 | 369 | $class->validateLifecycleCallbacks($this->getReflectionService()); |
|
214 | |||
215 | // verify inheritance |
||
216 | 369 | if (! $class->isMappedSuperclass && $class->inheritanceType !== InheritanceType::NONE) { |
|
217 | 77 | if (! $parent) { |
|
218 | 76 | if (! $class->discriminatorMap) { |
|
219 | 3 | throw MappingException::missingDiscriminatorMap($class->getClassName()); |
|
220 | } |
||
221 | |||
222 | 73 | if (! $class->discriminatorColumn) { |
|
223 | 74 | throw MappingException::missingDiscriminatorColumn($class->getClassName()); |
|
224 | } |
||
225 | } |
||
226 | 330 | } elseif (($class->discriminatorMap || $class->discriminatorColumn) && $class->isMappedSuperclass && $class->isRootEntity()) { |
|
227 | // second condition is necessary for mapped superclasses in the middle of an inheritance hierarchy |
||
228 | throw MappingException::noInheritanceOnMappedSuperClass($class->getClassName()); |
||
229 | } |
||
230 | 366 | } |
|
231 | |||
232 | /** |
||
233 | * {@inheritdoc} |
||
234 | */ |
||
235 | 1968 | protected function newClassMetadataBuildingContext() : ClassMetadataBuildingContext |
|
236 | { |
||
237 | 1968 | return new ClassMetadataBuildingContext( |
|
238 | 1968 | $this, |
|
239 | 1968 | $this->getReflectionService(), |
|
240 | 1968 | $this->em->getConfiguration()->getNamingStrategy() |
|
241 | ); |
||
242 | } |
||
243 | |||
244 | /** |
||
245 | * Populates the discriminator value of the given metadata (if not set) by iterating over discriminator |
||
246 | * map classes and looking for a fitting one. |
||
247 | * |
||
248 | * @throws InvalidArgumentException |
||
249 | * @throws ReflectionException |
||
250 | * @throws MappingException |
||
251 | */ |
||
252 | 365 | private function resolveDiscriminatorValue(ClassMetadata $metadata) : void |
|
253 | { |
||
254 | 365 | if ($metadata->discriminatorValue || ! $metadata->discriminatorMap || $metadata->isMappedSuperclass || |
|
255 | 365 | ! $metadata->getReflectionClass() || $metadata->getReflectionClass()->isAbstract()) { |
|
256 | 365 | return; |
|
257 | } |
||
258 | |||
259 | // minor optimization: avoid loading related metadata when not needed |
||
260 | 4 | foreach ($metadata->discriminatorMap as $discriminatorValue => $discriminatorClass) { |
|
261 | 4 | if ($discriminatorClass === $metadata->getClassName()) { |
|
262 | 3 | $metadata->discriminatorValue = $discriminatorValue; |
|
263 | |||
264 | 3 | return; |
|
265 | } |
||
266 | } |
||
267 | |||
268 | // iterate over discriminator mappings and resolve actual referenced classes according to existing metadata |
||
269 | 1 | foreach ($metadata->discriminatorMap as $discriminatorValue => $discriminatorClass) { |
|
270 | 1 | if ($metadata->getClassName() === $this->getMetadataFor($discriminatorClass)->getClassName()) { |
|
271 | $metadata->discriminatorValue = $discriminatorValue; |
||
272 | |||
273 | return; |
||
274 | } |
||
275 | } |
||
276 | |||
277 | 1 | throw MappingException::mappedClassNotPartOfDiscriminatorMap($metadata->getClassName(), $metadata->getRootClassName()); |
|
278 | } |
||
279 | |||
280 | /** |
||
281 | * Completes the ID generator mapping. If "auto" is specified we choose the generator |
||
282 | * most appropriate for the targeted database platform. |
||
283 | * |
||
284 | * @throws ORMException |
||
285 | */ |
||
286 | 372 | private function completeIdentifierGeneratorMappings(ClassMetadata $class) : void |
|
287 | { |
||
288 | 372 | foreach ($class->getDeclaredPropertiesIterator() as $property) { |
|
289 | 370 | if (! $property instanceof FieldMetadata /*&& ! $property instanceof AssocationMetadata*/) { |
|
290 | 253 | continue; |
|
291 | } |
||
292 | |||
293 | 368 | $this->completeFieldIdentifierGeneratorMapping($property); |
|
294 | } |
||
295 | 372 | } |
|
296 | |||
297 | 368 | private function completeFieldIdentifierGeneratorMapping(FieldMetadata $field) |
|
298 | { |
||
299 | 368 | if (! $field->hasValueGenerator()) { |
|
300 | 283 | return; |
|
301 | } |
||
302 | |||
303 | 295 | $platform = $this->getTargetPlatform(); |
|
304 | 295 | $class = $field->getDeclaringClass(); |
|
305 | 295 | $generator = $field->getValueGenerator(); |
|
306 | |||
307 | 295 | if ($generator->getType() === GeneratorType::AUTO) { |
|
308 | 285 | $generator = new ValueGeneratorMetadata( |
|
309 | 285 | $platform->prefersSequences() |
|
310 | ? GeneratorType::SEQUENCE |
||
311 | 285 | : ($platform->prefersIdentityColumns() |
|
312 | 285 | ? GeneratorType::IDENTITY |
|
313 | 285 | : GeneratorType::TABLE |
|
314 | ), |
||
315 | 285 | $field->getValueGenerator()->getDefinition() |
|
316 | ); |
||
317 | 285 | $field->setValueGenerator($generator); |
|
318 | } |
||
319 | |||
320 | // Validate generator definition and set defaults where needed |
||
321 | 295 | switch ($generator->getType()) { |
|
322 | 295 | case GeneratorType::SEQUENCE: |
|
323 | // If there is no sequence definition yet, create a default definition |
||
324 | 6 | if ($generator->getDefinition()) { |
|
325 | 6 | break; |
|
326 | } |
||
327 | |||
328 | // @todo guilhermeblanco Move sequence generation to DBAL |
||
329 | $sequencePrefix = $platform->getSequencePrefix($field->getTableName(), $field->getSchemaName()); |
||
330 | $idSequenceName = sprintf('%s_%s_seq', $sequencePrefix, $field->getColumnName()); |
||
331 | $sequenceName = $platform->fixSchemaElementName($idSequenceName); |
||
332 | |||
333 | $field->setValueGenerator( |
||
334 | new ValueGeneratorMetadata( |
||
335 | $generator->getType(), |
||
336 | [ |
||
337 | 'sequenceName' => $sequenceName, |
||
338 | 'allocationSize' => 1, |
||
339 | ] |
||
340 | ) |
||
341 | ); |
||
342 | |||
343 | break; |
||
344 | |||
345 | 289 | case GeneratorType::TABLE: |
|
346 | throw TableGeneratorNotImplementedYet::create(); |
||
347 | break; |
||
348 | |||
349 | 289 | case GeneratorType::CUSTOM: |
|
350 | 1 | $definition = $generator->getDefinition(); |
|
351 | 1 | if (! isset($definition['class'])) { |
|
352 | throw InvalidCustomGenerator::onClassNotConfigured(); |
||
353 | } |
||
354 | 1 | if (! class_exists($definition['class'])) { |
|
355 | throw InvalidCustomGenerator::onMissingClass($definition); |
||
356 | } |
||
357 | |||
358 | 1 | break; |
|
359 | |||
360 | 288 | case GeneratorType::IDENTITY: |
|
361 | case GeneratorType::NONE: |
||
362 | 288 | break; |
|
363 | |||
364 | default: |
||
365 | throw UnknownGeneratorType::create($generator->getType()); |
||
366 | } |
||
367 | 295 | } |
|
368 | |||
369 | /** |
||
370 | * {@inheritDoc} |
||
371 | */ |
||
372 | 199 | protected function getDriver() : Driver\MappingDriver |
|
373 | { |
||
374 | 199 | return $this->driver; |
|
375 | } |
||
376 | |||
377 | /** |
||
378 | * {@inheritDoc} |
||
379 | */ |
||
380 | protected function isEntity(ClassMetadata $class) : bool |
||
381 | { |
||
382 | return isset($class->isMappedSuperclass) && $class->isMappedSuperclass === false; |
||
383 | } |
||
384 | |||
385 | 295 | private function getTargetPlatform() : Platforms\AbstractPlatform |
|
386 | { |
||
387 | 295 | if (! $this->targetPlatform) { |
|
388 | 295 | $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform(); |
|
389 | } |
||
390 | |||
391 | 295 | return $this->targetPlatform; |
|
392 | } |
||
393 | |||
394 | 371 | private function buildValueGenerationPlan(ClassMetadata $class) : void |
|
395 | { |
||
396 | 371 | $executors = $this->buildValueGenerationExecutorList($class); |
|
397 | |||
398 | 371 | switch (count($executors)) { |
|
399 | 371 | case 0: |
|
400 | 92 | $class->setValueGenerationPlan(new NoopValueGenerationPlan()); |
|
401 | 92 | break; |
|
402 | |||
403 | 310 | case 1: |
|
404 | 305 | $class->setValueGenerationPlan(new SingleValueGenerationPlan($class, $executors[0])); |
|
405 | 305 | break; |
|
406 | |||
407 | default: |
||
408 | 18 | $class->setValueGenerationPlan(new CompositeValueGenerationPlan($class, $executors)); |
|
409 | 18 | break; |
|
410 | } |
||
411 | 371 | } |
|
412 | |||
413 | /** |
||
414 | * @return ValueGenerationExecutor[] |
||
415 | */ |
||
416 | 371 | private function buildValueGenerationExecutorList(ClassMetadata $class) : array |
|
417 | { |
||
418 | 371 | $executors = []; |
|
419 | |||
420 | 371 | foreach ($class->getDeclaredPropertiesIterator() as $property) { |
|
421 | 369 | $executor = $this->buildValueGenerationExecutorForProperty($class, $property); |
|
422 | |||
423 | 369 | if ($executor instanceof ValueGenerationExecutor) { |
|
424 | 310 | $executors[] = $executor; |
|
425 | } |
||
426 | } |
||
427 | |||
428 | 371 | return $executors; |
|
429 | } |
||
430 | |||
431 | 369 | private function buildValueGenerationExecutorForProperty( |
|
432 | ClassMetadata $class, |
||
433 | Property $property |
||
434 | ) : ?ValueGenerationExecutor { |
||
435 | 369 | if ($property instanceof LocalColumnMetadata && $property->hasValueGenerator()) { |
|
436 | 294 | return new ColumnValueGeneratorExecutor($property, $this->createPropertyValueGenerator($class, $property)); |
|
437 | } |
||
438 | |||
439 | 335 | if ($property instanceof ToOneAssociationMetadata && $property->isPrimaryKey()) { |
|
440 | 41 | return new AssociationValueGeneratorExecutor(); |
|
441 | } |
||
442 | |||
443 | 331 | return null; |
|
444 | } |
||
445 | |||
446 | 294 | private function createPropertyValueGenerator( |
|
447 | ClassMetadata $class, |
||
448 | LocalColumnMetadata $property |
||
449 | ) : Sequencing\Generator { |
||
450 | 294 | $platform = $this->getTargetPlatform(); |
|
451 | |||
452 | 294 | switch ($property->getValueGenerator()->getType()) { |
|
453 | 294 | case GeneratorType::IDENTITY: |
|
454 | 287 | $sequenceName = null; |
|
455 | |||
456 | // Platforms that do not have native IDENTITY support need a sequence to emulate this behaviour. |
||
457 | 287 | if ($platform->usesSequenceEmulatedIdentityColumns()) { |
|
458 | $sequencePrefix = $platform->getSequencePrefix($class->getTableName(), $class->getSchemaName()); |
||
459 | $idSequenceName = $platform->getIdentitySequenceName($sequencePrefix, $property->getColumnName()); |
||
460 | $sequenceName = $platform->quoteIdentifier($platform->fixSchemaElementName($idSequenceName)); |
||
461 | } |
||
462 | |||
463 | 287 | return $property->getTypeName() === 'bigint' |
|
464 | 1 | ? new Sequencing\BigIntegerIdentityGenerator($sequenceName) |
|
465 | 287 | : new Sequencing\IdentityGenerator($sequenceName); |
|
466 | 7 | case GeneratorType::SEQUENCE: |
|
467 | 6 | $definition = $property->getValueGenerator()->getDefinition(); |
|
468 | |||
469 | 6 | return new Sequencing\SequenceGenerator( |
|
470 | 6 | $platform->quoteIdentifier($definition['sequenceName']), |
|
471 | 6 | $definition['allocationSize'] |
|
472 | ); |
||
473 | 1 | case GeneratorType::CUSTOM: |
|
474 | 1 | $class = $property->getValueGenerator()->getDefinition()['class']; |
|
475 | |||
476 | 1 | return new $class(); |
|
0 ignored issues
–
show
In this branch, the function will implicitly return
null which is incompatible with the type-hinted return Doctrine\ORM\Sequencing\Generator . Consider adding a return statement or allowing null as return value.
For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example: interface ReturnsInt {
public function returnsIntHinted(): int;
}
class MyClass implements ReturnsInt {
public function returnsIntHinted(): int
{
if (foo()) {
return 123;
}
// here: null is implicitly returned
}
}
Loading history...
|
|||
477 | } |
||
478 | } |
||
479 | } |
||
480 |