Nelmioapidocbundle: Show parameter description from class

Created on 29 Jul 2016  ·  18Comments  ·  Source: nelmio/NelmioApiDocBundle

Hello,

I want to display the description of my parameters in the /api/doc page.
Is this possible without adding the parameters manually?
I want to import it from the input/output class.

This is how my documentation looks:

unbenannt

Here ist my ApiDoc of the controller action:

 * @ApiDoc(
 *     section = "user",
 *     resource = true,
 *     description = "Checks the user credentials and returns an authentication & refresh token if they are correct",
 *     input = { "class" = "AppBundle\Libraries\Core\User\LoginRequest", "name" = "" },
 *     output = { "class" = "AppBundle\Libraries\Core\User\LoginResponse", "name" = "" },
 *      statusCodes = {
 *          200 = "Returned when successful",
 *          400 = "Returned when request syntax is incorrect",
 *          404 = "Returned when the page is not found",
 *          429 = "Returned when the client sent too many requests during a time period",
 *          500 = "Returned when an internal server error occured",
 *          501 = "Returned when an unavailable request method is user (GET, POST, DELETE, PUT, ...)",
 *          503 = "Returned when the service is unavailable at the moment eg. due to maintenance or overload"
 *      },
 *
 * )

AppBundleLibrariesCoreUserLoginRequest class:

class LoginRequest implements JsonSerializable
{
/**
* The username.
*
* @var string
*
* @AssertNotBlank()
* @AssertType("string")
*/
public $username;

/**
 * The password.
 *
 * @var string
 *
 * @Assert\NotBlank()
 * @Assert\Type("string")
 */
public $password;

/**
 * Defines whether or not to save the refresh token as cooke.
 *
 * @var bool
 *
 * @Assert\NotBlank()
 * @Assert\Type("bool")
 */
public $rememberPassword;

public function getUsername()
{
    return $this->username;
}

public function setUsername($username)
{
    $this->username = $username;
}

public function getPassword()
{
    return $this->password;
}

public function setPassword($password)
{
    $this->password = $password;
}

public function getRememberPassword()
{
    return $this->rememberPassword;
}

public function setRememberPassword($rememberPassword)
{
    $this->rememberPassword = $rememberPassword;
}

public function jsonSerialize()
{
    return [
            'username' => $this->username,
            'password' => $this->password,
            'rememberPassword' => $this->rememberPassword
    ];
}

}

I would like to use the desciptions of this class, eg. for username: "The username.".

Greetings
Orlando

Most helpful comment

1 year later.. any help would be great :)

All 18 comments

/**
     * @ApiDoc(
     *     output="Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest",
     *     input={
     *         "class" = "Nelmio\ApiDocBundle\Tests\Fixtures\Model\JmsTest"
     *     },
     *     parameters={
     *          {
     *              "name"="number",
     *              "dataType"="integer",
     *              "actualType"="string",
     *              "subType"=null,
     *              "required"=true,
     *              "description"="This is the new description",
     *              "readonly"=false,
     *              "sinceVersion"="v3.0",
     *              "untilVersion"="v4.0"
     *          },
     *          {
     *              "name"="arr",
     *              "dataType"="object (ArrayCollection)"
     *          },
     *          {
     *              "name"="nested",
     *              "dataType"="object (JmsNested)",
     *              "children": {
     *                  "bar": {
     *                      "dataType"="integer",
     *                      "format"="d+"
     *                  }
     *              }
     *          }
     *     }
     * )
     */

可以设置parameters参数来表明

  • 查看源码才找到的。。心好累==
SwaggerFormatter.php  

            if (isset($data['parameters'])) {
                $parameters = array_merge($parameters, $this->deriveParameters($data['parameters'], $input['paramType']));
                }

ApiDoc.php

        if (isset($data['parameters'])) {
            foreach ($data['parameters'] as $parameter) {
                if (!isset($parameter['name'])) {
                    throw new \InvalidArgumentException('A "parameter" element has to contain a "name" attribute');
                }
                if (!isset($parameter['dataType'])) {
                    throw new \InvalidArgumentException(sprintf(
                        '"%s" parameter element has to contain a "dataType" attribute',
                        $parameter['name']
                    ));
                }
                $name = $parameter['name'];
                unset($parameter['name']);
                $this->addParameter($name, $parameter);
            }
        }

参考链接

TestController
SwaggerFormatter
ApiDoc

结果

@ApiDoc( description="登录验证", input={"class"="UniworkTransaction\ParameterBundle\Parameter\LoginArgsJson", "options"={"method" = "POST"}, }, parameters={ {"name"="UserAccount","dataType"="integer","actualType"="string","description"="hello world!"} }, output={"class"="UniworkTransaction\ParameterBundle\Parameter\CommonArgsJson"} )
Parameters
Parameter   Type    Required?   Format  Description
UserAccount     integer     false       hello world!
UserPassword    string  false       
ExtraInfo   string  false

1 year later.. any help would be great :)

verison 3 now has swagger-php support.

This is also in version 3 still an issue:

@SWG\Parameter( name="isoAlpha2", in="query", type="string")

The model:

/**
     * 2-character ISO 3166-1 country code
     *
     * @var string
     *
     * @ORM\Column(name="iso_alpha2", type="string", length=2, precision=0, scale=0, nullable=false, unique=true)
     * @Assert\NotBlank()
     * @Assert\Length(
     *      min = 2,
     *      max = 2
     * )
     * @JMS\Groups({"Country"})
     */
    private $isoAlpha2;

This is with version 3.1.0.

Expected Result:

  • Description shows in docs for isoAlpha2.

Actual Result:

  • Description does NOT show in docs.
    image

Additional Info

  • Would be nice if this would work for the type as well, but this field seems to be mandatory for query params. Alternatively model could be added as a new type.

Not quite sure if this is really a bug or if there is another way to do this.

Any workaround or solution would save me having to rewrite annotations for 7+ massive Controllers with which I have to start immediately otherwise :/

Thanks.

I think the problem is that we store this summary in title. Could you please try to update setTitle to setDescription in https://github.com/nelmio/NelmioApiDocBundle/blob/master/ModelDescriber/Annotations/PropertyPhpDocReader.php#L57 and see if it solves your issue?

Hi @GuilhemN, thanks for answering so quickly!
If I make this change I can see in the debugger that you are right and this is stored in the title. However, it still doesn't show up in the docs :(

EDIT: After further research, I found...

  1. If this method is called for my @Model it has the following debug values:
    image
    Looking at this It seems to be stored in docBlock->summary as well.
  2. If I remove my model from the response, the request does NOT hit this block of code. This indicates that @Parameter behaves differently to @Model.

And does it change something if you also update ...->getTitle to ...->getDescription?

If I remove my model from the response, the request does NOT hit this block of code. This indicates that @parameter behaves differently to @model.

Models are added to the docs only if they're used somewhere in your code. So if I understood correctly, this is normal.

In last ressort, could you please submit a PR with a failing case to help us understand the issue?

Hi @GuilhemN,
if I change getTitle to getDescription it does not have any effect. As mentioned before, debugger shows, that the entire method is not being called for @Parameter.

I have the same model model in the Response, but nowhere in the request:

    /**
     * Gets list of Countries
     * @SWG\Tag(name="Country")
     *
     * @SWG\Parameter(name="orderBy", in="query", type="string")
     * @SWG\Parameter(name="offset", in="query", type="integer")
     * @SWG\Parameter(name="limit", in="query", type="integer")
     *
     * @SWG\Parameter(name="isoAlpha2", in="query", type="string")
     * @SWG\Parameter(name="isoAlpha3", in="query", type="string")
     * @SWG\Parameter(name="nameEn", in="query", type="string")
     * @SWG\Parameter(name="currencyCode", in="query", type="string")
     *
     * @SWG\Response(
     *     response="200",
     *     description="Returns countries",
     *     @SWG\Schema(
     *         type="array",
     *         @Model(type=MyBundle\Entity\Country::class, groups={"Country"})
     *     )
     * )
     * @SWG\Response(response="400", description="Returned when the request data has incorrect format")
     * @SWG\Response(response="401", description="Returned when not authenticated")
     * @SWG\Response(response="403", description="Returned when not having permissions")
     * @SWG\Response(response="404", description="Returned when object not found")
     */

In the above example isoAlpha2, isoAlpha3, nameEn and currencyCode all exist in the Model specified in the Response.

So if I understand you correctly, a parameter which has a corresponding property in a model, not specified by the parameter (eg. a filter) cannot automatically load the description from the model annotation?
... or is there a way to specify something like a "Parameter Property"? I'm just trying to avoid having to redefine not just the description, but also validation params, etc.
Thanks.

The problem is in NelmioApiDocBundleModelDescriberJMSModelDescriber:
php if (null !== $item->reflection) { $this->annotationsReader->updateProperty($item->reflection, $property); }

For specific items reflection is null, so we never get into NelmioApiDocBundleModelDescriberAnnotationsPropertyPhpDocReader.

This is related to the fact that VirtualPropertyMetadata does not support reflection

Possible fix:

  • Add ReflectionMethod support to JMSSerializerMetadataVirtualPropertyMetadata::__construct
  • Add ReflectionMethod support to JMSSerializerMetadataVirtualPropertyMetadata::unserialize
  • Copy NelmioApiDocBundleModelDescriberAnnotationsPropertyPhpDocReader::updateProperty to updateMethod changing typehint
  • Create NelmioApiDocBundleModelDescriberAnnotationsAnnotationsReader::updateMethod that only calls phpDocReader
  • Add check for to NelmioApiDocBundleModelDescriberJMSModelDescriber, call AnnotationsReader::updateMethod if $item->reflection instanceof ReflectionMethod
  • Clear JMS cache. Something like rm -Rf var/cache/dev/jms_serializer && mkdir -p var/cache/dev/jms_serializer

I went through these steps locally, now descriptions are present. I plan to add two patches to composer, as I don't have time to create pull requests ATM

Patch for serializer:
````
--- a/vendor/jms/serializer/src/JMS/Serializer/Metadata/VirtualPropertyMetadata.php
+++ b/vendor/jms/serializer/src/JMS/Serializer/Metadata/VirtualPropertyMetadata.php
@@ -32,6 +32,7 @@ class VirtualPropertyMetadata extends PropertyMetadata
$this->name = $fieldName;
$this->getter = $methodName;
$this->readOnly = true;

  • $this->reflection = new ReflectionMethod($this->class, $methodName);
    }
 public function setValue($obj, $value)

@@ -103,5 +104,8 @@ class VirtualPropertyMetadata extends PropertyMetadata
if (isset($unserialized['excludeIf'])) {
$this->excludeIf = $unserialized['excludeIf'];
}
+

  • $this->reflection = new ReflectionMethod($this->class, $this->getter);
  • $this->reflection->setAccessible(true);
    }
    }

````

Patch for this bundle:
````
--- a/vendor/nelmio/api-doc-bundle/ModelDescriber/Annotations/PropertyPhpDocReader.php
+++ b/vendor/nelmio/api-doc-bundle/ModelDescriber/Annotations/PropertyPhpDocReader.php
@@ -60,4 +60,36 @@ class PropertyPhpDocReader
$property->setDescription($docBlock->getDescription()->render());
}
}
+

  • /**

    • Update the Swagger information with information from the DocBlock comment.

  • */
  • public function updateMethod(ReflectionMethod $reflectionProperty, Schema $property)
  • {
  • try {
  • $docBlock = $this->docBlockFactory->create($reflectionProperty);
  • } catch (Exception $e) {
  • // ignore
  • return;
  • }
    +
  • if (!$title = $docBlock->getSummary()) {
  • /** @var Var_ $var */
  • foreach ($docBlock->getTagsByName('var') as $var) {
  • if (!$description = $var->getDescription()) {
  • continue;
  • }
  • $title = $description->render();
  • if ($title) {
  • break;
  • }
  • }
  • }
  • if (null === $property->getTitle() && $title) {
  • $property->setTitle($title);
  • }
  • if (null === $property->getDescription() && $docBlock->getDescription() && $docBlock->getDescription()->render()) {
  • $property->setDescription($docBlock->getDescription()->render());
  • }
  • }
    }

--- a/vendor/nelmio/api-doc-bundle/ModelDescriber/Annotations/AnnotationsReader.php
+++ b/vendor/nelmio/api-doc-bundle/ModelDescriber/Annotations/AnnotationsReader.php
@@ -46,4 +46,9 @@ class AnnotationsReader
$this->swgAnnotationsReader->updateProperty($reflectionProperty, $property);
$this->symfonyConstraintAnnotationReader->updateProperty($reflectionProperty, $property);
}
+

  • public function updateMethod(ReflectionMethod $reflectionMethod, Schema $property)
  • {
  • $this->phpDocReader->updateMethod($reflectionMethod, $property);
  • }
    }

--- a/vendor/nelmio/api-doc-bundle/ModelDescriber/JMSModelDescriber.php
+++ b/vendor/nelmio/api-doc-bundle/ModelDescriber/JMSModelDescriber.php
@@ -73,7 +73,12 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn

         // read property options from Swagger Property annotation if it exists
         if (null !== $item->reflection) {

- $this->annotationsReader->updateProperty($item->reflection, $property);
+ if ($item->reflection instanceof ReflectionProperty) {
+ $this->annotationsReader->updateProperty($item->reflection, $property);
+ } else {
+ $this->annotationsReader->updateMethod($item->reflection, $property);
+ }
+
}

         if (null !== $property->getType()) {

````

@dbu should we reopen this issue?

the serializer can probably not be changed. jms serializer is not very actively maintained.

if you have a code change for this repository, best send a pull request. can we do the reflection thing in this bundle instead of in the jms serializer?

@nick4fake I'm not sure that's the issue here, the sample @phpPhil provided doesn't seem to be using virtual properties.

Is this behaviour working as intended or does it need a fix?
Apologies for my ignorance, but I'm rather new to NelmioApiDocBundle, Swagger and JMS :)

@nick4fake thanks for your efforts, that's great. I'm not using VPs though and didn't want to introduce them, especially as I thought this would work out-of-the-box.

@phpPhil it's most likely a bug but we need to find what's the issue. Creating a failing test would make it easier for us :)

I think we need to open a new issue, the original issue here is unrelated so I won't reopen it.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

numediaweb picture numediaweb  ·  4Comments

manseuk picture manseuk  ·  6Comments

andydandy80 picture andydandy80  ·  4Comments

abidichrak picture abidichrak  ·  5Comments

astronati picture astronati  ·  3Comments