PropertyInfoLoader.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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\Component\Validator\Mapping\Loader;
  11. use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
  12. use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
  13. use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
  14. use Symfony\Component\PropertyInfo\Type as PropertyInfoType;
  15. use Symfony\Component\Validator\Constraints\All;
  16. use Symfony\Component\Validator\Constraints\NotBlank;
  17. use Symfony\Component\Validator\Constraints\NotNull;
  18. use Symfony\Component\Validator\Constraints\Type;
  19. use Symfony\Component\Validator\Mapping\AutoMappingStrategy;
  20. use Symfony\Component\Validator\Mapping\ClassMetadata;
  21. /**
  22. * Guesses and loads the appropriate constraints using PropertyInfo.
  23. *
  24. * @author Kévin Dunglas <dunglas@gmail.com>
  25. */
  26. final class PropertyInfoLoader implements LoaderInterface
  27. {
  28. use AutoMappingTrait;
  29. private $listExtractor;
  30. private $typeExtractor;
  31. private $accessExtractor;
  32. private $classValidatorRegexp;
  33. public function __construct(PropertyListExtractorInterface $listExtractor, PropertyTypeExtractorInterface $typeExtractor, PropertyAccessExtractorInterface $accessExtractor, string $classValidatorRegexp = null)
  34. {
  35. $this->listExtractor = $listExtractor;
  36. $this->typeExtractor = $typeExtractor;
  37. $this->accessExtractor = $accessExtractor;
  38. $this->classValidatorRegexp = $classValidatorRegexp;
  39. }
  40. /**
  41. * {@inheritdoc}
  42. */
  43. public function loadClassMetadata(ClassMetadata $metadata): bool
  44. {
  45. $className = $metadata->getClassName();
  46. if (!$properties = $this->listExtractor->getProperties($className)) {
  47. return false;
  48. }
  49. $loaded = false;
  50. $enabledForClass = $this->isAutoMappingEnabledForClass($metadata, $this->classValidatorRegexp);
  51. foreach ($properties as $property) {
  52. if (false === $this->accessExtractor->isWritable($className, $property)) {
  53. continue;
  54. }
  55. if (!property_exists($className, $property)) {
  56. continue;
  57. }
  58. $types = $this->typeExtractor->getTypes($className, $property);
  59. if (null === $types) {
  60. continue;
  61. }
  62. $enabledForProperty = $enabledForClass;
  63. $hasTypeConstraint = false;
  64. $hasNotNullConstraint = false;
  65. $hasNotBlankConstraint = false;
  66. $allConstraint = null;
  67. foreach ($metadata->getPropertyMetadata($property) as $propertyMetadata) {
  68. // Enabling or disabling auto-mapping explicitly always takes precedence
  69. if (AutoMappingStrategy::DISABLED === $propertyMetadata->getAutoMappingStrategy()) {
  70. continue 2;
  71. }
  72. if (AutoMappingStrategy::ENABLED === $propertyMetadata->getAutoMappingStrategy()) {
  73. $enabledForProperty = true;
  74. }
  75. foreach ($propertyMetadata->getConstraints() as $constraint) {
  76. if ($constraint instanceof Type) {
  77. $hasTypeConstraint = true;
  78. } elseif ($constraint instanceof NotNull) {
  79. $hasNotNullConstraint = true;
  80. } elseif ($constraint instanceof NotBlank) {
  81. $hasNotBlankConstraint = true;
  82. } elseif ($constraint instanceof All) {
  83. $allConstraint = $constraint;
  84. }
  85. }
  86. }
  87. if (!$enabledForProperty) {
  88. continue;
  89. }
  90. $loaded = true;
  91. $builtinTypes = [];
  92. $nullable = false;
  93. $scalar = true;
  94. foreach ($types as $type) {
  95. $builtinTypes[] = $type->getBuiltinType();
  96. if ($scalar && !\in_array($type->getBuiltinType(), [PropertyInfoType::BUILTIN_TYPE_INT, PropertyInfoType::BUILTIN_TYPE_FLOAT, PropertyInfoType::BUILTIN_TYPE_STRING, PropertyInfoType::BUILTIN_TYPE_BOOL], true)) {
  97. $scalar = false;
  98. }
  99. if (!$nullable && $type->isNullable()) {
  100. $nullable = true;
  101. }
  102. }
  103. if (!$hasTypeConstraint) {
  104. if (1 === \count($builtinTypes)) {
  105. if ($types[0]->isCollection() && (null !== $collectionValueType = $types[0]->getCollectionValueType())) {
  106. $this->handleAllConstraint($property, $allConstraint, $collectionValueType, $metadata);
  107. }
  108. $metadata->addPropertyConstraint($property, $this->getTypeConstraint($builtinTypes[0], $types[0]));
  109. } elseif ($scalar) {
  110. $metadata->addPropertyConstraint($property, new Type(['type' => 'scalar']));
  111. }
  112. }
  113. if (!$nullable && !$hasNotBlankConstraint && !$hasNotNullConstraint) {
  114. $metadata->addPropertyConstraint($property, new NotNull());
  115. }
  116. }
  117. return $loaded;
  118. }
  119. private function getTypeConstraint(string $builtinType, PropertyInfoType $type): Type
  120. {
  121. if (PropertyInfoType::BUILTIN_TYPE_OBJECT === $builtinType && null !== $className = $type->getClassName()) {
  122. return new Type(['type' => $className]);
  123. }
  124. return new Type(['type' => $builtinType]);
  125. }
  126. private function handleAllConstraint(string $property, ?All $allConstraint, PropertyInfoType $propertyInfoType, ClassMetadata $metadata)
  127. {
  128. $containsTypeConstraint = false;
  129. $containsNotNullConstraint = false;
  130. if (null !== $allConstraint) {
  131. foreach ($allConstraint->constraints as $constraint) {
  132. if ($constraint instanceof Type) {
  133. $containsTypeConstraint = true;
  134. } elseif ($constraint instanceof NotNull) {
  135. $containsNotNullConstraint = true;
  136. }
  137. }
  138. }
  139. $constraints = [];
  140. if (!$containsNotNullConstraint && !$propertyInfoType->isNullable()) {
  141. $constraints[] = new NotNull();
  142. }
  143. if (!$containsTypeConstraint) {
  144. $constraints[] = $this->getTypeConstraint($propertyInfoType->getBuiltinType(), $propertyInfoType);
  145. }
  146. if (null === $allConstraint) {
  147. $metadata->addPropertyConstraint($property, new All(['constraints' => $constraints]));
  148. } else {
  149. $allConstraint->constraints = array_merge($allConstraint->constraints, $constraints);
  150. }
  151. }
  152. }