Skip to content

Model (Class) Customization

Edit this page

The bundle uses a various model describers to generate the OpenAPI schema for your models. You can create your own model describer to customize how your models are represented in the documentation.

This is useful when you have custom value objects or when you want to represent your models in a different way than the default describers do.

Note

Model describers are used for entire models (classes), while property describers are used for the individual properties of a model. A model will also be documented in the generated components.schemas section of the OpenAPI documentation, while properties are documented directly in the schema.

Note

Model describers are not chained like type describers. Only one model describer will be used for a given model. If multiple describers support the same model, the one with the highest priority will be used.

For example, let's say you have a Money value object that you want to represent as a string with a specific format in your API documentation:

1
2
3
4
5
6
7
namespace App\Entity;

class Money
{
    public int $cents;
    public string $currency;
}

Creating a custom Model Describer

To create a custom model describer, you need to create a class that implements the ModelDescriberInterface. This interface has two methods:

  • supports(Model $model): bool: This method should return true if your describer can handle the given model.
  • describe(Model $model, Schema $schema): void: This method should populate the OpenAPI Schema for the given model.

You can create a custom model describer for this Money class like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
namespace App\ModelDescriber;

use App\Entity\Money;
use Nelmio\ApiDocBundle\Model\Model;
use Nelmio\ApiDocBundle\ModelDescriber\ModelDescriberInterface;
use OpenApi\Annotations\Schema;
use Symfony\Component\TypeInfo\Type\ObjectType;

class MoneyModelDescriber implements ModelDescriberInterface
{
    public function describe(Model $model, Schema $schema): void
    {
        $schema->type = 'string';
        $schema->example = '12.34 EUR';
        $schema->description = 'A monetary value represented as a string.';
    }

    public function supports(Model $model): bool
    {
        $type = $model->getTypeInfo();
        if (!$type instanceof ObjectType) {
            return false;
        }

        return Money::class === $type->getClassName();
    }
}

Registering the custom Model Describer

If you are using Symfony's default services.yaml configuration, your custom model describer will be automatically registered and tagged thanks to autoconfiguration!

If you're not using autoconfigure or if you need to set a priority to make sure your describer runs before or after other describers, you can configure it manually in your services.yaml:

1
2
3
4
5
6
7
8
# config/services.yaml
services:
    # ...

    App\ModelDescriber\MoneyModelDescriber:
        tags:
            # register the model describer with a high priority (called earlier)
            - { name: 'nelmio_api_doc.model_describer', priority: 500 }

Self-Describing Models

Another way to customize model documentation is by implementing the SelfDescribingModelInterface. This is particularly useful when the model itself is the best place to hold its documentation.

If your model implements this interface, its describe method will be called to populate the schema.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace App\Entity;

use Nelmio\ApiDocBundle\Model\Model;
use Nelmio\ApiDocBundle\ModelDescriber\SelfDescribingModelInterface;
use OpenApi\Annotations\Schema;

class Money implements SelfDescribingModelInterface
{
    public static function describe(Schema $schema, Model $model): void
    {
        $schema->type = 'string';
        $schema->example = '12.34 EUR';
        $schema->description = 'A monetary value represented as a string.';
    }
}

Example Output

With the above customizations, the generated components.schemas section will include the following definition for the Money model:

1
2
3
4
5
6
7
8
9
10
11
{
    "components": {
        "schemas": {
            "Money": {
                "type": "string",
                "example": "12.34 EUR",
                "description": "A monetary value represented as a string."
            }
        }
    }
}

Decorating Built-in Model Describers

NelmioApiDocBundle also provides various built-in model describers.

You can decorate these describers to extend or modify their behavior.

For example, if you want to customize how enums are represented (for example, to help with client code generation), you can create a custom model describer that decorates or replaces the built-in enum describer.

1
2
3
4
5
6
7
8
namespace App\Entity;

enum Status: string
{
    case ACTIVE = 'active';
    case INACTIVE = 'inactive';
    case PENDING = 'pending';
}
1
2
3
4
5
6
# config/services.yaml
services:
    App\ModelDescriber\MyEnumDescriber:
        decorates: 'nelmio_api_doc.model_describer.enum'
        # pass the old service as an argument
        arguments: ['@.inner']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
namespace App\ModelDescriber;

use Nelmio\ApiDocBundle\Model\Model;
use Nelmio\ApiDocBundle\ModelDescriber\ModelDescriberInterface;
use OpenApi\Annotations\Schema;
use Symfony\Component\TypeInfo\Type\EnumType;

class MyEnumDescriber implements ModelDescriberInterface
{
    public function __construct(
        private ModelDescriberInterface $decorates,
    ) {
    }

    public function describe(Model $model, Schema $schema): void
    {
        $this->decorates->describe($model, $schema);

        /**
         * @var class-string<BackedEnum> $enumClass
         */
        $enumClass = $model->getType()->getClassName();

        $xEnumVarNames = [];
        foreach ($enumClass::cases() as $enumCase) {
            $xEnumVarNames[] = $enumCase->name;
        }

        $schema->x = [
            'enum-varnames' => $xEnumVarNames,
        ];
    }

    public function supports(Model $model): bool
    {
        return $this->decorates->supports($model);
    }
}

Expected Output

With the above decorator, the generated schema for an enum will include the x-enum-varnames extension:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
    "components": {
        "schemas": {
            "Status": {
                "type": "string",
                "enum": [
                    "active",
                    "inactive",
                    "pending"
                ],
                "x-enum-varnames": [
                    "ACTIVE",
                    "INACTIVE",
                    "PENDING"
                ]
            }
        }
    }
}
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version