Skip to content

[Serializer][PropertyInfo][PropertyAccess] Refactoring plan for AbstractObjectNormalizer and related code #30818

Open
@joelwurtz

Description

@joelwurtz

Hello,

AbstractObjectNormalizer has a long list of ongoing issues and pull request, we got a talk at Paris Symfony Live with @dunglas @fbourigault and @soyuka on how we want to move forward, here is a resume of our talk and what we would like to achieve for the future:

Current State and Context

At the origin this normalizer was done for API Platform and many other third party libraries / project, in order to have a normalizer that is able to normalize and denormalize any data object (specifically doctrine entities). ObjectNormalizer, PropertyNormalizer and GetSetMethodNormalizer were already existing, but were having slightly different beahvior, that's why two abstract class were created AbstractNormalizer and AbstractObjectNormalizer

Over time, lot of missing features in the ObjectNormalizer were added on both the AbstractObjectNormalizer and the AbstractNormalizer as we needed the same features there.

The main problem with the inheritance model is that it's very hard to customize some behavior without having to write a lot of code. For example, the current API Platform Normalizer has a lot of duplicated code as he extends the AbstractObjectNormalizer and must rewrite the logic around setting and getting value.

If we look at the main differences between those 3 normalizers they just have a different way of setting and getting value for a given object:

  • PropertyNormalizer use Reflection to access and set public / protected / private properties
  • GetSetMethodNormalizer use hard coded prefix to get getters and setters on properties
  • ObjectNormalizer use PropertyAccessor to get / set value on properties

Plan

Tests

  • creating a rock solid test suite that handles every features of those 3 normalizers implementing the NormalizerInterface. Thanks to this, the refactor can be handled without breaking the working behavior. Also this test suite would be used by other people that want to provide different ways of dealing with object normalization like:

  • A generated normalizer (like it can be done with the Automapper proposal)

  • A bridge for the JMS serializer

  • ...

See #30888

Feature set

Current normalizers already handle lot of features, but some of them, that are widely used in the JMS/Serializer library are missing. Goal is to provide some of this features or at least extension points.

New Normalizer(s)

We strongly believe that those 3 normalizers can be merged into a single normalizer, each feature that they currently support should be provided by a call to another object:

  • Serializer should have an hard dependency on symfony/property-access component, a bridge interface may be provided to handle how value can be setted or gettted: also should allow a new extension point that will allow virtual property

  • A new interface will be provided ObjectPropertyListExtractorInterface (name can change), that allow getting attributes based on an object. A default implementation will be provided that use the PropertyListExtractorInterface and a class resolver, as the latter one only rely on class name, it may be important to have this distinction (this will allow features that rely on a specific value of the object to get the current property list, like exclusion policy)

See #30904

  • Extracting list of attributes can be delegated to implementations that implements the PropertyListExtractorInterface or ObjectPropertyListExtractorInterface

See #30960

  • Groups, Max Depth, Attributes, IgnoredAttributes, Ignore and many other features that influence the way an attribute is allowed or disallowed on a normalizer can be also delegated to the extraction mechanism by implementing the PropertyListExtractorInterface or ObjectPropertyListExtractorInterface

See #30980

  • Instancing an object can be delegated to a new interface with implementations that will handle this part (something like InstantiatorInterface), which will provide a new extension point

See #30956

  • Name converter will certainly stay the same

  • Circular Reference could be delegated to a new Normalizer that decore another normalizer if possible (if not it will stay in this normalizer)

  • Discriminator Handling could be delegated to a new denormalizer that decore another denormalizer: as this is mainly to find the correct class when denormalizing

PropertyInfo

  • Add a new extraction extension point for accessor and mutators.

See #30704

PropertyAccess

  • Add a hard dependency on property-info component and use the ReadAccessor / WriteMutator to handle accessing property on a object or an array

See #30704

Context

All of those interface / extensions point will work with a $context variable. Like the http-client component, this $context will be an array.

  • Each implementation will have a $defaultContext attribute in the constructor to handle global configuration.-

See #38542

  • Each different context will have a ContextBuilder class that allow a API that play nicely with IDE and discovery for setting the configuration (like the HttpOptions class).

See #43973

Cache

Provide cache mechanism for each of those extension point:

  • Attributes extraction cache for the PropertyListExtractorInterface only, AttributeListExtractorInterface cannot have a cache by its volatile nature
  • Instantiator Cache: Construction without calling constructor can be cached
  • Accessor / Mutator cache: calling accessor / mutator can be cached by using code generation or other mechanism (like closure binding) to speed up this process

Cache will vary depending on the context, so a context hash mecanism should be provided.

Contracts

Some of the Serializer interfaces may be moved to the contracts component

Final thoughts

We want to tackle a lot of this work during the upcoming Hackthon, and i would like to thanks all people that contribute issues and pull requests on the serializer component.

We try to respect a lot of your work and problems by making this plan, if you think that there is some features missing or some wrong approach, feel free to respond here.

Thanks @soyuka for the review ot this plan.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions