Api Platform conference
Register now

Enums as API Resources

API Platform provides support for PHP 8.1+ BackedEnums, allowing them to be exposed as first-class API resources. This enables clients to discover available enum cases and their associated metadata directly through your API.

# Exposing BackedEnums

To expose a BackedEnum as an API resource, simply apply the #[ApiResource] attribute to your enum class:

<?php
// api/src/Enum/Status.php
namespace App\Enum;

use ApiPlatform\Metadata\ApiResource;

#[ApiResource]
enum Status: int
{
    case DRAFT = 0;
    case PUBLISHED = 1;
    case ARCHIVED = 2;
}

By default, API Platform will automatically generate GET and GET Collection operations for your enum resource. The enum’s value will be used as the identifier for individual enum cases.

# Default Operations and Identifiers

  • Collection: GET /statuses will return a collection of all enum cases.
  • Item: GET /statuses/{value} will return a single enum case based on its value.

Example GET /statuses response:

[
    {
        "name": "DRAFT",
        "value": 0
    },
    {
        "name": "PUBLISHED",
        "value": 1
    },
    {
        "name": "ARCHIVED",
        "value": 2
    }
]

Example GET /statuses/1 response:

{
    "name": "PUBLISHED",
    "value": 1
}

# Customizing Enum Resources

You can customize the behavior of enum resources using standard API Platform attributes.

# Custom Identifier

If you wish to use a property other than value as the identifier, or to expose additional data, you can implement methods within your enum and mark them with #[ApiProperty]. For instance, to use the enum name as an identifier, you can implement getId():

<?php
// api/src/Enum/Audit.php
namespace App\Enum;

use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;

#[ApiResource]
enum Audit: string
{
    case Pending = 'pending';
    case Passed = 'passed';
    case Failed = 'failed';

    #[ApiProperty(identifier: true)]
    public function getId(): string
    {
        return $this->name;
    }
}

# Adding Custom Properties

You can add custom properties to your enum resource by defining public methods and marking them with #[ApiProperty]:

<?php
// api/src/Enum/Status.php
namespace App\Enum;

use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;

#[ApiResource]
enum Status: int
{
    case DRAFT = 0;
    case PUBLISHED = 1;
    case ARCHIVED = 2;

    #[ApiProperty]
    public function getDescription(): string
    {
        return match ($this) {
            self::DRAFT => 'Article is not ready for public consumption',
            self::PUBLISHED => 'Article is publicly available',
            self::ARCHIVED => 'Article content is outdated or superseded',
        };
    }
}

With the above, GET /statuses/0 might return:

{
    "name": "DRAFT",
    "value": 0,
    "description": "Article is not ready for public consumption"
}

# Custom State Providers

For more advanced customization, you can implement custom state providers for your enum resources. A common pattern is to use a trait to provide common functionality:

<?php
// api/src/Enum/EnumApiResourceTrait.php
namespace App\Enum;

use ApiPlatform\Metadata\Operation;
use BackedEnum;

trait EnumApiResourceTrait
{
    public function getId(): string|int
    {
        return $this->value;
    }

    public function getValue(): int|string
    {
        return $this->value;
    }

    public static function getCases(): array
    {
        return self::cases();
    }

    public static function getCase(Operation $operation, array $uriVariables): ?BackedEnum
    {
        $id = is_numeric($uriVariables['id']) ? (int) $uriVariables['id'] : $uriVariables['id'];

        return array_reduce(self::cases(), static fn($c, BackedEnum $case) => $case->name === $id || $case->value === $id ? $case : $c, null);
    }
}

Then, apply the trait and specify the providers in your enum:

<?php
// api/src/Enum/Audit.php
namespace App\Enum;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;

#[ApiResource]
#[GetCollection(provider: Audit::class.'::getCases')]
#[Get(provider: Audit::class.'::getCase')]
enum Audit: string
{
    use EnumApiResourceTrait;

    case Pending = 'pending';
    case Passed = 'passed';
    case Failed = 'failed';
}

# Enums as Property Values

When an enum is used as a property in another ApiResource, it will be serialized by its value by default.

Consider an Article resource with a Status enum property:

<?php
// api/src/ApiResource/Article.php
namespace App\ApiResource;

use ApiPlatform\Metadata\ApiResource;
use App\Enum\Status; // Import your enum

#[ApiResource]
class Article
{
    public ?int $id = null;
    public ?string $title = null;
    public ?Status $status = null; // Enum property
}

The serialization of Article will include the value of the Status enum:

{
    "id": 1,
    "title": "Once Upon A Title",
    "status": 1
}

The OpenAPI schema will also correctly represent the enum as its backing type (integer for Status):

{
    "Article": {
        "type": "object",
        "properties": {
            "id": {
                "readOnly": true,
                "type": "integer"
            },
            "title": {
                "type": "string"
            },
            "status": {
                "type": "integer",
                "enum": [0, 1, 2] // Enum values derived from the BackedEnum
            }
        }
    }
}

# Referencing Enum Resources as Property Values

If you have exposed an enum as an ApiResource (e.g., #[ApiResource] on Status enum), and then use that enum as a property in another resource (e.g., Article::$status), API Platform will serialize it as an IRI (Internationalized Resource Identifier) by default.

<?php
// api/src/ApiResource/Article.php
namespace App\ApiResource;

use ApiPlatform\Metadata\ApiResource;
use App\Enum\Status; // Assume Status is also an ApiResource

#[ApiResource]
class Article
{
    public ?int $id = null;
    public ?string $title = null;
    public ?Status $status = null; // This will now be an IRI
}

The serialization of Article will include an IRI for the Status enum:

{
    "id": 1,
    "title": "Once Upon A Title",
    "status": "/statuses/1"
}

If you prefer the enum to be serialized by its value instead of an IRI, even when it’s an ApiResource, you might need to adjust your serialization context or create a custom normalizer if the default behavior doesn’t suit your needs.

You can also help us improve the documentation of this page.

Using an AI coding agent? See the documentation index for LLMs at /docs/llms.txt.

Made with love by

Les-Tilleuls.coop can help you design and develop your APIs and web projects, and train your teams in API Platform, Symfony, Next.js, Kubernetes and a wide range of other technologies.

Learn more

Copyright © 2023 Kévin Dunglas

Sponsored by Les-Tilleuls.coop