diff --git a/Exception/UndocumentedArrayItemsException.php b/Exception/UndocumentedArrayItemsException.php new file mode 100644 index 0000000..b44c05f --- /dev/null +++ b/Exception/UndocumentedArrayItemsException.php @@ -0,0 +1,42 @@ +class = $class; + $this->path = $path; + + $propertyName = ''; + if (null !== $class) { + $propertyName = $class.'::'; + } + $propertyName .= $path; + + parent::__construct(sprintf('Property "%s" is an array, but its items type isn\'t specified. You can specify that by using the type `string[]` for instance or `@SWG\Property(type="array", @SWG\Items(type="string"))`.', $propertyName)); + } + + public function getClass() + { + return $this->class; + } + + public function getPath() + { + return $this->path; + } +} diff --git a/ModelDescriber/ObjectModelDescriber.php b/ModelDescriber/ObjectModelDescriber.php index 7a4a269..0f41324 100644 --- a/ModelDescriber/ObjectModelDescriber.php +++ b/ModelDescriber/ObjectModelDescriber.php @@ -15,6 +15,7 @@ use Doctrine\Common\Annotations\Reader; use EXSyst\Component\Swagger\Schema; use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface; use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait; +use Nelmio\ApiDocBundle\Exception\UndocumentedArrayItemsException; use Nelmio\ApiDocBundle\Model\Model; use Nelmio\ApiDocBundle\ModelDescriber\Annotations\AnnotationsReader; use Nelmio\ApiDocBundle\PropertyDescriber\PropertyDescriberInterface; @@ -144,7 +145,15 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar $propertyDescriber->setModelRegistry($this->modelRegistry); } if ($propertyDescriber->supports($type)) { - $propertyDescriber->describe($type, $property, $model->getGroups()); + try { + $propertyDescriber->describe($type, $property, $model->getGroups()); + } catch (UndocumentedArrayItemsException $e) { + if (null !== $e->getClass()) { + throw $e; // This exception is already complete + } + + throw new UndocumentedArrayItemsException($model->getType()->getClassName(), sprintf('%s%s', $propertyName, $e->getPath())); + } return; } diff --git a/PropertyDescriber/ArrayPropertyDescriber.php b/PropertyDescriber/ArrayPropertyDescriber.php index 7055df4..defb84f 100644 --- a/PropertyDescriber/ArrayPropertyDescriber.php +++ b/PropertyDescriber/ArrayPropertyDescriber.php @@ -14,6 +14,7 @@ namespace Nelmio\ApiDocBundle\PropertyDescriber; use EXSyst\Component\Swagger\Schema; use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface; use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait; +use Nelmio\ApiDocBundle\Exception\UndocumentedArrayItemsException; use Symfony\Component\PropertyInfo\Type; class ArrayPropertyDescriber implements PropertyDescriberInterface, ModelRegistryAwareInterface @@ -32,7 +33,7 @@ class ArrayPropertyDescriber implements PropertyDescriberInterface, ModelRegistr { $type = $type->getCollectionValueType(); if (null === $type) { - throw new \LogicException(sprintf('Property "%s" is an array, but its items type isn\'t specified. You can specify that by using the type `string[]` for instance or `@SWG\Property(type="array", @SWG\Items(type="string"))`.', $property->getTitle())); + throw new UndocumentedArrayItemsException(); } $property->setType('array'); @@ -43,7 +44,15 @@ class ArrayPropertyDescriber implements PropertyDescriberInterface, ModelRegistr $propertyDescriber->setModelRegistry($this->modelRegistry); } if ($propertyDescriber->supports($type)) { - $propertyDescriber->describe($type, $property, $groups); + try { + $propertyDescriber->describe($type, $property, $groups); + } catch (UndocumentedArrayItemsException $e) { + if (null !== $e->getClass()) { + throw $e; // This exception is already complete + } + + throw new UndocumentedArrayItemsException(null, sprintf('%s[]', $e->getPath())); + } break; } diff --git a/Tests/Functional/ArrayItemsErrorTest.php b/Tests/Functional/ArrayItemsErrorTest.php new file mode 100644 index 0000000..24acb63 --- /dev/null +++ b/Tests/Functional/ArrayItemsErrorTest.php @@ -0,0 +1,37 @@ + 'api.example.com']); + } + + public function testModelPictureDocumentation() + { + $this->expectException(UndocumentedArrayItemsException::class); + $this->expectExceptionMessage('Property "Nelmio\ApiDocBundle\Tests\Functional\Entity\ArrayItemsError\Bar::things[]" is an array, but its items type isn\'t specified.'); + + $this->getSwaggerDefinition(); + } + + protected static function createKernel(array $options = []) + { + return new TestKernel(TestKernel::ERROR_ARRAY_ITEMS); + } +} diff --git a/Tests/Functional/BazingaFunctionalTest.php b/Tests/Functional/BazingaFunctionalTest.php index beab5fd..ab82fe6 100644 --- a/Tests/Functional/BazingaFunctionalTest.php +++ b/Tests/Functional/BazingaFunctionalTest.php @@ -122,6 +122,6 @@ class BazingaFunctionalTest extends WebTestCase protected static function createKernel(array $options = []) { - return new TestKernel(true, true); + return new TestKernel(TestKernel::USE_JMS | TestKernel::USE_BAZINGA); } } diff --git a/Tests/Functional/Controller/ArrayItemsErrorController.php b/Tests/Functional/Controller/ArrayItemsErrorController.php new file mode 100644 index 0000000..6bbabbd --- /dev/null +++ b/Tests/Functional/Controller/ArrayItemsErrorController.php @@ -0,0 +1,35 @@ + + */ +class Bar +{ + public $things; + + public function addThing(array $thing) { } +} diff --git a/Tests/Functional/Entity/ArrayItemsError/Foo.php b/Tests/Functional/Entity/ArrayItemsError/Foo.php new file mode 100644 index 0000000..c9d7704 --- /dev/null +++ b/Tests/Functional/Entity/ArrayItemsError/Foo.php @@ -0,0 +1,28 @@ + + */ +class Foo +{ + /** + * @var string + */ + public $articles; + + /** + * @var Bar[] + */ + public $bars; +} diff --git a/Tests/Functional/JMSFunctionalTest.php b/Tests/Functional/JMSFunctionalTest.php index 990f181..1532ca0 100644 --- a/Tests/Functional/JMSFunctionalTest.php +++ b/Tests/Functional/JMSFunctionalTest.php @@ -304,6 +304,6 @@ class JMSFunctionalTest extends WebTestCase protected static function createKernel(array $options = []) { - return new TestKernel(true); + return new TestKernel(TestKernel::USE_JMS); } } diff --git a/Tests/Functional/TestKernel.php b/Tests/Functional/TestKernel.php index 32e9399..edf9e2e 100644 --- a/Tests/Functional/TestKernel.php +++ b/Tests/Functional/TestKernel.php @@ -33,17 +33,19 @@ use Symfony\Component\Serializer\Annotation\SerializedName; class TestKernel extends Kernel { + const USE_JMS = 1; + const USE_BAZINGA = 2; + const ERROR_ARRAY_ITEMS = 4; + use MicroKernelTrait; - private $useJMS; - private $useBazinga; + private $flags; - public function __construct(bool $useJMS = false, bool $useBazinga = false) + public function __construct(int $flags = 0) { - parent::__construct('test'.(int) $useJMS.(int) $useBazinga, true); + parent::__construct('test'.$flags, true); - $this->useJMS = $useJMS; - $this->useBazinga = $useBazinga; + $this->flags = $flags; } /** @@ -61,10 +63,10 @@ class TestKernel extends Kernel new FOSRestBundle(), ]; - if ($this->useJMS) { + if ($this->flags & self::USE_JMS) { $bundles[] = new JMSSerializerBundle(); - if ($this->useBazinga) { + if ($this->flags & self::USE_BAZINGA) { $bundles[] = new BazingaHateoasBundle(); } } @@ -91,11 +93,11 @@ class TestKernel extends Kernel $routes->import(__DIR__.'/Controller/SerializedNameController.php', '/', 'annotation'); } - if ($this->useJMS) { + if ($this->flags & self::USE_JMS) { $routes->import(__DIR__.'/Controller/JMSController.php', '/', 'annotation'); } - if ($this->useBazinga) { + if ($this->flags & self::USE_BAZINGA) { $routes->import(__DIR__.'/Controller/BazingaController.php', '/', 'annotation'); try { @@ -104,6 +106,10 @@ class TestKernel extends Kernel } catch (\ReflectionException $e) { } } + + if ($this->flags & self::ERROR_ARRAY_ITEMS) { + $routes->import(__DIR__.'/Controller/ArrayItemsErrorController.php', '/', 'annotation'); + } } /** @@ -231,7 +237,7 @@ class TestKernel extends Kernel */ public function getCacheDir() { - return parent::getCacheDir().'/'.(int) $this->useJMS; + return parent::getCacheDir().'/'.$this->flags; } /** @@ -239,7 +245,7 @@ class TestKernel extends Kernel */ public function getLogDir() { - return parent::getLogDir().'/'.(int) $this->useJMS; + return parent::getLogDir().'/'.$this->flags; } public function serialize()