feat(serializer): support resource polymorphism through DiscriminatorMap - #8620
dylan-rumble wants to merge 2 commits into
Conversation
5488d58 to
2e7aa3c
Compare
2e7aa3c to
15ae810
Compare
|
I just realised the code as is will not read the |
…tor maps Expose the ORM and MongoDB ODM inheritance discriminator map of an API resource hierarchy as a serializer discriminator map, so subtypes are read, written and documented without declaring them twice. An explicit serializer DiscriminatorMap in the hierarchy takes precedence. Every non-leaf class gets a map of itself and its subclasses, so resources declared mid-hierarchy resolve their own subtypes. Enabled by default; disable with api_platform.doctrine.discriminator_map or api_platform.doctrine_mongodb_odm.discriminator_map. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2634e95 to
29635ac
Compare
|
@soyuka without going in depth into the code in the PR, what is your opinion on this feature? it would allow both reads and writes to entities using a discriminator column with any sort of InheritanceType. For custom database types that map json to objects for example, the Symfony DiscriminatorMap can be used. Right now we have to make a seperate api resource for every single subclass in our code that uses doctrine inheritance. |
|
Yeah its quite interesting I haven't yet looked at the code @Maxcastel has something going on at #7815 I think that with both pr we should be able to get some quite nice new feature. Not sure how to go forward, I can read the code if you think its close to be mergeable, let me know. Can you also run an agent to check if #7815 could maybe be adapted ? |
A resource can now declare its subtypes with Symfony's
#[DiscriminatorMap]. This works for every operation (GET, GetCollection, POST, PUT, PATCH, DELETE), in JSON-LD, JSON, JSON:API and HAL, and in the generated JSON Schema / OpenAPI.The map is the opt-in. #2931 was closed because exposing subclass properties leaks data. Here, only classes listed in the map are exposed. An unmapped subclass is still serialized as its closest mapped parent, and resources without a map behave exactly as before.
Tests
DiscriminatedResourceTestsecurity, unmapped subclasses, resources without a map, attribute cache across users, OpenAPIDiscriminatedResourceOverrideTestApiPropertyand constraints, narrowed relations, covariant gettersDiscriminatedNestedResourceTestkind+breed),allow_extra_attributes: false, schema validated against real payloadsDiscriminatedSelfMappedResourceTest|
DoctrineDiscriminatedResourceTest| Subtypes fromORM\DiscriminatorMaponly: all operations, JSON:API/HAL, subtypesecurity, a non-resource subtype, POST of a grandchild through a mid-hierarchy resource, JOINED with integer keys, serializer map precedence, OpenAPI ||
DoctrineDiscriminatedDocumentTest| The same for MongoDB ODM (runs in the MongoDB job) |Unit tests:
DoctrineDiscriminatorMappingLoaderTest,SerializerMappingLoaderPassTest, extension/configuration tests for the flag,ClassDiscriminatorHelperTest(including a cyclic map), andSchemaFactoryTestin JsonSchema and JsonApi.How it works
AbstractItemNormalizerisCacheKeySafe()also checks the subtypes' properties.JsonSchema\SchemaFactory/JsonApi\JsonSchema\SchemaFactoryoneOfand an OpenAPIdiscriminatoron the base. Subtypes never reference the base, so there are no circular refs.additionalProperties.ClassDiscriminatorHelper(@internal) is the single walker over the maps. It only goes down to strict subclasses, so it always terminates.?ClassDiscriminatorResolverInterface. It is wired for Symfony and Laravel.Doctrine discriminator maps (second commit)
Doctrine inheritance already declares the subtypes, so declaring them again for the serializer shouldn't be required.
DoctrineDiscriminatorMappingLoaderis a serializer mapping loader that exposes the ORM/ODM discriminator map as a serializerClassDiscriminatorMapping:#[DiscriminatorMap]anywhere in the hierarchy wins, so you can still expose a subset.POST /carscreating aSportsCar) resolves too.serializer.mapping.chain_loaderand the cache warmer bySerializerMappingLoaderPass.DoctrineDiscriminatorSerializerPropertyMetadataFactory(#8283) is untouched; part of it may become redundant, which could be a follow-up.Behavior changes for existing
#[DiscriminatorMap]resourcesNo public API breaks. Resources that already carry a serializer map will see:
oneOfanddiscriminatorin schemas, and noadditionalPropertieson the base definition.Behavior changes for existing Doctrine inheritance resources
With
discriminator_mapenabled (the default):#[ApiResource]are serialized as their own subtype instead of as the parent.TableInheritanceTest::testNonApiResourceChildAppearsAsParentwas updated accordingly.false, or declare a serializer#[DiscriminatorMap]subset, to keep the previous output.If you would rather put the serializer map support behind a configuration flag too, every touch point is listed above and I'm happy to add it.