diff --git a/CHANGELOG.md b/CHANGELOG.md
index 697ea73..9249430 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,15 +1,24 @@
# Changelog
+## 2.1.2
+
+Bug fix:
+ * The `@Assert\DateTime` and `@Assert\Date` will now transform the property type in `DateTime` if no type is set.
+ * You can now add a type to bypass the exception thrown by a `ParamConverter`. Check the [documentation](Doc/ParamConverter.md#bypass-paramconverter-exception-for-specific-classes).
+
+
## 2.1.1
Behaviour Change:
* The DTO handler can now also bind data from the cookie of the `Request`. It now uses the following priority: `Request > Attributes > Query > Cookies`.
+
## 2.1.0
New features:
* The DTO handler can now also bind data from the attributes and query of the `Request` object. It loads the content with the following priority: `Request > Attributes > Query`.
+
## 2.0.0
New features:
diff --git a/ConfigurationExtractor/PropertyConfigurationExtractor.php b/ConfigurationExtractor/PropertyConfigurationExtractor.php
index 44bfdf2..5c37633 100644
--- a/ConfigurationExtractor/PropertyConfigurationExtractor.php
+++ b/ConfigurationExtractor/PropertyConfigurationExtractor.php
@@ -16,6 +16,8 @@
use Doctrine\Common\Annotations\AnnotationReader;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\Validator\Constraints\All;
+use Symfony\Component\Validator\Constraints\Date;
+use Symfony\Component\Validator\Constraints\DateTime;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Constraints\Type;
@@ -76,6 +78,10 @@ public function __construct(\ReflectionProperty $property)
$arrayAnnotation = $annotationReader->getPropertyAnnotation($property, All::class);
/** @var Type $typeAnnotation */
$typeAnnotation = $annotationReader->getPropertyAnnotation($property, Type::class);
+ /** @var DateTime $dateTimeAnnotation */
+ $dateTimeAnnotation = $annotationReader->getPropertyAnnotation($property, DateTime::class);
+ /** @var Date $dateAnnotation */
+ $dateAnnotation = $annotationReader->getPropertyAnnotation($property, Date::class);
/** @var MapTo $mapToAnnotation */
$mapToAnnotation = $annotationReader->getPropertyAnnotation($property, MapTo::class);
/** @var NotNull $notNullAnnotation */
@@ -93,6 +99,10 @@ public function __construct(\ReflectionProperty $property)
$typeAnnotation = $this->findTypeConstraint($arrayAnnotation) ?? $typeAnnotation;
}
+ if ($dateTimeAnnotation !== null || $dateAnnotation !== null) {
+ $this->type = \DateTime::class;
+ }
+
if ($typeAnnotation !== null && \class_exists($typeAnnotation->type)) {
$this->type = $typeAnnotation->type;
}
diff --git a/DependencyInjection/ChapleanDtoHandlerExtension.php b/DependencyInjection/ChapleanDtoHandlerExtension.php
index a6be1fa..81cec4e 100644
--- a/DependencyInjection/ChapleanDtoHandlerExtension.php
+++ b/DependencyInjection/ChapleanDtoHandlerExtension.php
@@ -28,9 +28,30 @@ class ChapleanDtoHandlerExtension extends Extension
public function load(array $configs, ContainerBuilder $container): void
{
$configuration = new Configuration();
- $this->processConfiguration($configuration, $configs);
+ $config = $this->processConfiguration($configuration, $configs);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.xml');
+
+ $container->setParameter('chaplean_dto_handler', $config);
+ $this->setParameters($container, 'chaplean_dto_handler', $config);
+ }
+
+ /**
+ * @param ContainerBuilder $container
+ * @param string $name
+ * @param array $configs
+ *
+ * @return void
+ */
+ public function setParameters(ContainerBuilder $container, $name, array $configs): void
+ {
+ foreach ($configs as $key => $parameter) {
+ $container->setParameter($name . '.' . $key, $parameter);
+
+ if (is_array($parameter)) {
+ $this->setParameters($container, $name . '.' . $key, $parameter);
+ }
+ }
}
}
diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php
index 5165ca8..c64af53 100644
--- a/DependencyInjection/Configuration.php
+++ b/DependencyInjection/Configuration.php
@@ -25,7 +25,19 @@ final class Configuration implements ConfigurationInterface
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder();
- $treeBuilder->root('chaplean_dto_handler');
+ $rootNode = $treeBuilder->root('chaplean_dto_handler');
+
+ $rootNode
+ ->children()
+ ->arrayNode('bypass_param_converter_exception')
+ ->info('Bypass the ParamConverter exception for specified classes')
+ ->defaultValue([
+ \DateTime::class
+ ])
+ ->prototype('scalar')->end()
+ ->end()
+ ->end()
+ ->end();
return $treeBuilder;
}
diff --git a/Doc/ParamConverter.md b/Doc/ParamConverter.md
index 16e0010..36837ca 100644
--- a/Doc/ParamConverter.md
+++ b/Doc/ParamConverter.md
@@ -73,3 +73,16 @@ public function postAction(
// ...
}
```
+
+### Bypass `ParamConverter` exception for specific classes
+
+Some `ParamConverter` will throw an exception in case of a bad input. This is the case of the `DateTimeParamConverter` which will throw a 404 Not Found error if it fails to transform the input in date, when you give no value for instance. A 404 is not appropriated in most cases, especially if you have the `NotBlank` or `NotNull` assertion which will better handle the error.
+
+To bypass it, you can set the following options. This is the default value.
+
+```yaml
+chaplean_dto_handler:
+ bypass_param_converter_exception:
+ - 'DateTime'
+```
+
diff --git a/ParamConverter/DataTransferObjectParamConverter.php b/ParamConverter/DataTransferObjectParamConverter.php
index 1ec7734..77c6126 100644
--- a/ParamConverter/DataTransferObjectParamConverter.php
+++ b/ParamConverter/DataTransferObjectParamConverter.php
@@ -19,10 +19,9 @@
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterManager;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
-use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
@@ -37,13 +36,18 @@ class DataTransferObjectParamConverter implements ParamConverterInterface
/**
* @var array
*/
- protected $taggedDtoClasses;
+ protected $bypassParamConverterExceptionClasses;
/**
* @var ParamConverterManager
*/
protected $manager;
+ /**
+ * @var array
+ */
+ protected $taggedDtoClasses;
+
/**
* @var ValidatorInterface
*/
@@ -52,13 +56,16 @@ class DataTransferObjectParamConverter implements ParamConverterInterface
/**
* DataTransferObjectParamConverter constructor.
*
+ * @param ContainerInterface $container
* @param ParamConverterManager $paramConverterManager
* @param ValidatorInterface|null $validator
*/
public function __construct(
+ ContainerInterface $container,
ParamConverterManager $paramConverterManager,
ValidatorInterface $validator = null
) {
+ $this->bypassParamConverterExceptionClasses = $container->getParameter('chaplean_dto_handler.bypass_param_converter_exception') ?? [];
$this->manager = $paramConverterManager;
$this->validator = $validator;
$this->taggedDtoClasses = [];
@@ -245,7 +252,11 @@ protected function autoConfigureOne(
$config = new ParamConverter([]);
$config->setName($name);
$config->setClass($propertyConfigurationModel->getType());
- $config->setIsOptional($propertyConfigurationModel->isOptional());
+ $config->setIsOptional(true);
+
+ if (!\in_array($propertyConfigurationModel->getType(), $this->bypassParamConverterExceptionClasses, true)) {
+ $config->setIsOptional($propertyConfigurationModel->isOptional());
+ }
if ($propertyConfigurationModel->getMapTo() !== null) {
$config->setOptions(
diff --git a/Resources/config/services.xml b/Resources/config/services.xml
index 13beb30..c75a5f4 100644
--- a/Resources/config/services.xml
+++ b/Resources/config/services.xml
@@ -8,6 +8,7 @@
+
diff --git a/Tests/ConfigurationExtractor/PropertyConfigurationExtractorTest.php b/Tests/ConfigurationExtractor/PropertyConfigurationExtractorTest.php
index 7ad637b..75cbbbf 100644
--- a/Tests/ConfigurationExtractor/PropertyConfigurationExtractorTest.php
+++ b/Tests/ConfigurationExtractor/PropertyConfigurationExtractorTest.php
@@ -200,4 +200,60 @@ public function testDummyEntityWithCollectionConstraintWithoutEntity(): void
self::assertTrue($propertyConfigurationModel->isOptional());
self::assertTrue($propertyConfigurationModel->isCollection());
}
+
+ /**
+ * @covers \Chaplean\Bundle\DtoHandlerBundle\ConfigurationExtractor\PropertyConfigurationExtractor::__construct()
+ * @covers \Chaplean\Bundle\DtoHandlerBundle\ConfigurationExtractor\PropertyConfigurationExtractor::getName()
+ * @covers \Chaplean\Bundle\DtoHandlerBundle\ConfigurationExtractor\PropertyConfigurationExtractor::getMapTo()
+ * @covers \Chaplean\Bundle\DtoHandlerBundle\ConfigurationExtractor\PropertyConfigurationExtractor::getType()
+ * @covers \Chaplean\Bundle\DtoHandlerBundle\ConfigurationExtractor\PropertyConfigurationExtractor::getParamConverterAnnotation()
+ * @covers \Chaplean\Bundle\DtoHandlerBundle\ConfigurationExtractor\PropertyConfigurationExtractor::isOptional()
+ * @covers \Chaplean\Bundle\DtoHandlerBundle\ConfigurationExtractor\PropertyConfigurationExtractor::isCollection()
+ * @covers \Chaplean\Bundle\DtoHandlerBundle\ConfigurationExtractor\PropertyConfigurationExtractor::findTypeConstraint()
+ *
+ * @return void
+ *
+ * @throws AnnotationException
+ * @throws \ReflectionException
+ */
+ public function testDateTimeType(): void
+ {
+ $property = $this->dtoReflectionClass->getProperty('property8');
+ $propertyConfigurationModel = new PropertyConfigurationExtractor($property);
+
+ self::assertSame('property8', $propertyConfigurationModel->getName());
+ self::assertNull($propertyConfigurationModel->getMapTo());
+ self::assertSame(\DateTime::class, $propertyConfigurationModel->getType());
+ self::assertNull($propertyConfigurationModel->getParamConverterAnnotation());
+ self::assertFalse($propertyConfigurationModel->isOptional());
+ self::assertFalse($propertyConfigurationModel->isCollection());
+ }
+
+ /**
+ * @covers \Chaplean\Bundle\DtoHandlerBundle\ConfigurationExtractor\PropertyConfigurationExtractor::__construct()
+ * @covers \Chaplean\Bundle\DtoHandlerBundle\ConfigurationExtractor\PropertyConfigurationExtractor::getName()
+ * @covers \Chaplean\Bundle\DtoHandlerBundle\ConfigurationExtractor\PropertyConfigurationExtractor::getMapTo()
+ * @covers \Chaplean\Bundle\DtoHandlerBundle\ConfigurationExtractor\PropertyConfigurationExtractor::getType()
+ * @covers \Chaplean\Bundle\DtoHandlerBundle\ConfigurationExtractor\PropertyConfigurationExtractor::getParamConverterAnnotation()
+ * @covers \Chaplean\Bundle\DtoHandlerBundle\ConfigurationExtractor\PropertyConfigurationExtractor::isOptional()
+ * @covers \Chaplean\Bundle\DtoHandlerBundle\ConfigurationExtractor\PropertyConfigurationExtractor::isCollection()
+ * @covers \Chaplean\Bundle\DtoHandlerBundle\ConfigurationExtractor\PropertyConfigurationExtractor::findTypeConstraint()
+ *
+ * @return void
+ *
+ * @throws AnnotationException
+ * @throws \ReflectionException
+ */
+ public function testDateType(): void
+ {
+ $property = $this->dtoReflectionClass->getProperty('property9');
+ $propertyConfigurationModel = new PropertyConfigurationExtractor($property);
+
+ self::assertSame('property9', $propertyConfigurationModel->getName());
+ self::assertNull($propertyConfigurationModel->getMapTo());
+ self::assertSame(\DateTime::class, $propertyConfigurationModel->getType());
+ self::assertNull($propertyConfigurationModel->getParamConverterAnnotation());
+ self::assertTrue($propertyConfigurationModel->isOptional());
+ self::assertFalse($propertyConfigurationModel->isCollection());
+ }
}
diff --git a/Tests/ParamConverter/DataTransferObjectParamConverterTest.php b/Tests/ParamConverter/DataTransferObjectParamConverterTest.php
index 863590c..80b5c31 100644
--- a/Tests/ParamConverter/DataTransferObjectParamConverterTest.php
+++ b/Tests/ParamConverter/DataTransferObjectParamConverterTest.php
@@ -18,7 +18,7 @@
use phpmock\mockery\PHPMockery;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterManager;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
@@ -62,7 +62,16 @@ public function setUp(): void
PHPMockery::mock('Chaplean\Bundle\DtoHandlerBundle\ParamConverter', 'uniqid')->andReturn('hash');
+ $container = \Mockery::mock(ContainerInterface::class);
+ $container->shouldReceive('getParameter')
+ ->once()
+ ->with('chaplean_dto_handler.bypass_param_converter_exception')
+ ->andReturn([
+ \DateTime::class
+ ]);
+
$this->dataTransferObjectParamConverter = new DataTransferObjectParamConverter(
+ $container,
$this->manager,
$this->validator
);