From 14ae7411158fc7b95f6058c984f412ab4c48770e Mon Sep 17 00:00:00 2001 From: Guilhem N Date: Sat, 14 Jan 2017 17:36:56 +0100 Subject: [PATCH] Add models support --- Annotation/Model.php | 29 ++++ ApiDocGenerator.php | 16 ++- .../Compiler/AddModelDescribersPass.php | 30 ++++ Describer/DefaultDescriber.php | 2 +- Describer/ModelRegistryAwareInterface.php | 19 +++ Describer/ModelRegistryAwareTrait.php | 24 ++++ Describer/RouteDescriber.php | 8 +- Describer/SwaggerPhpDescriber.php | 48 ++++--- EXSystApiDocBundle.php | 45 ++++++ Model/Model.php | 37 +++++ Model/ModelRegistry.php | 132 ++++++++++++++++++ ModelDescriber/CollectionModelDescriber.php | 35 +++++ ModelDescriber/ModelDescriberInterface.php | 22 +++ ModelDescriber/ObjectModelDescriber.php | 55 ++++++++ ModelDescriber/ScalarModelDescriber.php | 36 +++++ NelmioApiDocBundle.php | 2 + Resources/config/api_platform.xml | 2 +- Resources/config/services.xml | 25 +++- Resources/config/swagger_php.xml | 11 +- SwaggerPhp/AddDefaults.php | 37 +++++ SwaggerPhp/ModelRegister.php | 84 +++++++++++ Tests/Describer/SwaggerPhpDescriberTest.php | 3 + Tests/Functional/Controller/ApiController.php | 17 ++- Tests/Functional/Entity/Dummy.php | 9 -- Tests/Functional/Entity/User.php | 26 ++++ Tests/Functional/FunctionalTest.php | 46 +++++- Tests/Model/ModelRegistryTest.php | 41 ++++++ composer.json | 1 + 28 files changed, 793 insertions(+), 49 deletions(-) create mode 100644 Annotation/Model.php create mode 100644 DependencyInjection/Compiler/AddModelDescribersPass.php create mode 100644 Describer/ModelRegistryAwareInterface.php create mode 100644 Describer/ModelRegistryAwareTrait.php create mode 100644 EXSystApiDocBundle.php create mode 100644 Model/Model.php create mode 100644 Model/ModelRegistry.php create mode 100644 ModelDescriber/CollectionModelDescriber.php create mode 100644 ModelDescriber/ModelDescriberInterface.php create mode 100644 ModelDescriber/ObjectModelDescriber.php create mode 100644 ModelDescriber/ScalarModelDescriber.php create mode 100644 SwaggerPhp/AddDefaults.php create mode 100644 SwaggerPhp/ModelRegister.php create mode 100644 Tests/Functional/Entity/User.php create mode 100644 Tests/Model/ModelRegistryTest.php 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": {