Description
Description
This was already closed in #2931, but I would like you to reconsider.
Since it's quite limiting not being able to use Doctrine's Inheritance mapping and OpenAPI has nowadays quite good polymorphism support thanks to discriminator
, oneOf
, anyOf
etc, I believe it makes sense to reconsider support of this. At beginning, I think most people would be fine if APIP at least allowed serialization/deserialization of properties in subclasses. Schema generator could be adjusted later to generate things like this
Main issue I would like to solve is that serializer does not see properties from subclasses. After further debugging I found that PropertyNameCollectionFactory
and PropertyMetadataFactory
are responsible. However, these factories only accept $resourceClass
and not an actual class that Serializer is serializing/deserializing, hence it's not possible to simply add own implementation of these factories.
Example
#[ApiResource()]
#[Doctrine\ORM\Mapping\Entity()]
#[Doctrine\ORM\Mapping\InheritanceType('SINGLE_TABLE')]
#[Doctrine\ORM\Mapping\DiscriminatorColumn('type', 'string')]
#[Doctrine\ORM\Mapping\DiscriminatorMap(self::DISCRIMINATOR_MAPPING)]
#[Symfony\Component\Serializer\Attribute\DiscriminatorMap('type', self::DISCRIMINATOR_MAPPING)]
abstract class DatasyncConnector {
private const array DISCRIMINATOR_MAPPING = ["SCIM" => SCIMConnector::class, "JSON" => JSONConnector::class];
public string $type;
}
{}
#[ORM\Entity()]
class SCIMConnector extends DatasyncConnector {
public ?string $foo = null;
}
#[ORM\Entity()]
class JSONConnector extends DatasyncConnector {
public ?string $bar = null;
}
Here, I expect serialization will result in
{"type": "SCIM", "foo": "string"}
or
{"type": "JSON", "bar": "string"}
Similarly, deserialization should accept (and write to) these properties
Further context
Currently, I have added support for this in application I'm maintaining by enabling allow_extra_attributes
and implementing custom normalizer:
#[AutoconfigureTag('serializer.normalizer', ['priority' => 1])]
class DatasyncConnectorNormalizer implements NormalizerInterface, DenormalizerInterface
{
public function __construct(
#[Autowire(service: 'serializer.normalizer.object')]
private ObjectNormalizer $normalizer,
#[Autowire(service: 'api_platform.jsonld.normalizer.item')]
private AbstractNormalizer $jsonldNormalizer,
) {}
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed
{
return $this->normalizer->denormalize(
$data,
$type,
$format,
[
AbstractNormalizer::OBJECT_TO_POPULATE => $this->jsonldNormalizer
->denormalize($data, $type, $format, $context),
] + $context,
);
}
public function supportsDenormalization(
mixed $data,
string $type,
?string $format = null,
array $context = [],
): bool {
return true;
}
public function normalize(
mixed $data,
?string $format = null,
array $context = [],
): null|array|\ArrayObject|bool|float|int|string {
\assert($data instanceof DatasyncConnector);
$context['uri_variables'] = ['id' => $data->id];
return $this->jsonldNormalizer->normalize($data, $format, $context)
+ $this->normalizer->normalize($data, $format, $context);
}
public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
{
return true;
}
public function getSupportedTypes(?string $format): array
{
return [DatasyncConnector::class => true];
}
}
Even OpenAPI schema is adjusted thanks to implementing own OpenApiFactory which generates definitions for subclasses, then linking to them from resource like so
new GetCollection(openapi: new OpenApiOperation(responses: [
200 => new Response(content: new \ArrayObject(['application/ld+json' => [
'schema' => [
'type' => 'array',
'items' => [
'oneOf' => [
['$ref' => self::SCHEMA_PATH_PREFIX.'.jsonld-connector.output'],
['$ref' => self::SCHEMA_PATH_PREFIX.'.jsonld-connector.output_MS_GRAPH'],
],
'discriminator' => [
'propertyName' => 'type',
'mapping' => [
DatasyncConnectorType::SCIM->value
=> self::SCHEMA_PATH_PREFIX.'.jsonld-connector.output',
DatasyncConnectorType::JSON->value
=> self::SCHEMA_PATH_PREFIX.'.jsonld-connector.output',
DatasyncConnectorType::MS_GRAPH->value
=> self::SCHEMA_PATH_PREFIX.'.jsonld-connector.output_MS_GRAPH',
],
],
],
],
]])),
])),