Repository navigation
feat(serializer): support resource polymorphism through DiscriminatorMap - #8620
dylan-rumble wants to merge 4 commits into
Conversation
5488d58 to
2e7aa3c
Compare
2e7aa3c to
15ae810
Compare
|
I just realised the code as is will not read the |
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 ? |
|
I think as is its fairly close to mergable, but I won't have time for a while to spend time into looking into the tests. I will get around to it eventually. I did review #7815 when creating this one, it didn't account for a lot of things. I no longer have the comprehensive list anymore. I'll let you know when I finish the PR. |
|
Hi @dylan-rumble, thanks for this PR! I tested it on a small app (Symfony 7.4, PHP 8.4, SQLite) with a Doctrine-only setup, and wanted to share some feedback. Test case:
|
29635ac to
d611ed3
Compare
|
Thanks a lot for testing this and for the detailed feedback @philpichet!
I've updated the PR description accordingly. |
…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>
…arent schema Every oneOf branch already carries the common properties, repeating them next to oneOf made Swagger UI display them twice. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
345e28d to
42c9822
Compare
|
Following up on the question about #7815 and whether this is close to mergeable. Comparison with #7815 I compared both PRs. This one covers what #7815 does, and also:
Adapting #7815 would mean re-implementing most of this, so I think it's simpler to continue here. Thanks to Maxcastel for the groundwork, which helped shape this. What I think is mergeable
The remaining CI failures also happen on Next steps Before asking for a review, I'll test this in our production application, including the Doctrine opt-in. I'll post here when it's ready for review. |
|
@soyuka I extensively tested all API operations and everything works:
Could you review the PR when you have a moment? Thanks! |
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 a serializer map are exposed. An unmapped subclass is still serialized as its closest mapped parent, and resources without a map behave exactly as before. Deriving the map from Doctrine inheritance is a separate opt-in (
discriminator_map, disabled by default), see below.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 payloadsDiscriminatedSelfMappedResourceTestDoctrineDiscriminatedResourceTestORM\DiscriminatorMaponly (discriminator_mapenabled throughDoctrineDiscriminatorMapAppKernel): 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, OpenAPIDoctrineDiscriminatedDocumentTestUnit 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 OpenAPIdiscriminator: the common properties are not repeated next tooneOf, so Swagger UI doesn't show them twice. Subtypes never reference the base, so there are no circular refs.oneOfbranch describing its own instances.additionalProperties.discriminatoris used. The base<Resource>.jsonapi.resourcedefinition holds aoneOfof one<Resource>.jsonapi.<Subtype>.resourcedefinition per subtype.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.api-platform/json-schema,api-platform/serializerandapi-platform/json-apinow requireapi-platform/metadata ^5.1(forClassDiscriminatorHelper). Likedoctrine-orm/doctrine-odmonmain, their "lowest"/"minimal-changes" jobs can't resolve until 5.1 is released.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. The base definition only lists its subtypes: noproperties,requiredoradditionalPropertiesof its own.Behavior changes for existing Doctrine inheritance resources
None by default:
discriminator_mapis disabled, andTableInheritanceTestis unchanged.Once enabled:
#[ApiResource]are serialized as their own subtype instead of as the parent.#[DiscriminatorMap]subset still takes precedence, to only expose some subtypes.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.