vendor/shopware/core/Framework/Validation/HappyPathValidator.php line 51

  1. <?php
  2. declare(strict_types=1);
  3. namespace Shopware\Core\Framework\Validation;
  4. use Shopware\Core\Framework\Log\Package;
  5. use Shopware\Core\Framework\Validation\Constraint\Uuid;
  6. use Symfony\Component\Validator\Constraint;
  7. use Symfony\Component\Validator\Constraints\Collection;
  8. use Symfony\Component\Validator\Constraints\GroupSequence;
  9. use Symfony\Component\Validator\Constraints\Length;
  10. use Symfony\Component\Validator\Constraints\NotBlank;
  11. use Symfony\Component\Validator\Constraints\NotNull;
  12. use Symfony\Component\Validator\Constraints\Optional;
  13. use Symfony\Component\Validator\Constraints\Range;
  14. use Symfony\Component\Validator\Constraints\Type;
  15. use Symfony\Component\Validator\ConstraintViolationList;
  16. use Symfony\Component\Validator\ConstraintViolationListInterface;
  17. use Symfony\Component\Validator\Context\ExecutionContextInterface;
  18. use Symfony\Component\Validator\Mapping\MetadataInterface;
  19. use Symfony\Component\Validator\Validator\ContextualValidatorInterface;
  20. use Symfony\Component\Validator\Validator\ValidatorInterface;
  21. #[Package('core
  22. calling into the validator machinery has a considerable overhead. Doing that thousands of time is notable.
  23. this validator implements a subset of the functionality and calls into the real validator if needed.')]
  24. class HappyPathValidator implements ValidatorInterface
  25. {
  26.     /**
  27.      * @internal
  28.      */
  29.     public function __construct(private readonly ValidatorInterface $inner)
  30.     {
  31.     }
  32.     /**
  33.      * @param Constraint|Constraint[]|null $constraints
  34.      */
  35.     public function validate(mixed $valueConstraint|array $constraints nullstring|GroupSequence|array $groups null): ConstraintViolationListInterface
  36.     {
  37.         if ($constraints === null) {
  38.             return $this->inner->validate($value$constraints$groups);
  39.         }
  40.         $constraints \is_array($constraints) ? $constraints : [$constraints];
  41.         foreach ($constraints as $constraint) {
  42.             // if one of our checks fails, we call the real validator
  43.             if (!$this->validateConstraint($value$constraint)) {
  44.                 return $this->inner->validate($value$constraints$groups);
  45.             }
  46.         }
  47.         return new ConstraintViolationList();
  48.     }
  49.     public function getMetadataFor(mixed $value): MetadataInterface
  50.     {
  51.         return $this->inner->getMetadataFor($value);
  52.     }
  53.     public function hasMetadataFor(mixed $value): bool
  54.     {
  55.         return $this->inner->hasMetadataFor($value);
  56.     }
  57.     public function validateProperty(object $objectstring $propertyNamestring|GroupSequence|array $groups null): ConstraintViolationListInterface
  58.     {
  59.         return $this->inner->validateProperty($object$propertyName$groups);
  60.     }
  61.     public function validatePropertyValue(object|string $objectOrClassstring $propertyNamemixed $valuestring|GroupSequence|array $groups null): ConstraintViolationListInterface
  62.     {
  63.         return $this->inner->validatePropertyValue($objectOrClass$propertyName$value$groups);
  64.     }
  65.     public function startContext(): ContextualValidatorInterface
  66.     {
  67.         return $this->inner->startContext();
  68.     }
  69.     public function inContext(ExecutionContextInterface $context): ContextualValidatorInterface
  70.     {
  71.         return $this->inner->inContext($context);
  72.     }
  73.     /**
  74.      * @param Constraint|Constraint[]|null $constraint
  75.      */
  76.     private function normalizeValueIfRequired(mixed $valueConstraint|array|null $constraint): mixed
  77.     {
  78.         if (!$constraint instanceof Constraint) {
  79.             return $value;
  80.         }
  81.         if (!\property_exists($constraint'normalizer')) {
  82.             return $value;
  83.         }
  84.         if (empty($constraint->normalizer)) {
  85.             return $value;
  86.         }
  87.         $normalizer $constraint->normalizer;
  88.         if (!\is_callable($normalizer)) {
  89.             return $value;
  90.         }
  91.         /** @var callable(mixed): mixed $normalizer */
  92.         return $normalizer($value);
  93.     }
  94.     /**
  95.      * @param Constraint|Constraint[]|null $constraint
  96.      */
  97.     private function validateConstraint(mixed $valueConstraint|array|null $constraint): bool
  98.     {
  99.         // apply defined normalizers to check $value from constraint if defined
  100.         $value $this->normalizeValueIfRequired(
  101.             $value,
  102.             $constraint
  103.         );
  104.         switch (true) {
  105.             case $constraint instanceof Uuid:
  106.                 if ($value !== null && \is_string($value) && !\Shopware\Core\Framework\Uuid\Uuid::isValid($value)) {
  107.                     return false;
  108.                 }
  109.                 break;
  110.             case $constraint instanceof NotBlank:
  111.                 if ($value === false || (empty($value) && $value !== '0')) {
  112.                     return false;
  113.                 }
  114.                 break;
  115.             case $constraint instanceof NotNull:
  116.                 if ($value === null) {
  117.                     return false;
  118.                 }
  119.                 break;
  120.             case $constraint instanceof Type:
  121.                 $types = (array) $constraint->type;
  122.                 foreach ($types as $type) {
  123.                     $type strtolower((string) $type);
  124.                     $type $type === 'boolean' 'bool' $type;
  125.                     $isFunction 'is_' $type;
  126.                     $ctypeFunction 'ctype_' $type;
  127.                     if (\function_exists($isFunction)) {
  128.                         if (\is_callable($isFunction) && !$isFunction($value)) { /* @phpstan-ignore-line */
  129.                             return false;
  130.                         }
  131.                     } elseif (\function_exists($ctypeFunction)) {
  132.                         if (\is_callable($ctypeFunction) && !$ctypeFunction($value)) { /* @phpstan-ignore-line */
  133.                             return false;
  134.                         }
  135.                     } elseif (!$value instanceof $type) {
  136.                         return false;
  137.                     }
  138.                 }
  139.                 break;
  140.             case $constraint instanceof Length:
  141.                 if (!\is_string($value)) {
  142.                     return false;
  143.                 }
  144.                 $length mb_strlen($value);
  145.                 if ($constraint->max !== null && $length $constraint->max) {
  146.                     return false;
  147.                 }
  148.                 if ($constraint->min !== null && $length $constraint->min) {
  149.                     return false;
  150.                 }
  151.                 break;
  152.             case $constraint instanceof Range:
  153.                 if (!is_numeric($value)) {
  154.                     return false;
  155.                 }
  156.                 if ($constraint->min === null && $constraint->max !== null) {
  157.                     if ($value $constraint->max) {
  158.                         return false;
  159.                     }
  160.                 } elseif ($constraint->min !== null && $constraint->max === null) {
  161.                     if ($value $constraint->min) {
  162.                         return false;
  163.                     }
  164.                 } elseif ($constraint->min !== null && $constraint->max !== null) {
  165.                     if ($value $constraint->min || $value $constraint->max) {
  166.                         return false;
  167.                     }
  168.                 }
  169.                 break;
  170.             case $constraint instanceof Collection:
  171.                 foreach ($constraint->fields as $field => $fieldConstraint) {
  172.                     \assert($fieldConstraint instanceof Constraint);
  173.                     // bug fix issue #2779
  174.                     $existsInArray \is_array($value) && \array_key_exists($field$value);
  175.                     $existsInArrayAccess $value instanceof \ArrayAccess && $value->offsetExists($field);
  176.                     if (($existsInArray || $existsInArrayAccess) && property_exists($fieldConstraint'constraints')) {
  177.                         if ((is_countable($fieldConstraint->constraints) ? \count($fieldConstraint->constraints) : 0) > 0) {
  178.                             /** @var array<mixed>|\ArrayAccess<string|int, mixed> $value */
  179.                             if (!$this->validateConstraint($value[$field], $fieldConstraint->constraints)) {
  180.                                 return false;
  181.                             }
  182.                         }
  183.                     } elseif (!$fieldConstraint instanceof Optional && !$constraint->allowMissingFields) {
  184.                         return false;
  185.                     }
  186.                     if (!$constraint->allowExtraFields && is_iterable($value)) {
  187.                         foreach ($value as $f => $_) {
  188.                             if (!isset($constraint->fields[$f])) {
  189.                                 return false;
  190.                             }
  191.                         }
  192.                     }
  193.                 }
  194.                 break;
  195.                 // unknown constraint
  196.             default:
  197.                 return false;
  198.         }
  199.         return true;
  200.     }
  201. }