Skip to content

Issues generating multiple schema formats when decorating SchemaFactory #6228

Closed
@GwendolenLynch

Description

@GwendolenLynch

API Platform version(s) affected: 3.2

Description
I am working on a PR to introduce a JSON:API schema factory and hit a couple of related bugs that feel better suited to a separate issue/PR combination.

I have working "fixes" laid out below, but would like them reviewed & sanity checked before I create a PR for this.

First problem was that ApiPlatform\Hydra\JsonSchema\SchemaFactory#addDistinctFormat() checks for ApiPlatform\JsonSchema\SchemaFactory, where as ApiPlatform\Hal\JsonSchema\SchemaFactory simply does a method_exists call.

No priority is set on any of the service decorators, and Hydra's, by chance, is currently the one with the base service .inner. However, my new service took first place and it became impossible to build both schema formats at the same time as the last one "won out". Changing Hydra's method behaviour to be a method_exists call fixes this.

The second is just as subtle. The base ApiPlatform\JsonSchema\SchemaFactory calls the injected decorator service to enable subschema generation (see #6055). When the Hydra service is the one decorating the base this works fine, but the introduction of another service causes that one to be called instead, meaning Hydra misses out (see #5950)

How to reproduce

final class SchemaFactory implements SchemaFactoryInterface
{
    public function __construct(private readonly SchemaFactoryInterface $schemaFactory)
    {
        $this->addDistinctFormat('jsonapi');
        if ($this->schemaFactory instanceof SchemaFactoryAwareInterface) {
            $this->schemaFactory->setSchemaFactory($this);
        }
    }

    public function buildSchema(string $className, string $format = 'jsonapi', string $type = Schema::TYPE_OUTPUT, ?Operation $operation = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema
    {
        $schema = $this->schemaFactory->buildSchema($className, $format, $type, $operation, $schema, $serializerContext, $forceCollection);

        if ('jsonapi' !== $format) {
            return $schema;
        }

        // Do schema updates here …

        return $schema;
    }

    public function addDistinctFormat(string $format): void
    {
        // The Hydra schema factory does:
        //  if ($this->schemaFactory instanceof SchemaFactoryAwareInterface) {
        //
        // The HAL one does as below:
        if (method_exists($this->schemaFactory, 'addDistinctFormat')) {
            $this->schemaFactory->addDistinctFormat($format);
        }
    }
}

Obligatory service definition 😇

    api_platform.jsonapi.json_schema.schema_factory:
        class: 'ApiPlatform\JsonApi\JsonSchema\SchemaFactory'
        decorates: 'api_platform.json_schema.schema_factory'
        arguments:
            - '@api_platform.jsonapi.json_schema.schema_factory.inner'

And API Platform config:

api_platform:
    formats:
        json: [ 'application/json' ]
        jsonld: [ 'application/ld+json' ]
        jsonhal: [ 'application/hal+json' ]
        jsonapi: [ 'application/vnd.api+json' ]

Possible Solution

  1. Change Hydra SchemaFactory#addDistinctFormat() to use a method_exists call
  2. Have all SchemaFactorys implement SchemaFactoryAwareInterface 😞

Obviously neither of those are particularly elegant, hence this discussion. Help and guidance most welcome!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions