-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[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: 7.3
Are you sure you want to change the base?
Conversation
16e5ed6
to
2bee4b6
Compare
Hm.. Im considering renaming this to EDIT: |
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
@@ -59,7 +59,7 @@ | |||
"symfony/scheduler": "^6.4.4|^7.0.4", | |||
"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
@@ -80,6 +82,9 @@ | |||
|
|||
class SerializerTest extends TestCase | |||
{ | |||
/** | |||
* @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
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
/** | ||
* @param array<NormalizerInterface|DenormalizerInterface> $normalizers | ||
* @param array<EncoderInterface|DecoderInterface> $encoders | ||
*/ | ||
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
ChainNormalizer
andChainDenormalizer
.I found this work when I was creating custom denormalizers. A low level class like
MyBlogDenormalizer
should 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
SerializerAwareInterface
as deprecated for this reason. Currently,SerializerAwareInterface
only works if you combine it withNormalizerInterface
,DenormalizerInterface
orEncoderInterface
. I deprecate it because one should useNormalizerAwareInterface
instead.