-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[Serializer] Add ChainNormalizer and ChainDenormalizer #52900
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
base: 8.1
Are you sure you want to change the base?
Conversation
16e5ed6 to
2bee4b6
Compare
|
Hm.. Im considering renaming this to EDIT: |
mtarld
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really think that's a good idea to have a better separation between normalization and serialization, thanks for pushing it forward!
src/Symfony/Component/Serializer/DependencyInjection/SerializerPass.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/Serializer/Normalizer/ChainDenormalizerInterface.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php
Outdated
Show resolved
Hide resolved
03f4233 to
72b85a5
Compare
|
Wohoo. All tests are passing. While working on this PR I found an issues like #52992. Ie some code assume a certain behaviour without any contract. This is something we should fix and this PR is a tiny step towards that direction. |
a36a11a to
1846b99
Compare
|
PR is rebased and all tests are green again. |
5e913c5 to
98bdcb8
Compare
|
PR is rebased =) |
8910244 to
d212a7d
Compare
| "symfony/security-bundle": "^6.4|^7.0", | ||
| "symfony/semaphore": "^6.4|^7.0", | ||
| "symfony/serializer": "^6.4|^7.0", | ||
| "symfony/serializer": "^7.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that bump really needed? AFAIK, we try to avoid as much as possible to bump dependencies (see #53160 (comment))
(same for other composer.json files)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FrameworkBundle often bumps the min version instead of trying to support multiple versions of the service definitions, for simplicity.
This is different for other package, which can be reused outside the fullstack framework and for which supporting compatibility with 2 different major versions of Symfony reduces the risk of dependency hell in those projects (the lower level the package is, the more useful this is).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you.
I've bumped version on FrameworkBundle and HttpKernel. I have reverted the other packages and make sure they (still) support both 6.4 and 7.2.
...y/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php
Show resolved
Hide resolved
| { | ||
| /** | ||
| * @group legacy | ||
| */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that we can add the non-legacy version as well
|
It's a good step forward. I'm +1 for the idea. |
d18dd6d to
7fe79f3
Compare
ee04567 to
5e56886
Compare
yceruto
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice refactoring! I left minor comments only.
src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/Serializer/Normalizer/ChainDenormalizer.php
Outdated
Show resolved
Hide resolved
| */ | ||
| public function __construct( | ||
| private array $normalizers = [], | ||
| array $normalizers = [], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defining the new signature of the constructor as being new Serializer([], $encoders, $chainNormalizer, $chainDenormalizer) looks weird to me. It don't think the array as first argument should be kept in the new signature
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alternatively, we could keep the current constructor signature, making the ChainNormalizer and ChainDenormalizer internal implementation details, as done for the ChainEncoder and ChainDecoder (for which we classify them as being encoders, decoders or both inside the constructor). As most implementations are implementing both interfaces in the same class, it allows passing a single list in instantiation, making the configuration easier
Btw, this would mean that the chain classes would not need to implement the *aware interfaces (as the injection would be handled by the Serializer on each registered class)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. I dont like the current signature too.
I want to have a signature like:
new Serializer($encoders, $normalizer, $denormalizer);
// or
new Serializer($normalizer, $denormalizer, $encoders);Should I immediately go to that signature or should we change it in SF8?
On your separate topic of making the Chain* classes an implementation detail.. That was an earlier version of the PR, but we decided against that to make the Serializer more simple. See #52900 (comment)
Treating them like $encoders is a valid idea. However, you have 1-3 encoders MAX. But you may have 100+ normalizers/denormalizers. So I think it makes more sense to keep them separate and give the developer more control.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I immediately go to that signature or should we change it in SF8?
You must support the new signature immediately. Otherwise doing the change in 8.0 will be forbidden as it would be a BC break without migration path.
On your separate topic of making the Chain* classes an implementation detail.. That was an earlier version of the PR, but we decided against that to make the Serializer more simple. See #52900 (comment)
That comment was actually suggesting to also change the way encoders are injected (we should avoid making 2 successive signature changes on the constructor, as that would be a pain to have 2 deprecated signatures to support in the BC layer)
Treating them like $encoders is a valid idea. However, you have 1-3 encoders MAX. But you may have 100+ normalizers/denormalizers. So I think it makes more sense to keep them separate and give the developer more control.
but then, you make it more likely to have nested ChainNormalizer instance, making it necessary to be 100% sure about the reliability of getSupportedTypes for instance (my suggested return ['*' => false]` will be reliable, even though it might not be the most optimized one).
| } | ||
|
|
||
| $class = $definition->getClass(); | ||
| if (null === $class) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this logic looks wrong to me. As this compiler pass is registered as a before-optimization pass, the definition class might not be known yet as a class name:
- when using ChildDefinition, it can be
null(if the class will be inherited from the parent), but this does not mean that skipping the registration of the service is the valid processing (btw, this is a BC break in case someone uses a ChildDefinition to configure a normalizer today) - if a service definition uses a parameter in the class name (which is supported since 2.0), the parameter is not yet resolved at that point, requiring to resolve it explicitly
src/Symfony/Component/Serializer/Normalizer/ChainNormalizer.php
Outdated
Show resolved
Hide resolved
| public function addNormalizer(NormalizerInterface $normalizer): void | ||
| { | ||
| if ($normalizer instanceof NormalizerAwareInterface) { | ||
| $normalizer->setNormalizer($this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we make the ChainNormalizer available publicly, this is flawed as it breaks when nesting a ChainNormalizer inside another ChainNormalizer (the normalizers inside the inner ChainNormalizer won't get access to the main normalizer supporting everything)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would a solution be to have ChainNormalizer also implement the NormalizerAwareInterface?
This PR is less of a feature and more refactoring.
Our Serializer currently implements 5 interfaces. You may serialize and normalize (and reverse). There are also a lot of logic to figure out context, error messages and what normalizer to use.
This PR suggest to separate responsibilities a bit by splitting the normalizing and denormalizing into
ChainNormalizerandChainDenormalizer.I found this work when I was creating custom denormalizers. A low level class like
MyBlogDenormalizershould not have a dependency on the high levelSerializer. It would make more sense to use aChainDenormalizer. (Yes, there is still an ugly circular reference).I've marked
SerializerAwareInterfaceas deprecated for this reason. Currently,SerializerAwareInterfaceonly works if you combine it withNormalizerInterface,DenormalizerInterfaceorEncoderInterface. I deprecate it because one should useNormalizerAwareInterfaceinstead.