diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.php index c397e73d4250..6eaafb704afd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.php @@ -19,6 +19,7 @@ use Symfony\Component\Validator\Constraints\NotCompromisedPasswordValidator; use Symfony\Component\Validator\Constraints\WhenValidator; use Symfony\Component\Validator\ContainerConstraintValidatorFactory; +use Symfony\Component\Validator\EventListener\RequestValidationSubscriber; use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use Symfony\Component\Validator\Validation; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -109,5 +110,12 @@ service('property_info'), ]) ->tag('validator.auto_mapper') + + ->set('validator.request_validator', RequestValidationSubscriber::class) + ->args([ + service('validator'), + service('serializer')->nullOnInvalid(), + ]) + ->tag('kernel.event_subscriber') ; }; diff --git a/src/Symfony/Component/Validator/Attribute/RequestValidator.php b/src/Symfony/Component/Validator/Attribute/RequestValidator.php new file mode 100644 index 000000000000..d94979286317 --- /dev/null +++ b/src/Symfony/Component/Validator/Attribute/RequestValidator.php @@ -0,0 +1,24 @@ + 'validateRequest' + ]; + } + + public function validateRequest(ControllerArgumentsEvent $event): void { + $controller = $event->getController(); + $arguments = $event->getArguments(); + $reflectionMethod = $this->getReflectionMethod($controller); + $request = $event->getRequest(); + + $attributes = $reflectionMethod->getAttributes(RequestValidator::class, \ReflectionAttribute::IS_INSTANCEOF); + + if (count($attributes) === 0) { + return; + } + + // only first attribute can validate + $attribute = $attributes[0]; + + $attributeArguments = $attribute->getArguments(); + if(key_exists('class', $attributeArguments)) { + $class = $attributeArguments['class']; + $override = key_exists('override', $attributeArguments) ? $attributeArguments['override'] : true; + $order = key_exists('order', $attributeArguments) ? $attributeArguments['order'] : [ + RequestValidator::ORDER_ATTRIBUTES, + RequestValidator::ORDER_QUERY, + RequestValidator::ORDER_REQUEST, + ]; + $serializedFormat = key_exists('serializedFormat', $attributeArguments) ? $attributeArguments['json'] : 'json'; + }else { + $class = $attributeArguments[0]; + $override = key_exists(1, $attributeArguments) ? $attributeArguments[1] : true; + $order = key_exists(2, $attributeArguments) ? $attributeArguments[2] : [ + RequestValidator::ORDER_ATTRIBUTES, + RequestValidator::ORDER_QUERY, + RequestValidator::ORDER_REQUEST, + ]; + $serializedFormat = key_exists(3, $attributeArguments) ? $attributeArguments[3] : 'json'; + } + + $object = new $class(); + + foreach ($order as $type) { + switch ($type) { + case RequestValidator::ORDER_SERIALIZE: + if(empty($request->getContent())) { + continue 2; + } + $serializer = $this->getSerializer(); + $serializer->deserialize($request->getContent(), $class, $serializedFormat, + [AbstractNormalizer::OBJECT_TO_POPULATE => $object]); + continue 2; + case RequestValidator::ORDER_REQUEST: + $this->setProperties($object, $request->request->all(), $override); + break; + case RequestValidator::ORDER_QUERY: + $this->setProperties($object, $request->query->all(), $override); + break; + case RequestValidator::ORDER_ATTRIBUTES: + $this->setProperties($object, $request->attributes->all(), $override); + break; + } + + } + + $violations = $this->validator->validate($object); + + if(count($violations) > 0) { + throw new ValidationFailedException(sprintf("Validation of %s failed!", $class), $violations); + } + + foreach ($arguments as $index => $argument) { + if(!$argument instanceof $class) { + continue; + } + $arguments[$index] = $object; + } + + $event->setArguments($arguments); + } + + private function setProperties(object $object, array $parameters, bool $override) { + foreach ($parameters as $key => $value) { + if(false === $override && property_exists($object, $key) && isset($object->{$key})) { + continue; + } + $object->{$key} = $value; + } + } + + private function getReflectionMethod(callable $controller): \ReflectionMethod + { + if (is_array($controller)) { + $class = $controller[0]; + $method = $controller[1]; + } else { + /** @var object $controller */ + $class = $controller; + $method = '__invoke'; + } + + return new \ReflectionMethod($class, $method); + } + + private function getSerializer(): SerializerInterface + { + if (!class_exists(Serializer::class)) { + throw new LogicException(sprintf('The "symfony/serializer" component is required to use the "%s" validator. Try running "composer require symfony/serializer".', + __CLASS__)); + } + + return $this->serializer; + } +}
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: