Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
7 of 16 tasks
joelwurtz opened this issue Apr 1, 2019 · 7 comments

Comments

@joelwurtz
Copy link
Contributor

joelwurtz commented Apr 1, 2019

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.

symfony-splitter pushed a commit to symfony/serializer that referenced this issue Apr 6, 2019
…terface (dmaicher)

This PR was merged into the 4.3-dev branch.

Discussion
----------

[Serializer] provide new ObjectPropertyListExtractorInterface

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | related to symfony/symfony#30818
| License       | MIT
| Doc PR        | -

EUFOSSA Hackathon

As discussed with @joelwurtz this adds a new `ObjectPropertyListExtractorInterface` and a default implementation.

See symfony/symfony#30818

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

Commits
-------

997270f7ac [Serializer] provide new ObjectPropertyListExtractorInterface
fabpot added a commit that referenced this issue Apr 6, 2019
…terface (dmaicher)

This PR was merged into the 4.3-dev branch.

Discussion
----------

[Serializer] provide new ObjectPropertyListExtractorInterface

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | related to #30818
| License       | MIT
| Doc PR        | -

EUFOSSA Hackathon

As discussed with @joelwurtz this adds a new `ObjectPropertyListExtractorInterface` and a default implementation.

See #30818

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

Commits
-------

997270f [Serializer] provide new ObjectPropertyListExtractorInterface
nicolas-grekas added a commit that referenced this issue Apr 7, 2019
…lwurtz)

This PR was merged into the 4.3-dev branch.

Discussion
----------

[Serializer] Experimental for ObjectListExtractor

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #30904
| License       | MIT
| Doc PR        | -

Related to #30818

I want to mark this class as `@expiremental` until we have the full refactoring done of the Serializer, also this would allow change needed if some behavior was not correctly taken into care in 4.3

Mark also `final` for the default implementation as we don't want that to be extendable and user should use composition over inheritance.

Commits
-------

b0cdf45 Set object list extractor as expiremental, and use final for default implementation
@joelwurtz
Copy link
Contributor Author

Do you want me to merge #30960 #30980 #30956 into the same PR for better visibility, or do you prefer to keep concept separated ? /cc @dunglas @fabpot

fabpot added a commit that referenced this issue Apr 27, 2019
This PR was squashed before being merged into the 4.3-dev branch (closes #30888).

Discussion
----------

[serializer] extract normalizer tests to traits

eufossa

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | Relates to #30818
| License       | MIT
| Doc PR        | -

As discussed with @joelwurtz, extract normalizer functionality tests into traits to ensure consistent behaviour of all normalizers.

* [x] Rebase when #30977, #30950 and #30907 are merged to master **blocker**
* [x] Clean up order of trait inclusion and methods in the tests
* [x] Clean up fixture classes of the traits. I started having one class named the same as the trait, where possible

Stuff that we should do eventually, but can also do in separate pull requests, after this one has been merged:
* [ ] Extract all features that we can (the existing normalizer tests should more or less only have the legacy tests in them, all functionality should be in trait)
* [ ] Run test coverage and increase coverage so that we cover all important features and all relevant error cases.

Commits
-------

2b6ebea [serializer] extract normalizer tests to traits
@fabpot
Copy link
Member

fabpot commented Aug 17, 2020

@joelwurtz How can we move forward with this issue and the related PRs? Best could be to dedicate some time (online?) with involved people (to be defined) to try to reach a point where we can actually merge something? I would be more than happy to help and organize such a "mini-hackaton". /cc @dunglas

@dunglas
Copy link
Member

dunglas commented Aug 17, 2020

I would be glad to attend to such hackathon too.

@fabpot
Copy link
Member

fabpot commented Sep 21, 2020

@joelwurtz Friendly ping, do you think you could dedicate some time to organize a meetup to help move forward the various changes you proposed?

@joelwurtz
Copy link
Contributor Author

Thanks for the ping, didn't see the first one (lost in notifications during holidays ^^).

Such a mini hackaton would definitely help with @Korbeil also as he tries to take most of the work.
It's a great idea as we both need a lot of communication with someone in charge of this component to make things right.

I am mostly available during classic work time (should be the same for @Korbeil),

@carsonbot
Copy link

Thank you for this issue.
There has not been a lot of activity here for a while. Has this been resolved?

@dunglas
Copy link
Member

dunglas commented Mar 22, 2021

No @carsonbot, it's still on the roadmap.

@carsonbot carsonbot removed the Stalled label Mar 22, 2021
chalasr added a commit that referenced this issue Feb 2, 2022
This PR was merged into the 6.1 branch.

Discussion
----------

[Serializer] Add context builders

| Q             | A
| ------------- | ---
| Branch?       | 6.1
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | Fixes partially #30818
| License       | MIT
| Doc PR        | TODO

This PR introduces ContextBuilders as discussed in #30818.

The main idea here is to introduce an abstract context builder that could be extended to create concrete context builders.
These context builders will allow serialization context construction using withers (maybe setters are better?) while providing validation, documentation, and IDE autocompletion.
Once construction is ready, `toArray` (maybe `build` is better?) can be called to generate the actual serialization context.

For example:
```php
use Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder;
use Symfony\Component\Serializer\Context\Normalizer\DateTimeNormalizerContextBuilder;

$initialContext = ['custom_key' => 'custom_value']);

$contextBuilder = (new DateTimeNormalizerContextBuilder()
  ->withContext($initialContext)
  ->withFormat('Y_m_d')
  ->withTimezone('GMT');

$contextBuilder = (new CsvEncoderContextBuilder())
  ->withContext($contextBuilder->toArray())
  ->withDelimiter('-')
  ->withHeaders(['foo', 'bar']);

$this->serializer->serialize($data, 'csv', $contextBuilder->toArray());
// Serialization context will be:
// [
//   'custom_key' => 'custom_value',
//   'datetime_format' => 'Y_m_d',
//   'datetime_timezone' => DateTimeZone instance,
//   'csv_delimiter' => '-',
//   'csv_headers' => ['foo', 'bar'],
// ]
```

Commits
-------

f1b078c Add context builers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants