diff --git a/Annotation/Model.php b/Annotation/Model.php
new file mode 100644
index 0000000..799914a
--- /dev/null
+++ b/Annotation/Model.php
@@ -0,0 +1,29 @@
+describers = $describers;
+ $this->modelDescribers = $modelDescribers;
$this->cacheItemPool = $cacheItemPool;
}
@@ -44,9 +50,15 @@ final class ApiDocGenerator
}
$this->swagger = new Swagger();
+ $modelRegistry = new ModelRegistry($this->modelDescribers, $this->swagger);
foreach ($this->describers as $describer) {
+ if ($describer instanceof ModelRegistryAwareInterface) {
+ $describer->setModelRegistry($modelRegistry);
+ }
+
$describer->describe($this->swagger);
}
+ $modelRegistry->registerDefinitions();
if (isset($item)) {
$this->cacheItemPool->save($item->set($this->swagger));
diff --git a/DependencyInjection/Compiler/AddModelDescribersPass.php b/DependencyInjection/Compiler/AddModelDescribersPass.php
new file mode 100644
index 0000000..5a4c84c
--- /dev/null
+++ b/DependencyInjection/Compiler/AddModelDescribersPass.php
@@ -0,0 +1,30 @@
+findAndSortTaggedServices('nelmio_api_doc.model_describer', $container);
+
+ $container->getDefinition('nelmio_api_doc.generator')->replaceArgument(1, $modelDescribers);
+ }
+}
diff --git a/Describer/DefaultDescriber.php b/Describer/DefaultDescriber.php
index 0b7c8ff..2c803aa 100644
--- a/Describer/DefaultDescriber.php
+++ b/Describer/DefaultDescriber.php
@@ -38,7 +38,7 @@ final class DefaultDescriber implements DescriberInterface
$operation = $path->getOperation($method);
// Default Response
- if (0 === iterator_count($operation->getResponses())) {
+ if (0 === count($operation->getResponses())) {
$defaultResponse = $operation->getResponses()->get('default');
$defaultResponse->setDescription('');
}
diff --git a/Describer/ModelRegistryAwareInterface.php b/Describer/ModelRegistryAwareInterface.php
new file mode 100644
index 0000000..36bdc9e
--- /dev/null
+++ b/Describer/ModelRegistryAwareInterface.php
@@ -0,0 +1,19 @@
+modelRegistry = $modelRegistry;
+ }
+}
diff --git a/Describer/RouteDescriber.php b/Describer/RouteDescriber.php
index eb7dca6..70f1d66 100644
--- a/Describer/RouteDescriber.php
+++ b/Describer/RouteDescriber.php
@@ -17,8 +17,10 @@ use Nelmio\ApiDocBundle\Util\ControllerReflector;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
-final class RouteDescriber implements DescriberInterface
+final class RouteDescriber implements DescriberInterface, ModelRegistryAwareInterface
{
+ use ModelRegistryAwareTrait;
+
private $routeCollection;
private $controllerReflector;
private $routeDescribers;
@@ -51,6 +53,10 @@ final class RouteDescriber implements DescriberInterface
if ($method = $this->controllerReflector->getReflectionMethod($controller)) {
// Extract as many informations as possible about this route
foreach ($this->routeDescribers as $describer) {
+ if ($describer instanceof ModelRegistryAwareInterface) {
+ $describer->setModelRegistry($this->modelRegistry);
+ }
+
$describer->describe($api, $route, $method);
}
}
diff --git a/Describer/SwaggerPhpDescriber.php b/Describer/SwaggerPhpDescriber.php
index 2716a08..5f26d1c 100644
--- a/Describer/SwaggerPhpDescriber.php
+++ b/Describer/SwaggerPhpDescriber.php
@@ -11,37 +11,30 @@
namespace Nelmio\ApiDocBundle\Describer;
+use Nelmio\ApiDocBundle\SwaggerPhp\AddDefaults;
+use Nelmio\ApiDocBundle\SwaggerPhp\ModelRegister;
use Nelmio\ApiDocBundle\SwaggerPhp\OperationResolver;
+use Swagger\Analyser;
use Swagger\Analysis;
-final class SwaggerPhpDescriber extends ExternalDocDescriber
+final class SwaggerPhpDescriber extends ExternalDocDescriber implements ModelRegistryAwareInterface
{
+ use ModelRegistryAwareTrait;
+
private $operationResolver;
public function __construct(string $projectPath, bool $overwrite = false)
{
+ $nelmioNamespace = 'Nelmio\\ApiDocBundle\\';
+ if (!in_array($nelmioNamespace, Analyser::$whitelist)) {
+ Analyser::$whitelist[] = $nelmioNamespace;
+ }
+
parent::__construct(function () use ($projectPath) {
- // Ignore notices as the documentation can be completed by other describers
- $prevHandler = set_error_handler(function ($type, $message, $file, $line, $context) use (&$prevHandler) {
- if (E_USER_NOTICE === $type || E_USER_WARNING === $type) {
- return;
- }
+ $options = ['processors' => $this->getProcessors()];
+ $annotation = \Swagger\scan($projectPath, $options);
- return null !== $prevHandler && call_user_func($prevHandler, $type, $message, $file, $line, $context);
- });
-
- try {
- $options = [];
- if (null !== $this->operationResolver) {
- $options['processors'] = array_merge([$this->operationResolver], Analysis::processors());
- }
-
- $annotation = \Swagger\scan($projectPath, $options);
-
- return json_decode(json_encode($annotation));
- } finally {
- restore_error_handler();
- }
+ return json_decode(json_encode($annotation));
}, $overwrite);
}
@@ -53,4 +46,17 @@ final class SwaggerPhpDescriber extends ExternalDocDescriber
{
$this->operationResolver = $operationResolver;
}
+
+ private function getProcessors(): array
+ {
+ $processors = [
+ new AddDefaults(),
+ new ModelRegister($this->modelRegistry),
+ ];
+ if (null !== $this->operationResolver) {
+ $processors[] = $this->operationResolver;
+ }
+
+ return array_merge($processors, Analysis::processors());
+ }
}
diff --git a/EXSystApiDocBundle.php b/EXSystApiDocBundle.php
new file mode 100644
index 0000000..13ee40a
--- /dev/null
+++ b/EXSystApiDocBundle.php
@@ -0,0 +1,45 @@
+addCompilerPass(new AddDescribersPass());
+ $container->addCompilerPass(new AddRouteDescribersPass());
+ $container->addCompilerPass(new AddModelDescribersPass());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getContainerExtension()
+ {
+ if (null === $this->extension) {
+ $this->extension = new EXSystApiDocExtension();
+ }
+ if ($this->extension) {
+ return $this->extension;
+ }
+ }
+}
diff --git a/Model/Model.php b/Model/Model.php
new file mode 100644
index 0000000..82693fe
--- /dev/null
+++ b/Model/Model.php
@@ -0,0 +1,37 @@
+type = $type;
+ }
+
+ /**
+ * @return Type|null
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ public function getHash()
+ {
+ return md5(serialize($this->type));
+ }
+}
diff --git a/Model/ModelRegistry.php b/Model/ModelRegistry.php
new file mode 100644
index 0000000..197a336
--- /dev/null
+++ b/Model/ModelRegistry.php
@@ -0,0 +1,132 @@
+modelDescribers = $modelDescribers;
+ $this->api = $api;
+ }
+
+ public function register(Model $model): string
+ {
+ $hash = $model->getHash();
+ if (isset($this->names[$hash])) {
+ return '#/definitions/'.$this->names[$hash];
+ }
+
+ $this->names[$hash] = $name = $this->generateModelName($model);
+ $this->models[$hash] = $model;
+ $this->unregistered[] = $hash;
+
+ // Reserve the name
+ $this->api->getDefinitions()->get($name);
+
+ return '#/definitions/'.$name;
+ }
+
+ /**
+ * @internal
+ */
+ public function registerDefinitions()
+ {
+ while (count($this->unregistered)) {
+ $tmp = [];
+ foreach ($this->unregistered as $hash) {
+ $tmp[$this->names[$hash]] = $this->models[$hash];
+ }
+ $this->unregistered = [];
+
+ foreach ($tmp as $name => $model) {
+ $schema = null;
+ foreach ($this->modelDescribers as $modelDescriber) {
+ if ($modelDescriber instanceof ModelRegistryAwareInterface) {
+ $modelDescriber->setModelRegistry($this);
+ }
+ if ($modelDescriber->supports($model)) {
+ $schema = new Schema();
+ $modelDescriber->describe($model, $schema);
+
+ break;
+ }
+ }
+
+ if (null === $schema) {
+ throw new \LogicException(sprintf('Schema of type "%s" can\'t be generated, no describer supports it.', $this->typeToString($model->getType())));
+ }
+
+ $this->api->getDefinitions()->set($name, $schema);
+ }
+ }
+ }
+
+ private function generateModelName(Model $model): string
+ {
+ $definitions = $this->api->getDefinitions();
+ $base = $name = $this->getTypeShortName($model->getType());
+ $i = 1;
+ while ($definitions->has($name)) {
+ ++$i;
+ $name = $base.$i;
+ }
+
+ return $name;
+ }
+
+ private function getTypeShortName(Type $type)
+ {
+ if (null !== $type->getCollectionValueType()) {
+ return $this->getTypeShortName($type->getCollectionValueType()).'[]';
+ }
+ if (Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType()) {
+ $parts = explode('\\', $type->getClassName());
+
+ return end($parts);
+ }
+
+ return $type->getBuiltinType();
+ }
+
+ private function typeToString(Type $type): string
+ {
+ if (Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType()) {
+ return $type->getClassName();
+ } elseif ($type->isCollection()) {
+ if (null !== $type->getCollectionValueType()) {
+ return $this->typeToString($type->getCollectionValueType()).'[]';
+ } else {
+ return 'mixed[]';
+ }
+ } else {
+ return $type->getBuiltinType();
+ }
+ }
+}
diff --git a/ModelDescriber/CollectionModelDescriber.php b/ModelDescriber/CollectionModelDescriber.php
new file mode 100644
index 0000000..c75d7ee
--- /dev/null
+++ b/ModelDescriber/CollectionModelDescriber.php
@@ -0,0 +1,35 @@
+setType('array');
+ $schema->getItems()->setRef(
+ $this->modelRegistry->register(new Model($model->getType()->getCollectionValueType()))
+ );
+ }
+
+ public function supports(Model $model)
+ {
+ return $model->getType()->isCollection() && null !== $model->getType()->getCollectionValueType();
+ }
+}
diff --git a/ModelDescriber/ModelDescriberInterface.php b/ModelDescriber/ModelDescriberInterface.php
new file mode 100644
index 0000000..698226c
--- /dev/null
+++ b/ModelDescriber/ModelDescriberInterface.php
@@ -0,0 +1,22 @@
+propertyInfo = $propertyInfo;
+ }
+
+ public function describe(Model $model, Schema $schema)
+ {
+ $schema->setType('object');
+ $properties = $schema->getProperties();
+
+ $class = $model->getType()->getClassName();
+ foreach ($this->propertyInfo->getProperties($class) as $propertyName) {
+ $types = $this->propertyInfo->getTypes($class, $propertyName);
+ if (0 === count($types)) {
+ throw new \LogicException(sprintf('The PropertyInfo component was not able to guess the type of %s::$%s', $class, $propertyName));
+ }
+ if (count($types) > 1) {
+ throw new \LogicException(sprintf('Property %s::$%s defines more than one type.', $class, $propertyName));
+ }
+
+ $properties->get($propertyName)->setRef(
+ $this->modelRegistry->register(new Model($types[0]))
+ );
+ }
+ }
+
+ public function supports(Model $model)
+ {
+ return Type::BUILTIN_TYPE_OBJECT === $model->getType()->getBuiltinType();
+ }
+}
diff --git a/ModelDescriber/ScalarModelDescriber.php b/ModelDescriber/ScalarModelDescriber.php
new file mode 100644
index 0000000..bf2c3c5
--- /dev/null
+++ b/ModelDescriber/ScalarModelDescriber.php
@@ -0,0 +1,36 @@
+ 'integer',
+ Type::BUILTIN_TYPE_FLOAT => 'float',
+ Type::BUILTIN_TYPE_STRING => 'string',
+ ];
+
+ public function describe(Model $model, Schema $schema)
+ {
+ $type = self::$supportedTypes[$model->getType()->getBuiltinType()];
+ $schema->setType($type);
+ }
+
+ public function supports(Model $model)
+ {
+ return isset(self::$supportedTypes[$model->getType()->getBuiltinType()]);
+ }
+}
diff --git a/NelmioApiDocBundle.php b/NelmioApiDocBundle.php
index 7834209..e356c05 100644
--- a/NelmioApiDocBundle.php
+++ b/NelmioApiDocBundle.php
@@ -12,6 +12,7 @@
namespace Nelmio\ApiDocBundle;
use Nelmio\ApiDocBundle\DependencyInjection\Compiler\AddDescribersPass;
+use Nelmio\ApiDocBundle\DependencyInjection\Compiler\AddModelDescribersPass;
use Nelmio\ApiDocBundle\DependencyInjection\Compiler\AddRouteDescribersPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
@@ -24,6 +25,7 @@ final class NelmioApiDocBundle extends Bundle
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new AddDescribersPass());
+ $container->addCompilerPass(new AddModelDescribersPass());
$container->addCompilerPass(new AddRouteDescribersPass());
}
}
diff --git a/Resources/config/api_platform.xml b/Resources/config/api_platform.xml
index 51a15c1..e52e283 100644
--- a/Resources/config/api_platform.xml
+++ b/Resources/config/api_platform.xml
@@ -8,7 +8,7 @@
-
+
diff --git a/Resources/config/services.xml b/Resources/config/services.xml
index 1a7296e..14bc892 100644
--- a/Resources/config/services.xml
+++ b/Resources/config/services.xml
@@ -5,7 +5,8 @@
-
+
+
@@ -18,7 +19,6 @@
-
@@ -33,16 +33,31 @@
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Resources/config/swagger_php.xml b/Resources/config/swagger_php.xml
index 0ef8082..e582117 100644
--- a/Resources/config/swagger_php.xml
+++ b/Resources/config/swagger_php.xml
@@ -10,7 +10,16 @@
-
+
+
+
+
+
+
+
+
+
+
diff --git a/SwaggerPhp/AddDefaults.php b/SwaggerPhp/AddDefaults.php
new file mode 100644
index 0000000..2618d70
--- /dev/null
+++ b/SwaggerPhp/AddDefaults.php
@@ -0,0 +1,37 @@
+getAnnotationsOfType(Info::class)) {
+ return;
+ }
+ if (($annotations = $analysis->getAnnotationsOfType(Swagger::class)) && null !== $annotations[0]->info) {
+ return;
+ }
+
+ $analysis->addAnnotation(new Info(['title' => '', 'version' => '0.0.0', '_context' => new Context(['generated' => true])]), null);
+ }
+}
diff --git a/SwaggerPhp/ModelRegister.php b/SwaggerPhp/ModelRegister.php
new file mode 100644
index 0000000..84a203c
--- /dev/null
+++ b/SwaggerPhp/ModelRegister.php
@@ -0,0 +1,84 @@
+modelRegistry = $modelRegistry;
+ }
+
+ public function __invoke(Analysis $analysis)
+ {
+ foreach ($analysis->annotations as $annotation) {
+ if (!$annotation instanceof ModelAnnotation || $annotation->_context->not('nested')) {
+ continue;
+ }
+
+ if (!is_string($annotation->type)) {
+ // Ignore invalid annotations, they are validated later
+ continue;
+ }
+
+ $parent = $annotation->_context->nested;
+ if (!$parent instanceof Response && !$parent instanceof Parameter && !$parent instanceof Schema) {
+ continue;
+ }
+
+ $annotationClass = Schema::class;
+ if ($parent instanceof Schema) {
+ $annotationClass = Items::class;
+ }
+
+ $parent->merge([new $annotationClass([
+ 'ref' => $this->modelRegistry->register(new Model($this->createType($annotation->type))),
+ ])]);
+
+ // It is no longer an unmerged annotation
+ foreach ($parent->_unmerged as $key => $unmerged) {
+ if ($unmerged === $annotation) {
+ unset($parent->_unmerged[$key]);
+
+ break;
+ }
+ }
+ $analysis->annotations->detach($annotation);
+ }
+ }
+
+ private function createType(string $type): Type
+ {
+ if ('[]' === substr($type, -2)) {
+ return new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, null, $this->createType(substr($type, 0, -2)));
+ }
+
+ return new Type(Type::BUILTIN_TYPE_OBJECT, false, $type);
+ }
+}
diff --git a/Tests/Describer/SwaggerPhpDescriberTest.php b/Tests/Describer/SwaggerPhpDescriberTest.php
index 788ba65..0102f2e 100644
--- a/Tests/Describer/SwaggerPhpDescriberTest.php
+++ b/Tests/Describer/SwaggerPhpDescriberTest.php
@@ -11,7 +11,9 @@
namespace Nelmio\ApiDocBundle\Tests\Describer;
+use EXSyst\Component\Swagger\Swagger;
use Nelmio\ApiDocBundle\Describer\SwaggerPhpDescriber;
+use Nelmio\ApiDocBundle\Model\ModelRegistry;
class SwaggerPhpDescriberTest extends AbstractDescriberTest
{
@@ -27,5 +29,6 @@ class SwaggerPhpDescriberTest extends AbstractDescriberTest
protected function setUp()
{
$this->describer = new SwaggerPhpDescriber(__DIR__.'/../Fixtures');
+ $this->describer->setModelRegistry(new ModelRegistry([], new Swagger()));
}
}
diff --git a/Tests/Functional/Controller/ApiController.php b/Tests/Functional/Controller/ApiController.php
index 68874b4..f8c62d6 100644
--- a/Tests/Functional/Controller/ApiController.php
+++ b/Tests/Functional/Controller/ApiController.php
@@ -14,6 +14,9 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Controller;
use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Controller\Annotations\RequestParam;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
+use Nelmio\ApiDocBundle\Annotation\Model;
+use Nelmio\ApiDocBundle\Tests\Functional\Entity\Dummy;
+use Nelmio\ApiDocBundle\Tests\Functional\Entity\User;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Swagger\Annotations as SWG;
@@ -35,11 +38,19 @@ class ApiController
/**
* @Route("/swagger/implicit", methods={"GET", "POST"})
- * @SWG\Response(response="201", description="Operation automatically detected")
+ * @SWG\Response(
+ * response="201",
+ * description="Operation automatically detected",
+ * @Model(type="Nelmio\ApiDocBundle\Tests\Functional\Entity\User")
+ * )
* @SWG\Parameter(
* name="foo",
- * in="query",
- * description="This is a parameter"
+ * in="body",
+ * description="This is a parameter",
+ * @SWG\Schema(
+ * type="array",
+ * @Model(type="Nelmio\ApiDocBundle\Tests\Functional\Entity\User")
+ * )
* )
*/
public function implicitSwaggerAction()
diff --git a/Tests/Functional/Entity/Dummy.php b/Tests/Functional/Entity/Dummy.php
index ac4399a..f7e22ad 100644
--- a/Tests/Functional/Entity/Dummy.php
+++ b/Tests/Functional/Entity/Dummy.php
@@ -42,11 +42,6 @@ class Dummy
*/
private $name;
- /**
- * @var array
- */
- private $foo;
-
public function getId(): int
{
return $this->id;
@@ -65,8 +60,4 @@ class Dummy
public function hasRole(string $role)
{
}
-
- public function setFoo(array $foo = null)
- {
- }
}
diff --git a/Tests/Functional/Entity/User.php b/Tests/Functional/Entity/User.php
new file mode 100644
index 0000000..39fb774
--- /dev/null
+++ b/Tests/Functional/Entity/User.php
@@ -0,0 +1,26 @@
+
+ */
+class User
+{
+ public function addUsers(User $user)
+ {
+ }
+
+ public function setDummy(Dummy $dummy)
+ {
+ }
+}
diff --git a/Tests/Functional/FunctionalTest.php b/Tests/Functional/FunctionalTest.php
index 4a5c5e6..94cc167 100644
--- a/Tests/Functional/FunctionalTest.php
+++ b/Tests/Functional/FunctionalTest.php
@@ -11,6 +11,8 @@
namespace Nelmio\ApiDocBundle\Tests\Functional;
+use EXSyst\Component\Swagger\Operation;
+use EXSyst\Component\Swagger\Schema;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class FunctionalTest extends WebTestCase
@@ -50,11 +52,16 @@ class FunctionalTest extends WebTestCase
$responses = $operation->getResponses();
$this->assertTrue($responses->has('201'));
- $this->assertEquals('Operation automatically detected', $responses->get('201')->getDescription());
+ $response = $responses->get('201');
+ $this->assertEquals('Operation automatically detected', $response->getDescription());
+ $this->assertEquals('#/definitions/User', $response->getSchema()->getRef());
$parameters = $operation->getParameters();
- $this->assertTrue($parameters->has('foo', 'query'));
- $this->assertEquals('This is a parameter', $parameters->get('foo', 'query')->getDescription());
+ $this->assertTrue($parameters->has('foo', 'body'));
+ $parameter = $parameters->get('foo', 'body');
+
+ $this->assertEquals('This is a parameter', $parameter->getDescription());
+ $this->assertEquals('#/definitions/User', $parameter->getSchema()->getItems()->getRef());
}
public function implicitSwaggerActionMethodsProvider()
@@ -109,6 +116,27 @@ class FunctionalTest extends WebTestCase
$operation = $this->getOperation('/api/dummies/{id}', 'get');
}
+ public function testUserModel()
+ {
+ $model = $this->getModel('User');
+ $this->assertEquals('object', $model->getType());
+ $properties = $model->getProperties();
+
+ $this->assertTrue($properties->has('users'));
+ $this->assertEquals('#/definitions/User[]', $properties->get('users')->getRef());
+
+ $this->assertTrue($properties->has('dummy'));
+ $this->assertEquals('#/definitions/Dummy2', $properties->get('dummy')->getRef());
+ }
+
+ public function testUsersModel()
+ {
+ $model = $this->getModel('User[]');
+ $this->assertEquals('array', $model->getType());
+
+ $this->assertEquals('#/definitions/User', $model->getItems()->getRef());
+ }
+
private function getSwaggerDefinition()
{
static::createClient();
@@ -116,12 +144,20 @@ class FunctionalTest extends WebTestCase
return static::$kernel->getContainer()->get('nelmio_api_doc.generator')->generate();
}
- private function getOperation($path, $method)
+ private function getModel($name): Schema
+ {
+ $definitions = $this->getSwaggerDefinition()->getDefinitions();
+ $this->assertTrue($definitions->has($name));
+
+ return $definitions->get($name);
+ }
+
+ private function getOperation($path, $method): Operation
{
$api = $this->getSwaggerDefinition();
$paths = $api->getPaths();
- $this->assertTrue($paths->has($path), sprintf('Path "%s" does not exist', $path));
+ $this->assertTrue($paths->has($path), sprintf('Path "%s" does not exist.', $path));
$action = $paths->get($path);
$this->assertTrue($action->hasOperation($method), sprintf('Operation "%s" for path "%s" does not exist', $path, $method));
diff --git a/Tests/Model/ModelRegistryTest.php b/Tests/Model/ModelRegistryTest.php
new file mode 100644
index 0000000..8ad5a3a
--- /dev/null
+++ b/Tests/Model/ModelRegistryTest.php
@@ -0,0 +1,41 @@
+setExpectedException('\LogicException', sprintf('Schema of type "%s" can\'t be generated, no describer supports it.', $stringType));
+
+ $registry = new ModelRegistry([], new Swagger());
+ $registry->register(new Model($type));
+ $registry->registerDefinitions();
+ }
+
+ public function unsupportedTypesProvider()
+ {
+ return [
+ [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true), 'mixed[]'],
+ [new Type(Type::BUILTIN_TYPE_OBJECT, false, self::class), self::class],
+ ];
+ }
+}
diff --git a/composer.json b/composer.json
index 0622946..431a599 100644
--- a/composer.json
+++ b/composer.json
@@ -17,6 +17,7 @@
"require": {
"php": "~7.0|~7.1",
"symfony/framework-bundle": "^2.8|^3.0",
+ "symfony/property-info": "^2.8|^3.0",
"exsyst/swagger": "~0.2.3"
},
"require-dev": {