From af30c6ac0e22e9c72de4199fd3f13c035412d6f2 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Fri, 27 Apr 2018 11:57:21 +0200 Subject: [PATCH] handle form collection with type introspection --- ModelDescriber/FormModelDescriber.php | 160 +++++++++++++---------- Tests/Functional/Form/DummyEmptyType.php | 22 ++++ Tests/Functional/Form/UserType.php | 9 ++ Tests/Functional/FunctionalTest.php | 10 ++ 4 files changed, 132 insertions(+), 69 deletions(-) create mode 100644 Tests/Functional/Form/DummyEmptyType.php diff --git a/ModelDescriber/FormModelDescriber.php b/ModelDescriber/FormModelDescriber.php index 13c3836..fae651d 100644 --- a/ModelDescriber/FormModelDescriber.php +++ b/ModelDescriber/FormModelDescriber.php @@ -16,6 +16,7 @@ use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface; use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait; use Nelmio\ApiDocBundle\Model\Model; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormConfigBuilderInterface; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormTypeInterface; @@ -77,98 +78,119 @@ final class FormModelDescriber implements ModelDescriberInterface, ModelRegistry continue; // Type manually defined } - for ($type = $config->getType(); null !== $type; $type = $type->getParent()) { - $blockPrefix = $type->getBlockPrefix(); + $this->findFormType($config, $property); + } + } - if ('text' === $blockPrefix) { - $property->setType('string'); + /** + * Finds and sets the schema type on $property based on $config info. + * + * Returns true if a native Swagger type was found, false otherwise + * + * @param FormConfigBuilderInterface $config + * @param $property + * + * @return bool + */ + private function findFormType(FormConfigBuilderInterface $config, $property): bool + { + for ($type = $config->getType(); null !== $type; $type = $type->getParent()) { + $blockPrefix = $type->getBlockPrefix(); - break; - } + if ('text' === $blockPrefix) { + $property->setType('string'); - if ('number' === $blockPrefix) { - $property->setType('number'); + return true; + } - break; - } + if ('number' === $blockPrefix) { + $property->setType('number'); - if ('integer' === $blockPrefix) { - $property->setType('integer'); + return true; + } - break; - } + if ('integer' === $blockPrefix) { + $property->setType('integer'); - if ('date' === $blockPrefix) { - $property->setType('string'); - $property->setFormat('date'); + return true; + } - break; - } + if ('date' === $blockPrefix) { + $property->setType('string'); + $property->setFormat('date'); - if ('datetime' === $blockPrefix) { - $property->setType('string'); - $property->setFormat('date-time'); + return true; + } - break; - } + if ('datetime' === $blockPrefix) { + $property->setType('string'); + $property->setFormat('date-time'); - if ('choice' === $blockPrefix) { - if ($config->getOption('multiple')) { - $property->setType('array'); - } else { - $property->setType('string'); - } - if (($choices = $config->getOption('choices')) && is_array($choices) && count($choices)) { - $enums = array_values($choices); - $type = $this->isNumbersArray($enums) ? 'number' : 'string'; - if ($config->getOption('multiple')) { - $property->getItems()->setType($type)->setEnum($enums); - } else { - $property->setType($type)->setEnum($enums); - } - } + return true; + } - break; - } - - if ('checkbox' === $blockPrefix) { - $property->setType('boolean'); - } - - if ('collection' === $blockPrefix) { - $subType = $config->getOption('entry_type'); + if ('choice' === $blockPrefix) { + if ($config->getOption('multiple')) { $property->setType('array'); - - $model = new Model(new Type(Type::BUILTIN_TYPE_OBJECT, false, $subType), null); - $property->getItems()->setRef($this->modelRegistry->register($model)); - $property->setExample(sprintf('[{%s}]', $subType)); - - break; + } else { + $property->setType('string'); } - - if ('entity' === $blockPrefix) { - $entityClass = $config->getOption('class'); - + if (($choices = $config->getOption('choices')) && is_array($choices) && count($choices)) { + $enums = array_values($choices); + $type = $this->isNumbersArray($enums) ? 'number' : 'string'; if ($config->getOption('multiple')) { - $property->setFormat(sprintf('[%s id]', $entityClass)); - $property->setType('array'); + $property->getItems()->setType($type)->setEnum($enums); } else { - $property->setType('string'); - $property->setFormat(sprintf('%s id', $entityClass)); + $property->setType($type)->setEnum($enums); } - - break; } - if ($type->getInnerType() && ($formClass = get_class($type->getInnerType())) && !$this->isBuiltinType($formClass)) { - // if form type is not builtin in Form component. - $model = new Model(new Type(Type::BUILTIN_TYPE_OBJECT, false, $formClass)); - $property->setRef($this->modelRegistry->register($model)); + return true; + } - break; + if ('checkbox' === $blockPrefix) { + $property->setType('boolean'); + } + + if ('collection' === $blockPrefix) { + $subType = $config->getOption('entry_type'); + $subOptions = $config->getOption('entry_options'); + $subForm = $this->formFactory->create($subType, null, $subOptions); + + $property->setType('array'); + $itemsProp = $property->getItems(); + + if (!$this->findFormType($subForm->getConfig(), $itemsProp)) { + $property->setExample(sprintf('[{%s}]', $subType)); } + + return true; + } + + if ('entity' === $blockPrefix) { + $entityClass = $config->getOption('class'); + + if ($config->getOption('multiple')) { + $property->setFormat(sprintf('[%s id]', $entityClass)); + $property->setType('array'); + } else { + $property->setType('string'); + $property->setFormat(sprintf('%s id', $entityClass)); + } + + return true; + } + + if ($type->getInnerType() && ($formClass = get_class($type->getInnerType())) && !$this->isBuiltinType($formClass)) { + // if form type is not builtin in Form component. + $model = new Model(new Type(Type::BUILTIN_TYPE_OBJECT, false, $formClass)); + $property->setRef($this->modelRegistry->register($model)); + + return false; } } + + return false; } /** diff --git a/Tests/Functional/Form/DummyEmptyType.php b/Tests/Functional/Form/DummyEmptyType.php new file mode 100644 index 0000000..e549e93 --- /dev/null +++ b/Tests/Functional/Form/DummyEmptyType.php @@ -0,0 +1,22 @@ +add('strings', CollectionType::class, [ + 'entry_type' => TextType::class, + 'required' => false, + ]) ->add('dummy', DummyType::class) ->add('dummies', CollectionType::class, [ 'entry_type' => DummyType::class, ]) + ->add('empty_dummies', CollectionType::class, [ + 'entry_type' => DummyEmptyType::class, + 'required' => false, + ]) ->add('quz', DummyType::class, ['documentation' => ['type' => 'string', 'description' => 'User type.'], 'required' => false]); } diff --git a/Tests/Functional/FunctionalTest.php b/Tests/Functional/FunctionalTest.php index 0d6c7ae..aa56496 100644 --- a/Tests/Functional/FunctionalTest.php +++ b/Tests/Functional/FunctionalTest.php @@ -12,6 +12,7 @@ namespace Nelmio\ApiDocBundle\Tests\Functional; use EXSyst\Component\Swagger\Tag; +use Nelmio\ApiDocBundle\Tests\Functional\Form\DummyEmptyType; use Nelmio\ApiDocBundle\Tests\Functional\Form\DummyType; class FunctionalTest extends WebTestCase @@ -223,12 +224,21 @@ class FunctionalTest extends WebTestCase $this->assertEquals([ 'type' => 'object', 'properties' => [ + 'strings' => [ + 'items' => ['type' => 'string'], + 'type' => 'array', + ], 'dummy' => ['$ref' => '#/definitions/DummyType'], 'dummies' => [ 'items' => ['$ref' => '#/definitions/DummyType'], 'type' => 'array', 'example' => sprintf('[{%s}]', DummyType::class), ], + 'empty_dummies' => [ + 'items' => ['$ref' => '#/definitions/DummyEmptyType'], + 'type' => 'array', + 'example' => sprintf('[{%s}]', DummyEmptyType::class), + ], 'quz' => [ 'type' => 'string', 'description' => 'User type.',