Last active
February 2, 2025 09:45
-
-
Save vincentchalamon/456fb84af4ddf1281a63a0a06e633c60 to your computer and use it in GitHub Desktop.
Polymorphism with API Platform
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
#[ApiResource( | |
operations: [ | |
// no GetCollection operation | |
new Get(...), | |
new Post(...), | |
new Patch(...), | |
], | |
)] | |
final class CustomCalendarPeriod implements Period | |
{ | |
public const string PERIOD_TYPE = 'custom'; | |
#[Groups(groups: ['period:read'])] | |
public function getPeriodType(): string | |
{ | |
return self::PERIOD_TYPE; | |
} | |
// ... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
#[ApiResource( | |
operations: [ | |
new GetCollection( | |
uriTemplate: '/periods', | |
provider: PeriodCollectionProvider::class, | |
normalizationContext: [ | |
'groups' => ['period:read'], | |
], | |
), | |
], | |
)] | |
interface Period | |
{ | |
public function getPeriodType(): string; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
#[AsDecorator(decorates: 'api_platform.jsonld.context_builder')] | |
final readonly class PeriodsContextBuilder implements AnonymousContextBuilderInterface | |
{ | |
public function __construct(private AnonymousContextBuilderInterface $decorated) | |
{ | |
} | |
/** | |
* Overrides Period context with external contexts for polymorphism. | |
* | |
* @see https://json-ld.org/spec/ED/json-ld-syntax/20120122/#external-contexts | |
* @see https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/ | |
* | |
* @phpstan-ignore-next-line return type has no value type specified in iterable type array | |
*/ | |
public function getResourceContext(string $resourceClass, int $referenceType = UrlGeneratorInterface::ABS_PATH): array | |
{ | |
$context = $this->decorated->getResourceContext($resourceClass, $referenceType); | |
if ($resourceClass === Period::class) { | |
// keep "@vocab" and "hydra" from API Platform JSON-LD context as generic keys | |
// override the rest of the context to disable the generated schema and implement polymorphism | |
return [ | |
['@vocab' => $context['@vocab']], | |
['hydra' => $context['hydra']], | |
'/contexts/SubscribedCalendarPeriod', | |
'/contexts/CustomCalendarPeriod', | |
]; | |
} | |
return $context; | |
} | |
/** | |
* @phpstan-ignore-next-line $context has no value type specified in iterable type array | |
*/ | |
public function getAnonymousResourceContext(object $object, array $context = [], int $referenceType = UrlGeneratorInterface::ABS_PATH): array | |
{ | |
return $this->decorated->getAnonymousResourceContext($object, $context, $referenceType); | |
} | |
/** | |
* @phpstan-ignore-next-line return type has no value type specified in iterable type array | |
*/ | |
public function getBaseContext(int $referenceType = UrlGeneratorInterface::ABS_PATH): array | |
{ | |
return $this->decorated->getBaseContext($referenceType); | |
} | |
/** | |
* @phpstan-ignore-next-line return type has no value type specified in iterable type array | |
*/ | |
public function getEntrypointContext(int $referenceType = UrlGeneratorInterface::ABS_PATH): array | |
{ | |
return $this->decorated->getEntrypointContext($referenceType); | |
} | |
public function getResourceContextUri(string $resourceClass, int $referenceType = UrlGeneratorInterface::ABS_PATH): string | |
{ | |
return $this->decorated->getResourceContextUri($resourceClass, $referenceType); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
#[AsDecorator(decorates: 'api_platform.openapi.factory')] | |
final readonly class PeriodsOpenApiFactory implements OpenApiFactoryInterface | |
{ | |
public function __construct(private OpenApiFactoryInterface $decorated) | |
{ | |
} | |
public function __invoke(array $context = []): OpenApi | |
{ | |
return $this->overrideSchema($this->decorated->__invoke($context)); | |
} | |
/** | |
* Overrides Period schema with polymorphism. | |
* | |
* @see https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/ | |
*/ | |
private function overrideSchema(OpenApi $openApi): OpenApi | |
{ | |
$components = $openApi->getComponents(); | |
$schema = $components->getSchemas() ?? new \ArrayObject(); | |
$schema['Period.jsonld-period.read'] = [ | |
'oneOf' => [ | |
['$ref' => '#/components/schemas/SubscribedCalendarPeriod.jsonld-subscribed-calendar-period.read'], | |
['$ref' => '#/components/schemas/CustomCalendarPeriod.jsonld-custom-calendar-period.read'], | |
], | |
'discriminator' => [ | |
'propertyName' => 'periodType', | |
'mapping' => [ | |
SubscribedCalendarPeriod::PERIOD_TYPE => '#/components/schemas/SubscribedCalendarPeriod.jsonld-subscribed-calendar-period.read', | |
CustomCalendarPeriod::PERIOD_TYPE => '#/components/schemas/CustomCalendarPeriod.jsonld-custom-calendar-period.read', | |
], | |
], | |
]; | |
return $openApi->withComponents($components->withSchemas($schema)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
#[ApiResource( | |
operations: [ | |
// no GetCollection operation | |
new Get(...), | |
new Post(...), | |
new Patch(...), | |
], | |
)] | |
final class SubscribedCalendarPeriod implements Period | |
{ | |
public const string PERIOD_TYPE = 'subscribed'; | |
#[Groups(groups: ['period:read'])] | |
public function getPeriodType(): string | |
{ | |
return self::PERIOD_TYPE; | |
} | |
// ... | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks! It seems to work fine, but in my Swagger UI, I get the following error messages:
Any idea how to fix this? Your help is greatly appreciated.