diff --git a/DependencyInjection/Compiler/ConfigurationPass.php b/DependencyInjection/Compiler/ConfigurationPass.php
index 2bfa5ec..674062f 100644
--- a/DependencyInjection/Compiler/ConfigurationPass.php
+++ b/DependencyInjection/Compiler/ConfigurationPass.php
@@ -31,6 +31,7 @@ final class ConfigurationPass implements CompilerPassInterface
->addArgument(new Reference('form.factory'))
->addArgument(new Reference('annotations.reader'))
->addArgument($container->getParameter('nelmio_api_doc.media_types'))
+ ->addArgument($container->getParameter('nelmio_api_doc.use_validation_groups'))
->addTag('nelmio_api_doc.model_describer', ['priority' => 100]);
}
diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php
index 5a4c2e3..18879d6 100644
--- a/DependencyInjection/Configuration.php
+++ b/DependencyInjection/Configuration.php
@@ -29,6 +29,10 @@ final class Configuration implements ConfigurationInterface
$rootNode
->children()
+ ->booleanNode('use_validation_groups')
+ ->info('If true, `groups` passed to @Model annotations will be used to limit validation constraints')
+ ->defaultFalse()
+ ->end()
->arrayNode('documentation')
->useAttributeAsKey('key')
->info('The documentation used as base')
diff --git a/DependencyInjection/NelmioApiDocExtension.php b/DependencyInjection/NelmioApiDocExtension.php
index cd6a622..1b7f113 100644
--- a/DependencyInjection/NelmioApiDocExtension.php
+++ b/DependencyInjection/NelmioApiDocExtension.php
@@ -64,6 +64,7 @@ final class NelmioApiDocExtension extends Extension implements PrependExtensionI
$container->setParameter('nelmio_api_doc.areas', array_keys($config['areas']));
$container->setParameter('nelmio_api_doc.media_types', $config['media_types']);
+ $container->setParameter('nelmio_api_doc.use_validation_groups', $config['use_validation_groups']);
foreach ($config['areas'] as $area => $areaConfig) {
$nameAliases = $this->findNameAliases($config['models']['names'], $area);
$container->register(sprintf('nelmio_api_doc.generator.%s', $area), ApiDocGenerator::class)
@@ -175,6 +176,7 @@ final class NelmioApiDocExtension extends Extension implements PrependExtensionI
new Reference('annotations.reader'),
$config['media_types'],
$jmsNamingStrategy,
+ $container->getParameter('nelmio_api_doc.use_validation_groups')
])
->addTag('nelmio_api_doc.model_describer', ['priority' => 50]);
diff --git a/ModelDescriber/Annotations/AnnotationsReader.php b/ModelDescriber/Annotations/AnnotationsReader.php
index dcd38bb..3fbcbfb 100644
--- a/ModelDescriber/Annotations/AnnotationsReader.php
+++ b/ModelDescriber/Annotations/AnnotationsReader.php
@@ -27,14 +27,21 @@ class AnnotationsReader
private $openApiAnnotationsReader;
private $symfonyConstraintAnnotationReader;
- public function __construct(Reader $annotationsReader, ModelRegistry $modelRegistry, array $mediaTypes)
- {
+ public function __construct(
+ Reader $annotationsReader,
+ ModelRegistry $modelRegistry,
+ array $mediaTypes,
+ bool $useValidationGroups = false
+ ) {
$this->annotationsReader = $annotationsReader;
$this->modelRegistry = $modelRegistry;
$this->phpDocReader = new PropertyPhpDocReader();
$this->openApiAnnotationsReader = new OpenApiAnnotationsReader($annotationsReader, $modelRegistry, $mediaTypes);
- $this->symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader($annotationsReader);
+ $this->symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader(
+ $annotationsReader,
+ $useValidationGroups
+ );
}
public function updateDefinition(\ReflectionClass $reflectionClass, OA\Schema $schema): void
diff --git a/ModelDescriber/FormModelDescriber.php b/ModelDescriber/FormModelDescriber.php
index 6bff0e4..a0f5c2a 100644
--- a/ModelDescriber/FormModelDescriber.php
+++ b/ModelDescriber/FormModelDescriber.php
@@ -37,9 +37,14 @@ final class FormModelDescriber implements ModelDescriberInterface, ModelRegistry
private $formFactory;
private $doctrineReader;
private $mediaTypes;
+ private $useValidationGroups;
- public function __construct(FormFactoryInterface $formFactory = null, Reader $reader = null, array $mediaTypes = null)
- {
+ public function __construct(
+ FormFactoryInterface $formFactory = null,
+ Reader $reader = null,
+ array $mediaTypes = null,
+ bool $useValidationGroups = false
+ ) {
$this->formFactory = $formFactory;
$this->doctrineReader = $reader;
if (null === $reader) {
@@ -51,6 +56,7 @@ final class FormModelDescriber implements ModelDescriberInterface, ModelRegistry
@trigger_error(sprintf('Not passing media types to the constructor of %s is deprecated since version 4.1 and won\'t be allowed in version 5.', self::class), E_USER_DEPRECATED);
}
$this->mediaTypes = $mediaTypes;
+ $this->useValidationGroups = $useValidationGroups;
}
public function describe(Model $model, OA\Schema $schema)
@@ -66,7 +72,12 @@ final class FormModelDescriber implements ModelDescriberInterface, ModelRegistry
$class = $model->getType()->getClassName();
- $annotationsReader = new AnnotationsReader($this->doctrineReader, $this->modelRegistry, $this->mediaTypes);
+ $annotationsReader = new AnnotationsReader(
+ $this->doctrineReader,
+ $this->modelRegistry,
+ $this->mediaTypes,
+ $this->useValidationGroups
+ );
$annotationsReader->updateDefinition(new \ReflectionClass($class), $schema);
$form = $this->formFactory->create($class, null, $model->getOptions() ?? []);
diff --git a/ModelDescriber/JMSModelDescriber.php b/ModelDescriber/JMSModelDescriber.php
index c81519a..129c432 100644
--- a/ModelDescriber/JMSModelDescriber.php
+++ b/ModelDescriber/JMSModelDescriber.php
@@ -49,16 +49,23 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn
*/
private $propertyTypeUseGroupsCache = [];
+ /**
+ * @var bool
+ */
+ private $useValidationGroups;
+
public function __construct(
MetadataFactoryInterface $factory,
Reader $reader,
array $mediaTypes,
- ?PropertyNamingStrategyInterface $namingStrategy = null
+ ?PropertyNamingStrategyInterface $namingStrategy = null,
+ bool $useValidationGroups = false
) {
$this->factory = $factory;
$this->namingStrategy = $namingStrategy;
$this->doctrineReader = $reader;
$this->mediaTypes = $mediaTypes;
+ $this->useValidationGroups = $useValidationGroups;
}
/**
@@ -73,7 +80,12 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn
}
$schema->type = 'object';
- $annotationsReader = new AnnotationsReader($this->doctrineReader, $this->modelRegistry, $this->mediaTypes);
+ $annotationsReader = new AnnotationsReader(
+ $this->doctrineReader,
+ $this->modelRegistry,
+ $this->mediaTypes,
+ $this->useValidationGroups
+ );
$annotationsReader->updateDefinition(new \ReflectionClass($className), $schema);
$isJmsV1 = null !== $this->namingStrategy;
diff --git a/ModelDescriber/ObjectModelDescriber.php b/ModelDescriber/ObjectModelDescriber.php
index 0aa339a..c4c9b87 100644
--- a/ModelDescriber/ObjectModelDescriber.php
+++ b/ModelDescriber/ObjectModelDescriber.php
@@ -40,19 +40,23 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar
private $mediaTypes;
/** @var NameConverterInterface[] */
private $nameConverter;
+ /** @var bool */
+ private $useValidationGroups;
public function __construct(
PropertyInfoExtractorInterface $propertyInfo,
Reader $reader,
iterable $propertyDescribers,
array $mediaTypes,
- NameConverterInterface $nameConverter = null
+ NameConverterInterface $nameConverter = null,
+ bool $useValidationGroups = false
) {
$this->propertyInfo = $propertyInfo;
$this->doctrineReader = $reader;
$this->propertyDescribers = $propertyDescribers;
$this->mediaTypes = $mediaTypes;
$this->nameConverter = $nameConverter;
+ $this->useValidationGroups = $useValidationGroups;
}
public function describe(Model $model, OA\Schema $schema)
@@ -68,7 +72,12 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar
}
$reflClass = new \ReflectionClass($class);
- $annotationsReader = new AnnotationsReader($this->doctrineReader, $this->modelRegistry, $this->mediaTypes);
+ $annotationsReader = new AnnotationsReader(
+ $this->doctrineReader,
+ $this->modelRegistry,
+ $this->mediaTypes,
+ $this->useValidationGroups
+ );
$annotationsReader->updateDefinition($reflClass, $schema);
$discriminatorMap = $this->doctrineReader->getClassAnnotation($reflClass, DiscriminatorMap::class);
diff --git a/Resources/config/services.xml b/Resources/config/services.xml
index 9d2c38c..bfc845c 100644
--- a/Resources/config/services.xml
+++ b/Resources/config/services.xml
@@ -75,6 +75,7 @@
+ %nelmio_api_doc.use_validation_groups%
diff --git a/Tests/Functional/Controller/ApiController.php b/Tests/Functional/Controller/ApiController.php
index 4a40897..3de4c5d 100644
--- a/Tests/Functional/Controller/ApiController.php
+++ b/Tests/Functional/Controller/ApiController.php
@@ -18,6 +18,7 @@ use Nelmio\ApiDocBundle\Annotation\Security;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\Article;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\CompoundEntity;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraints;
+use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraintsWithValidationGroups;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyDiscriminator;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\User;
use Nelmio\ApiDocBundle\Tests\Functional\Form\DummyType;
@@ -179,6 +180,18 @@ class ApiController
{
}
+ /**
+ * @Route("/swagger/symfonyConstraintsWithValidationGroups", methods={"GET"})
+ * @OA\Response(
+ * response="201",
+ * description="Used for symfony constraints with validation groups test",
+ * @Model(type=SymfonyConstraintsWithValidationGroups::class, groups={"test"})
+ * )
+ */
+ public function symfonyConstraintsWithGroupsAction()
+ {
+ }
+
/**
* @OA\Response(
* response="200",
diff --git a/Tests/Functional/Entity/SymfonyConstraints.php b/Tests/Functional/Entity/SymfonyConstraints.php
index 05e65c9..86e3d16 100644
--- a/Tests/Functional/Entity/SymfonyConstraints.php
+++ b/Tests/Functional/Entity/SymfonyConstraints.php
@@ -19,7 +19,7 @@ class SymfonyConstraints
/**
* @var int
*
- * @Assert\NotBlank()
+ * @Assert\NotBlank(groups={"test"})
*/
private $propertyNotBlank;
diff --git a/Tests/Functional/Entity/SymfonyConstraintsWithValidationGroups.php b/Tests/Functional/Entity/SymfonyConstraintsWithValidationGroups.php
new file mode 100644
index 0000000..01057b2
--- /dev/null
+++ b/Tests/Functional/Entity/SymfonyConstraintsWithValidationGroups.php
@@ -0,0 +1,36 @@
+ [
'propertyNotBlank' => [
'type' => 'integer',
- 'maxItems' => '10',
- 'minItems' => '0',
+ 'maxItems' => 10,
+ 'minItems' => 0,
],
'propertyNotNull' => [
'type' => 'integer',
diff --git a/Tests/Functional/TestKernel.php b/Tests/Functional/TestKernel.php
index 5de564d..fea681b 100644
--- a/Tests/Functional/TestKernel.php
+++ b/Tests/Functional/TestKernel.php
@@ -19,6 +19,7 @@ use JMS\SerializerBundle\JMSSerializerBundle;
use Nelmio\ApiDocBundle\NelmioApiDocBundle;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\BazingaUser;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSComplex;
+use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraintsWithValidationGroups;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSPicture;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\PrivateProtectedExposure;
use Nelmio\ApiDocBundle\Tests\Functional\ModelDescriber\VirtualTypeClassDoesNotExistsHandlerDefinedDescriber;
@@ -32,12 +33,14 @@ use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Routing\RouteCollectionBuilder;
use Symfony\Component\Serializer\Annotation\SerializedName;
+use Symfony\Component\Validator\Constraint;
class TestKernel extends Kernel
{
const USE_JMS = 1;
const USE_BAZINGA = 2;
const ERROR_ARRAY_ITEMS = 4;
+ const USE_VALIDATION_GROUPS = 8;
use MicroKernelTrait;
@@ -173,6 +176,7 @@ class TestKernel extends Kernel
// Filter routes
$c->loadFromExtension('nelmio_api_doc', [
+ 'use_validation_groups' => boolval($this->flags & self::USE_VALIDATION_GROUPS),
'documentation' => [
'servers' => [ // from https://github.com/nelmio/NelmioApiDocBundle/issues/1691
[
@@ -263,6 +267,16 @@ class TestKernel extends Kernel
'type' => JMSComplex::class,
'groups' => null,
],
+ [
+ 'alias' => 'SymfonyConstraintsTestGroup',
+ 'type' => SymfonyConstraintsWithValidationGroups::class,
+ 'groups' => ['test'],
+ ],
+ [
+ 'alias' => 'SymfonyConstraintsDefaultGroup',
+ 'type' => SymfonyConstraintsWithValidationGroups::class,
+ 'groups' => null,
+ ],
],
],
]);
diff --git a/Tests/Functional/ValidationGroupsFunctionalTest.php b/Tests/Functional/ValidationGroupsFunctionalTest.php
new file mode 100644
index 0000000..611b869
--- /dev/null
+++ b/Tests/Functional/ValidationGroupsFunctionalTest.php
@@ -0,0 +1,77 @@
+ 'api.example.com']);
+ }
+
+ public function testConstraintGroupsAreRespectedWhenDescribingModels()
+ {
+ $expected = [
+ 'required' => [
+ 'property',
+ ],
+ 'properties' => [
+ 'property' => [
+ 'type' => 'integer',
+ // the min/max constraint is in the default group only and shouldn't
+ // be read here with validation groups turned on
+ ],
+ ],
+ 'type' => 'object',
+ 'schema' => 'SymfonyConstraintsTestGroup',
+ ];
+
+ $this->assertEquals(
+ $expected,
+ json_decode($this->getModel('SymfonyConstraintsTestGroup')->toJson(), true)
+ );
+ }
+
+ public function testConstraintDefaultGroupsAreRespectedWhenReadingAnnotations()
+ {
+ $expected = [
+ 'properties' => [
+ 'property' => [
+ 'type' => 'integer',
+ // min/max will be read here as they are in th e default group
+ 'maximum' => 100,
+ 'minimum' => 1,
+ ],
+ 'propertyInDefaultGroup' => [
+ 'type' => 'integer',
+ // min/max will be read here as they are in th e default group
+ 'maximum' => 100,
+ 'minimum' => 1,
+ ],
+ ],
+ 'type' => 'object',
+ 'schema' => 'SymfonyConstraintsDefaultGroup',
+ ];
+
+ $this->assertEquals(
+ $expected,
+ json_decode($this->getModel('SymfonyConstraintsDefaultGroup')->toJson(), true)
+ );
+ }
+}