nelmio / NelmioApiDocBundle

Generates documentation for your REST API from annotations
MIT License
2.23k stars 833 forks source link

The PropertyInfo component was not able to guess the type of Doctrine\Common\Collections\Collection::$element #1542

Open Taronyuu opened 5 years ago

Taronyuu commented 5 years ago

I'm trying to generate a response based on a model entity. On the controller I'm using the following docblock:

 /**
     * @Rest\Post("/business", name="create")
     * @SWG\Parameter(name="body", in="body", @Model(type=Business::class))
     * @SWG\Response(response=200, description="Create a new business")
     * @param ValidatorInterface     $validator
     * @param Request                $request
     * @param EntityManagerInterface $entityManager
     *
     * @return \Symfony\Component\HttpFoundation\JsonResponse|null
     */
    public function create( ValidatorInterface $validator, Request $request, EntityManagerInterface $entityManager )

The moment I add the @Model(type=Business::class) to the block the following error is being thrown:

LogicException:
The PropertyInfo component was not able to guess the type of Doctrine\Common\Collections\Collection::$element

  at vendor/nelmio/api-doc-bundle/ModelDescriber/ObjectModelDescriber.php:83
  at Nelmio\ApiDocBundle\ModelDescriber\ObjectModelDescriber->describe(object(Model), object(Schema))
     (vendor/nelmio/api-doc-bundle/Model/ModelRegistry.php:89)
  at Nelmio\ApiDocBundle\Model\ModelRegistry->registerDefinitions()
     (vendor/nelmio/api-doc-bundle/ApiDocGenerator.php:72)
  at Nelmio\ApiDocBundle\ApiDocGenerator->generate()
     (vendor/nelmio/api-doc-bundle/Controller/SwaggerUiController.php:53)
  at Nelmio\ApiDocBundle\Controller\SwaggerUiController->__invoke(object(Request), 'default')
     (vendor/symfony/http-kernel/HttpKernel.php:151)
  at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
     (vendor/symfony/http-kernel/HttpKernel.php:68)
  at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
     (vendor/symfony/http-kernel/Kernel.php:198)
  at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
     (public/index.php:25)

On the Entity itself I've got all relations/collections tagged with the actual class like here:

   /**
     * @ORM\OneToMany(targetEntity="App\Entity\User", mappedBy="current_business")
     * @SWG\Items(type=User::class)
     */
    private $users;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Business", inversedBy="businesses")
     * @Model(type=Business::class)
     */
    private $parent;

As far as I can see I've done this for all models that are being used (actually all of them in the project). In the end what I'm trying to achieve is to not have to define all properties for a request in the docblock, but use some kind of class that defines how a request should look like. A bit like the Laravel request object does. (https://laravel.com/docs/5.7/validation#form-request-validation)

There are 3 things I'd like to know. 1) Is there a way to debug on what property in which class it is failing? 2) Why is it failing? 3) Is there a way to do what I'm trying to achieve? (To have some kind of class instead of having to define all request properties in the docblocks)

GuilhemN commented 5 years ago

Is there a way to debug on what property in which class it is failing?

We could add the class hierarchy to the error, that would be quite interesting in your case indeed.

Why is it failing?

@Model is not supported directly, you have to use @SWG\Property(ref=@Model(Business::class). Though I don't think this is what's breaking here (your type should be detected from the doctrine annotation).

I think you have to specify the type of the property businesses to an array of Business. We should probably automatically map Collection to array as this is most of the time what people want...

haroldiedema commented 5 years ago

The error you're experiencing originates from Symfony's PropertyInfo component.

See related issue here; #1432

It breaks in your case because you're trying to expose a doctrine collection to Swagger, which - as far as I know - isn't supported.

gempir commented 2 years ago

Is there a way to define a class as a specific type instead of the property? I can easily fix this issue from OP by using something like

/**
 * @OA\Property(
 *     type="array",
 *     @OA\Items(ref=@Model(type=ClassOfTheCollectionItem::class))
 * )
 */

But I have to define that on the property, not on the class itself. Which means a lot of repeating. Ideally I would have it generic on my Abstract Collection so you only ever type something like

/**
 * @extends AbstractCollection<string>
 */
class StringCollection extends AbstractCollection

And the abstract Collection has something like

/**
*  @template T
 * @OA\Property(
 *     type="array",
 *     @OA\Items(ref=@Model(type=T))
 * )
 */

And this would automatically be converted to an array in my open api docs.

But generics is a bit harder so an Idea how I could just define my Type on a class basis would be a great start already and not on a per property basis.