Skip to content

Instantly share code, notes, and snippets.

@vincentchalamon
Last active June 27, 2024 12:01
Show Gist options
  • Save vincentchalamon/456fb84af4ddf1281a63a0a06e633c60 to your computer and use it in GitHub Desktop.
Save vincentchalamon/456fb84af4ddf1281a63a0a06e633c60 to your computer and use it in GitHub Desktop.
Polymorphism with API Platform
<?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;
}
// ...
}
<?php
#[ApiResource(
operations: [
new GetCollection(
uriTemplate: '/periods',
provider: PeriodCollectionProvider::class,
normalizationContext: [
'groups' => ['period:read'],
],
),
],
)]
interface Period
{
public function getPeriodType(): string;
}
<?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);
}
}
<?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));
}
}
<?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