Skip to content

Instantly share code, notes, and snippets.

@joelwurtz
Last active April 1, 2019 14:27
Show Gist options
  • Save joelwurtz/cc3a909b878fbfcd2184a799c666a095 to your computer and use it in GitHub Desktop.
Save joelwurtz/cc3a909b878fbfcd2184a799c666a095 to your computer and use it in GitHub Desktop.

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, lots of missing features in the ObjectNormalizer were added on both the AbstractObjectNormalizer and the AbstractNormalizer as these features were also needed there.

Main problem of that inheritance model is the fact this 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 then must rewrite all the logic about 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 of an object:

  • PropertyNormalizer use Reflection to access and set public / protected / private properties
  • GetSetMethodNormalizer use hard coded prefix to get and all a getter and setter on properties
  • ObjectNormalizer use PropertyAccessor to get / set value on properties

Plan

Tests

  • Goal is to create a rock solid test suite that handle all existing features of those 3 normalizers and can be used with any NormalizerInterface, so this refactoring can be handled with the fear of breaking existing behavior. Also this test suite can be used by other people that want to provide other way of dealing with object normalization like:

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

  • A bridge for the JMS serializer

  • ...

Feature set

Current normalizers already handle a 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 the same normalizer, and that 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 AttributeListExtractorInterface (name can change), that allow getting attributes based on an object. A default implementation will be provided that use the PropertyListInfoExtractorInterface 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)

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

  • 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 AttributeListExtractorInterface

  • 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

  • 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

  • Finish the work on symfony/symfony#30704 to add a new extraction extension point for accessor and mutators.

PropertyAcess

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

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.

  • 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).

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment