DoctrineLoader.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Bridge\Doctrine\Validator;
  11. use Doctrine\ORM\EntityManagerInterface;
  12. use Doctrine\ORM\Mapping\ClassMetadataInfo;
  13. use Doctrine\ORM\Mapping\MappingException as OrmMappingException;
  14. use Doctrine\Persistence\Mapping\MappingException;
  15. use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
  16. use Symfony\Component\Validator\Constraints\Length;
  17. use Symfony\Component\Validator\Constraints\Valid;
  18. use Symfony\Component\Validator\Mapping\AutoMappingStrategy;
  19. use Symfony\Component\Validator\Mapping\ClassMetadata;
  20. use Symfony\Component\Validator\Mapping\Loader\AutoMappingTrait;
  21. use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
  22. /**
  23. * Guesses and loads the appropriate constraints using Doctrine's metadata.
  24. *
  25. * @author Kévin Dunglas <dunglas@gmail.com>
  26. */
  27. final class DoctrineLoader implements LoaderInterface
  28. {
  29. use AutoMappingTrait;
  30. private $entityManager;
  31. private $classValidatorRegexp;
  32. public function __construct(EntityManagerInterface $entityManager, string $classValidatorRegexp = null)
  33. {
  34. $this->entityManager = $entityManager;
  35. $this->classValidatorRegexp = $classValidatorRegexp;
  36. }
  37. /**
  38. * {@inheritdoc}
  39. */
  40. public function loadClassMetadata(ClassMetadata $metadata): bool
  41. {
  42. $className = $metadata->getClassName();
  43. try {
  44. $doctrineMetadata = $this->entityManager->getClassMetadata($className);
  45. } catch (MappingException | OrmMappingException $exception) {
  46. return false;
  47. }
  48. if (!$doctrineMetadata instanceof ClassMetadataInfo) {
  49. return false;
  50. }
  51. $loaded = false;
  52. $enabledForClass = $this->isAutoMappingEnabledForClass($metadata, $this->classValidatorRegexp);
  53. /* Available keys:
  54. - type
  55. - scale
  56. - length
  57. - unique
  58. - nullable
  59. - precision
  60. */
  61. $existingUniqueFields = $this->getExistingUniqueFields($metadata);
  62. // Type and nullable aren't handled here, use the PropertyInfo Loader instead.
  63. foreach ($doctrineMetadata->fieldMappings as $mapping) {
  64. $enabledForProperty = $enabledForClass;
  65. $lengthConstraint = null;
  66. foreach ($metadata->getPropertyMetadata($mapping['fieldName']) as $propertyMetadata) {
  67. // Enabling or disabling auto-mapping explicitly always takes precedence
  68. if (AutoMappingStrategy::DISABLED === $propertyMetadata->getAutoMappingStrategy()) {
  69. continue 2;
  70. }
  71. if (AutoMappingStrategy::ENABLED === $propertyMetadata->getAutoMappingStrategy()) {
  72. $enabledForProperty = true;
  73. }
  74. foreach ($propertyMetadata->getConstraints() as $constraint) {
  75. if ($constraint instanceof Length) {
  76. $lengthConstraint = $constraint;
  77. }
  78. }
  79. }
  80. if (!$enabledForProperty) {
  81. continue;
  82. }
  83. if (true === ($mapping['unique'] ?? false) && !isset($existingUniqueFields[$mapping['fieldName']])) {
  84. $metadata->addConstraint(new UniqueEntity(['fields' => $mapping['fieldName']]));
  85. $loaded = true;
  86. }
  87. if (null === ($mapping['length'] ?? null) || !\in_array($mapping['type'], ['string', 'text'], true)) {
  88. continue;
  89. }
  90. if (null === $lengthConstraint) {
  91. if (isset($mapping['originalClass']) && false === strpos($mapping['declaredField'], '.')) {
  92. $metadata->addPropertyConstraint($mapping['declaredField'], new Valid());
  93. $loaded = true;
  94. } elseif (property_exists($className, $mapping['fieldName'])) {
  95. $metadata->addPropertyConstraint($mapping['fieldName'], new Length(['max' => $mapping['length']]));
  96. $loaded = true;
  97. }
  98. } elseif (null === $lengthConstraint->max) {
  99. // If a Length constraint exists and no max length has been explicitly defined, set it
  100. $lengthConstraint->max = $mapping['length'];
  101. }
  102. }
  103. return $loaded;
  104. }
  105. private function getExistingUniqueFields(ClassMetadata $metadata): array
  106. {
  107. $fields = [];
  108. foreach ($metadata->getConstraints() as $constraint) {
  109. if (!$constraint instanceof UniqueEntity) {
  110. continue;
  111. }
  112. if (\is_string($constraint->fields)) {
  113. $fields[$constraint->fields] = true;
  114. } elseif (\is_array($constraint->fields) && 1 === \count($constraint->fields)) {
  115. $fields[$constraint->fields[0]] = true;
  116. }
  117. }
  118. return $fields;
  119. }
  120. }