vendor/doctrine/orm/src/Tools/ResolveTargetEntityListener.php line 61

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM\Tools;
  4. use Doctrine\Common\EventSubscriber;
  5. use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
  6. use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
  7. use Doctrine\ORM\Events;
  8. use Doctrine\ORM\Mapping\ClassMetadata;
  9. use function array_key_exists;
  10. use function array_replace_recursive;
  11. use function ltrim;
  12. /**
  13. * ResolveTargetEntityListener
  14. *
  15. * Mechanism to overwrite interfaces or classes specified as association
  16. * targets.
  17. *
  18. * @phpstan-import-type AssociationMapping from ClassMetadata
  19. */
  20. class ResolveTargetEntityListener implements EventSubscriber
  21. {
  22. /** @var mixed[][] indexed by original entity name */
  23. private $resolveTargetEntities = [];
  24. /**
  25. * {@inheritDoc}
  26. */
  27. public function getSubscribedEvents()
  28. {
  29. return [
  30. Events::loadClassMetadata,
  31. Events::onClassMetadataNotFound,
  32. ];
  33. }
  34. /**
  35. * Adds a target-entity class name to resolve to a new class name.
  36. *
  37. * @param string $originalEntity
  38. * @param string $newEntity
  39. * @phpstan-param array<string, mixed> $mapping
  40. *
  41. * @return void
  42. */
  43. public function addResolveTargetEntity($originalEntity, $newEntity, array $mapping)
  44. {
  45. $mapping['targetEntity'] = ltrim($newEntity, '\\');
  46. $this->resolveTargetEntities[ltrim($originalEntity, '\\')] = $mapping;
  47. }
  48. /**
  49. * @internal this is an event callback, and should not be called directly
  50. *
  51. * @return void
  52. */
  53. public function onClassMetadataNotFound(OnClassMetadataNotFoundEventArgs $args)
  54. {
  55. if (array_key_exists($args->getClassName(), $this->resolveTargetEntities)) {
  56. $args->setFoundMetadata(
  57. $args
  58. ->getObjectManager()
  59. ->getClassMetadata($this->resolveTargetEntities[$args->getClassName()]['targetEntity'])
  60. );
  61. }
  62. }
  63. /**
  64. * Processes event and resolves new target entity names.
  65. *
  66. * @internal this is an event callback, and should not be called directly
  67. *
  68. * @return void
  69. */
  70. public function loadClassMetadata(LoadClassMetadataEventArgs $args)
  71. {
  72. $cm = $args->getClassMetadata();
  73. foreach ($cm->associationMappings as $mapping) {
  74. if (isset($this->resolveTargetEntities[$mapping['targetEntity']])) {
  75. $this->remapAssociation($cm, $mapping);
  76. }
  77. }
  78. foreach ($cm->discriminatorMap as $value => $class) {
  79. if (isset($this->resolveTargetEntities[$class])) {
  80. $cm->addDiscriminatorMapClass($value, $this->resolveTargetEntities[$class]['targetEntity']);
  81. }
  82. }
  83. }
  84. /** @param AssociationMapping $mapping */
  85. private function remapAssociation(ClassMetadata $classMetadata, array $mapping): void
  86. {
  87. $newMapping = $this->resolveTargetEntities[$mapping['targetEntity']];
  88. $newMapping = array_replace_recursive($mapping, $newMapping);
  89. $newMapping['fieldName'] = $mapping['fieldName'];
  90. unset($classMetadata->associationMappings[$mapping['fieldName']]);
  91. switch ($mapping['type']) {
  92. case ClassMetadata::MANY_TO_MANY:
  93. $classMetadata->mapManyToMany($newMapping);
  94. break;
  95. case ClassMetadata::MANY_TO_ONE:
  96. $classMetadata->mapManyToOne($newMapping);
  97. break;
  98. case ClassMetadata::ONE_TO_MANY:
  99. $classMetadata->mapOneToMany($newMapping);
  100. break;
  101. case ClassMetadata::ONE_TO_ONE:
  102. $classMetadata->mapOneToOne($newMapping);
  103. break;
  104. }
  105. }
  106. }