API Platform 4.1 - Documentation at the heart of API Discoverability

API Platform is a framework centered around API standards, assisting developers in maintaining and utilizing these specifications. With API Platform 4.1, our focus is on enhancing support for the Hydra specification, OpenAPI, and JSON Schema.

# OpenAPI improvements

# Create several versions of a specification

You can now decline a same OpenAPI specification in multiple versions using the x-apiplatform-tags tag:

use ApiPlatform\OpenApi\Factory\OpenApiFactory;

#[GetCollection(openapi: new Operation(extensionProperties: [OpenApiFactory::API_PLATFORM_TAG => ['customer', 'developer']]))]
#[Post(openapi: new Operation(extensionProperties: [OpenApiFactory::API_PLATFORM_TAG => 'developer']))]
class Book {}

Then, either use the query parameter for the web version such as /docs?filter_tags[]=customer or through the command line:

bin/console api:openapi:export --filter-tags=customer

This will produce a specification including only the operations matching your tag.

# Error schemas

With the improvement of our implementation of the Problem Detail specification in 4.0 and the ability to declare error as API Resources, we were one step closer to being able to document their JSON Schema. Since 3.4, you could already add errors to an operation’s documentation:

#[GetCollection(errors: [MyDomainException::class])]
class Greeting
{
// ...
}

This is now also enabled for every standard responses we support. For example a 422 Unprocessable entity is now documented in every supported formats:

Swagger example 422

The JSON Schema associated with an error is also available with more friendly documentation:

error json schema

# Hydra

The hydra patch changes default hydra:title and uses the resource shortname. Previously the hydra:title information was duplicating the hydra:description. The rdfs:label got removed from the hydra:Class as it was used instead of the hydra:title. On hydra:property rdfs:label got renamed to label as the rdfs namespace is available in the context.

The ApiPlatform\Metadata\ErrorResource and the ConstraintViolation (ValidationException class) are now generated directly from your PHP classes, only our ConstraintViolationList is hard-written and documents the ConstraintViolation::violation property. Therefore, your own error resources are also documented. On top of that, we now set the rdfs:subClassOf to hydra:Error.

It’s possible to hide an hydra operation (#[Get(hideHydraOperation: true)]), and to skip a documented property using #[ApiProperty(hydra: false)] on a class.

On write operations, we added the expectsHeader field.

# Query Parameters and filtering

You can now let API Platform send a validation error when a query parameter is used on your API but you don’t support it by enabling strictQueryParameters: true (globally using defaults, on a resource or an operation). On top of that vinceAmstoutz and myself worked on improving the declaration of Symfony filters within query parameters.

#[GetCollection(
    parameters: [
        'enabled' => new QueryParameter(
            filter: new BooleanFilter(),
            property: 'active',
        ),
    ],
)]
#[ORM\Entity]
class FilteredBooleanParameter
{
}

Previously, a filter had to be a Symfony service; now it’s a static instance. A filter is a callback where you should have everything needed to do the filtering:

public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void;

Therefore you shouldn’t need any dependency injection. We’ll never deprecate using a service but we feel that this approach is more user-friendly and it also improves to decoupling of the documentation. For that you get two new interfaces ApiPlatform\Metadata\JsonSchemaFilterInterface and ApiPlatform\Metadata\OpenApiParameterFilterInterface:

use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
use ApiPlatform\Metadata\JsonSchemaFilterInterface;

public class MyDateFilter implements JsonSchemaFilterInterface, OpenApiParameterFilterInterface
    /**
     * @return array<string, string>
     */
    public function getSchema(Parameter $parameter): array
    {
        return ['type' => 'date'];
    }

    public function getOpenApiParameters(Parameter $parameter): OpenApiParameter|array|null
    {
        $in = $parameter instanceof QueryParameter ? 'query' : 'header';
        $key = $parameter->getKey();

        return [
            new OpenApiParameter(name: $key.'[after]', in: $in),
            new OpenApiParameter(name: $key.'[before]', in: $in),
            new OpenApiParameter(name: $key.'[strictly_after]', in: $in),
            new OpenApiParameter(name: $key.'[strictly_before]', in: $in),
        ];
    }

OpenAPI Parameter will use the getSchema if none is provided. This gives more control on a filter without having to copy that filter to just change a part of the documentation.

# Laravel

Along Laravel 12 support (^4.0.19), a BooleanFilter was added, and we automatically register the model’s Policy when found. We improved the overall support of Eloquent. The default caching of our Metadata has been set to the file cache as it is more appropriate then using the global cache (you can still change this). Special thanks to toitzi and amermchaudhary for providing many fixes since the Laravel release!

# And more…

GromNaN from MongoDB improved the overall memory usage of our MongoDB collection provider! Elasticsearch 7 is supported again, we can configure GraphQl max query depth and max query complexity and so much more!

Check the full detailed release notes on Github.

Thanks to every contributor!

# What’s up next?

We’re working on adding more filters and refactoring our SearchFilter. We will also focus on JSON Schema generation as they’re far from perfect, especially that we studied a nice optimisation where we mutualize Schemas with base schemas per formats. Take a sneak peak here. On the side we’re working on an Hypermedia HTTP Client that’s still in an experimentation phase.