Reading form recursively down(nested form) (#1087)

* Added support for EntityType in FormModel Describer. Reading form recursively down. Modified formSupport test

* codestyle fixes
This commit is contained in:
Vladislav Kopaygorodsky 2017-09-20 08:18:58 +03:00 committed by Guilhem Niot
parent 3adf8c3d97
commit 0baa677515
5 changed files with 110 additions and 3 deletions

View File

@ -15,6 +15,9 @@ use Nelmio\ApiDocBundle\Model\ModelRegistry;
trait ModelRegistryAwareTrait
{
/**
* @var ModelRegistry
*/
private $modelRegistry;
public function setModelRegistry(ModelRegistry $modelRegistry)

View File

@ -12,15 +12,22 @@
namespace Nelmio\ApiDocBundle\ModelDescriber;
use EXSyst\Component\Swagger\Schema;
use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface;
use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait;
use Nelmio\ApiDocBundle\Model\Model;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\PropertyInfo\Type;
/**
* @internal
*/
final class FormModelDescriber implements ModelDescriberInterface
final class FormModelDescriber implements ModelDescriberInterface, ModelRegistryAwareInterface
{
use ModelRegistryAwareTrait;
private $formFactory;
public function __construct(FormFactoryInterface $formFactory = null)
@ -30,7 +37,7 @@ final class FormModelDescriber implements ModelDescriberInterface
public function describe(Model $model, Schema $schema)
{
if (method_exists('Symfony\Component\Form\AbstractType', 'setDefaultOptions')) {
if (method_exists(AbstractType::class, 'setDefaultOptions')) {
throw new \LogicException('symfony/form < 3.0 is not supported, please upgrade to an higher version to use a form as a model.');
}
if (null === $this->formFactory) {
@ -51,9 +58,10 @@ final class FormModelDescriber implements ModelDescriberInterface
return is_a($model->getType()->getClassName(), FormTypeInterface::class, true);
}
private function parseForm(Schema $schema, $form)
private function parseForm(Schema $schema, FormInterface $form)
{
$properties = $schema->getProperties();
foreach ($form as $name => $child) {
$config = $child->getConfig();
$property = $properties->get($name);
@ -64,16 +72,24 @@ final class FormModelDescriber implements ModelDescriberInterface
$property->setType('string');
break;
}
if ('number' === $blockPrefix) {
$property->setType('number');
break;
}
if ('date' === $blockPrefix) {
$property->setType('string');
$property->setFormat('date');
break;
}
if ('datetime' === $blockPrefix) {
$property->setType('string');
$property->setFormat('date-time');
break;
}
if ('choice' === $blockPrefix) {
$property->setType('string');
if (($choices = $config->getOption('choices')) && is_array($choices) && count($choices)) {
@ -85,6 +101,27 @@ final class FormModelDescriber implements ModelDescriberInterface
if ('collection' === $blockPrefix) {
$subType = $config->getOption('entry_type');
}
if ('entity' === $blockPrefix) {
$entityClass = $config->getOption('class');
if ($config->getOption('multiple')) {
$property->setFormat(sprintf('[%s id]', $entityClass));
$property->setType('array');
$property->setExample('[1, 2, 3]');
} else {
$property->setType('string');
$property->setFormat(sprintf('%s id', $entityClass));
}
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));
break;
}
}
if ($config->getRequired()) {
@ -95,4 +132,9 @@ final class FormModelDescriber implements ModelDescriberInterface
}
}
}
private function isBuiltinType(string $type): bool
{
return 0 === strpos($type, 'Symfony\Component\Form\Extension\Core\Type');
}
}

View File

@ -18,6 +18,7 @@ use Nelmio\ApiDocBundle\Annotation\Operation;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\Article;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\User;
use Nelmio\ApiDocBundle\Tests\Functional\Form\DummyType;
use Nelmio\ApiDocBundle\Tests\Functional\Form\UserType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Swagger\Annotations as SWG;
@ -71,6 +72,27 @@ class ApiController
{
}
/**
* @Route("/test/users/{user}", methods={"POST"}, schemes={"https"}, requirements={"user"="/foo/"})
* @SWG\Response(
* response="201",
* description="Operation automatically detected",
* @Model(type=User::class)
* )
* @SWG\Parameter(
* name="foo",
* in="body",
* description="This is a parameter",
* @SWG\Schema(
* type="array",
* @Model(type=UserType::class)
* )
* )
*/
public function submitUserTypeAction()
{
}
/**
* @Route("/test/{user}", methods={"GET"}, schemes={"https"}, requirements={"user"="/foo/"})
* @Operation(

View File

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the NelmioApiDocBundle package.
*
* (c) Nelmio
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Nelmio\ApiDocBundle\Tests\Functional\Form;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('dummy', DummyType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}

View File

@ -176,6 +176,14 @@ class FunctionalTest extends WebTestCase
public function testFormSupport()
{
$this->assertEquals([
'type' => 'object',
'properties' => [
'dummy' => ['$ref' => '#/definitions/DummyType'],
],
'required' => ['dummy'],
], $this->getModel('UserType')->toArray());
$this->assertEquals([
'type' => 'object',
'properties' => [