Merge pull request #1022 from nelmio/forms

Add forms support
This commit is contained in:
Guilhem Niot 2017-06-25 11:56:25 +00:00 committed by GitHub
commit 8c244f73e5
7 changed files with 164 additions and 0 deletions

View File

@ -19,9 +19,11 @@ use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RouteCollection;
use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder; use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder;
use Nelmio\ApiDocBundle\ModelDescriber\FormModelDescriber;
final class NelmioApiDocExtension extends Extension implements PrependExtensionInterface final class NelmioApiDocExtension extends Extension implements PrependExtensionInterface
{ {
@ -43,6 +45,13 @@ final class NelmioApiDocExtension extends Extension implements PrependExtensionI
$loader->load('services.xml'); $loader->load('services.xml');
if (interface_exists(FormInterface::class)) {
$container->register('nelmio_api_doc.model_describers.form', FormModelDescriber::class)
->setPublic(false)
->addArgument(new Reference('form.factory'))
->addTag('nelmio_api_doc.model_describer', ['priority' => 10]);
}
// Filter routes // Filter routes
$routesDefinition = (new Definition(RouteCollection::class)) $routesDefinition = (new Definition(RouteCollection::class))
->setFactory([new Reference('router'), 'getRouteCollection']); ->setFactory([new Reference('router'), 'getRouteCollection']);

View File

@ -0,0 +1,102 @@
<?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\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\FormFactoryInterface;
use Symfony\Component\Form\FormTypeInterface;
/**
* @internal
*/
final class FormModelDescriber implements ModelDescriberInterface, ModelRegistryAwareInterface
{
use ModelRegistryAwareTrait;
private $formFactory;
public function __construct(FormFactoryInterface $formFactory = null)
{
$this->formFactory = $formFactory;
}
public function describe(Model $model, Schema $schema)
{
if (method_exists('Symfony\Component\Form\AbstractType', '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) {
throw new \LogicException('You need to enable forms in your application to use a form as a model.');
}
$schema->setType('object');
$properties = $schema->getProperties();
$class = $model->getType()->getClassName();
$form = $this->formFactory->create($class, null, []);
$this->parseForm($schema, $form);
}
public function supports(Model $model): bool
{
return is_a($model->getType()->getClassName(), FormTypeInterface::class, true);
}
private function parseForm(Schema $schema, $form)
{
$properties = $schema->getProperties();
foreach ($form as $name => $child) {
$config = $child->getConfig();
$property = $properties->get($name);
for ($type = $config->getType(); null !== $type; $type = $type->getParent()) {
$blockPrefix = $type->getBlockPrefix();
if ('text' === $blockPrefix) {
$property->setType('string');
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)) {
$property->setEnum(array_values($choices));
}
break;
}
if ('collection' === $blockPrefix) {
$subType = $config->getOption('entry_type');
}
}
if ($config->getRequired()) {
$required = $schema->getRequired() ?? [];
$required[] = $name;
$schema->setRequired($required);
}
}
}
}

View File

@ -17,6 +17,7 @@ use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Annotation\Operation; use Nelmio\ApiDocBundle\Annotation\Operation;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\User; use Nelmio\ApiDocBundle\Tests\Functional\Entity\User;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\Article; use Nelmio\ApiDocBundle\Tests\Functional\Entity\Article;
use Nelmio\ApiDocBundle\Tests\Functional\Form\DummyType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Swagger\Annotations as SWG; use Swagger\Annotations as SWG;
@ -120,4 +121,19 @@ class ApiController
public function filteredAction() public function filteredAction()
{ {
} }
/**
* @Route("/form", methods={"POST"})
* @SWG\Parameter(
* name="form",
* in="body",
* description="Request content",
* @Model(type=DummyType::class)
* )
* @SWG\Response(response="201", description="")
*/
public function formAction()
{
}
} }

View File

@ -0,0 +1,18 @@
<?php
namespace Nelmio\ApiDocBundle\Tests\Functional\Form;
use Nelmio\ApiDocBundle\Annotation\Model;
use Swagger\Annotations\Definition;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
class DummyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('bar', TextType::class, ['required' => false]);
$builder->add('foo', ChoiceType::class, ['choices' => ['male', 'female']]);
}
}

View File

@ -167,6 +167,23 @@ class FunctionalTest extends WebTestCase
$this->assertEquals('#/definitions/User', $model->getItems()->getRef()); $this->assertEquals('#/definitions/User', $model->getItems()->getRef());
} }
public function testFormSupport()
{
$this->assertEquals([
'type' => 'object',
'properties' => [
'bar' => [
'type' => 'string',
],
'foo' => [
'type' => 'string',
'enum' => ['male', 'female'],
]
],
'required' => ['foo'],
], $this->getModel('DummyType')->toArray());
}
private function getSwaggerDefinition() private function getSwaggerDefinition()
{ {
static::createClient(); static::createClient();

View File

@ -63,6 +63,7 @@ class TestKernel extends Kernel
'secret' => 'MySecretKey', 'secret' => 'MySecretKey',
'test' => null, 'test' => null,
'validation' => null, 'validation' => null,
'form' => null,
'templating' => [ 'templating' => [
'engines' => ['twig'], 'engines' => ['twig'],
], ],

View File

@ -29,6 +29,7 @@
"symfony/config": "^2.8|^3.0|^4.0", "symfony/config": "^2.8|^3.0|^4.0",
"symfony/validator": "^2.8|^3.0|^4.0", "symfony/validator": "^2.8|^3.0|^4.0",
"symfony/property-access": "^2.8|^3.0|^4.0", "symfony/property-access": "^2.8|^3.0|^4.0",
"symfony/form": "^3.0.8|^4.0",
"symfony/dom-crawler": "^2.8|^3.0|^4.0", "symfony/dom-crawler": "^2.8|^3.0|^4.0",
"symfony/browser-kit": "^2.8|^3.0|^4.0", "symfony/browser-kit": "^2.8|^3.0|^4.0",
"symfony/cache": "^3.1|^4.0", "symfony/cache": "^3.1|^4.0",