Skip to content

[Serializer] Support subclasses of DateTime and DateTimeImmutable #57793

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

Merged
merged 1 commit into from
Aug 30, 2024

Conversation

amcsi
Copy link
Contributor

@amcsi amcsi commented Jul 22, 2024

Q A
Branch? 7.2
Bug fix? no
New feature? yes
Deprecations? no
Issues #57482
License MIT

@amcsi amcsi requested a review from dunglas as a code owner July 22, 2024 08:22
@carsonbot carsonbot added this to the 7.2 milestone Jul 22, 2024
@carsonbot
Copy link

Hey!

I see that this is your first PR. That is great! Welcome!

Symfony has a contribution guide which I suggest you to read.

In short:

  • Always add tests
  • Keep backward compatibility (see https://symfony.com/bc).
  • Bug fixes must be submitted against the lowest maintained branch where they apply (see https://symfony.com/releases)
  • Features and deprecations must be submitted against the 7.2 branch.

Review the GitHub status checks of your pull request and try to solve the reported issues. If some tests are failing, try to see if they are failing because of this change.

When two Symfony core team members approve this change, it will be merged and you will become an official Symfony contributor!
If this PR is merged in a lower version branch, it will be merged up to all maintained branches within a few days.

I am going to sit back now and wait for the reviews.

Cheers!

Carsonbot

@carsonbot carsonbot changed the title Support subclasses of DateTime(Immutable) in Serializer [Serializer] Support subclasses of DateTime(Immutable) in Serializer Jul 22, 2024
@OskarStark OskarStark changed the title [Serializer] Support subclasses of DateTime(Immutable) in Serializer [Serializer] Support subclasses of DateTime(Immutable) Jul 22, 2024
Copy link
Member

@derrabus derrabus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your PR. However, your implementation is significantly slower than the lookup that we currently use. I don't think we should merge this change.

@amcsi
Copy link
Contributor Author

amcsi commented Jul 22, 2024

@derrabus I understand the need to be as fast as possible.
As an alternative, I could instead implement this by allowing to override the supported types via constructor option, as was suggested by you in the Issue.
Would you have any preferences in the name/key of the option in this case?

@amcsi
Copy link
Contributor Author

amcsi commented Jul 22, 2024

Made the change.

@amcsi
Copy link
Contributor Author

amcsi commented Jul 24, 2024

@derrabus @xabbuh changes ready

@OskarStark OskarStark changed the title [Serializer] Support subclasses of DateTime(Immutable) [Serializer] Support subclasses of DateTime and DateTimeImmutable Jul 24, 2024
@amcsi
Copy link
Contributor Author

amcsi commented Aug 6, 2024

@derrabus @xabbuh anything else?

@stof
Copy link
Member

stof commented Aug 6, 2024

I don't think this is the right fix. getSupportedTypes does not need any change because of the way this API works (supporting a type means supporting all its subtypes).
The only thing that should change is the implementation of supportsDenormalization to properly handle subclasses.

@amcsi
Copy link
Contributor Author

amcsi commented Aug 7, 2024

@stof Literally my first implementation was changing the supportsDenormalization() method's behavior to iterate the SUPPORTED_TYPES constant's types and do additional is_subclass_of() checks against the passed $type, but @derrabus told me not to do it, because it would make supportsDenormalization() slower.

I could potentially cache each result if I remove the context support for supported types that I added, but then I would be concerned about memory leaks (the supported-types true/false values array keeping on getting bigger and bigger). I'm not sure the performance would be overall better, though.

This first checking the supported types from the $context and then falling back to checking the default context to me sounds like the best compromise, all things considered.

@derrabus
Copy link
Member

@stof has a point with regards to the cachability of the normalizer. I think, we should not use the context for this piece of configuration. We could inject the list of supported classes via the constructor. This way, it is set once when we build the serializer and the decisions of the voter become cacheable again.

@amcsi
Copy link
Contributor Author

amcsi commented Aug 11, 2024

@derrabus, ok, then I'll change the behavior to make the list of supported classes configurable in the constructor, but use a separate property from the context's to store/read them.

@amcsi
Copy link
Contributor Author

amcsi commented Aug 12, 2024

@derrabus Done. Note that I made the DEFAULT_SUPPORTED_TYPES const public so that you could easily do an array_replace()``/array_merge() with the default supported types when overriding them in the constructor.

@stof
Copy link
Member

stof commented Aug 13, 2024

having a slower supportsDenormalization is not an issue if it stays cacheable as it would run only once for each type (and we can keep the existing isset check as a first check for a fast path for code using the PHP core classes and not subtypes)

@stof
Copy link
Member

stof commented Aug 13, 2024

and no need for a caching in DateTimeNormalizer: we already have caching in Serializer

@amcsi
Copy link
Contributor Author

amcsi commented Aug 14, 2024

@stof you need to come to some agreement with @derrabus, because he didn't like it when I wrote it to the way you are suggesting.
The only difference was that I didn't put an isset() check in the beginning, but...

  1. most of the time the incoming type will not be a date class type, so isset will return false most of the time and we would have to fall back to the loop with the is_subclass_of() logic.
  2. adding an isset() check in the the beginning of supportsNormalization() wouldn't really matter anyway if what you claim is true that Serializer already caches the results of supportsNormalization() per normalizer.

@stof
Copy link
Member

stof commented Aug 14, 2024

  1. most of the time the incoming type will not be a date class type, so isset will return false most of the time

why that ? The supportsDenormalization method will be called only for cases where getSupportedTypes reports that it may be supported (otherwise, the Serializer will skip calling it at all, which is the source of the main performance gain provided by getSupportedTypes).
So unless you use custom subclasses, the isset check will actually be the common path.

But indeed, keeping the isset check would still be a micro-optimization given we have the caching in Serializer

@derrabus
Copy link
Member

What is your actual proposal @stof? Maybe I did not fully understand what you'd like to be changed.

@stof
Copy link
Member

stof commented Aug 14, 2024

@derrabus always supporting all subtypes of DateTimeInterface, by making supportsDenormalization report that properly (getSupportedTypes already does), without any need for configuration.

The only reason denormalization does not support subclasses is because supportsDenormalization does not account for subclasses in its check (the implementation would be correct if DateTime and DateTimeImmutable were final classes).

The implementation could be is_a($type, \DateTimeInterface::class, true)

@derrabus
Copy link
Member

The implementation could be is_a($type, \DateTimeInterface::class, true)

And the cache warmup via getSupportedTypes() would only happen for the built-in classes then?

@stof
Copy link
Member

stof commented Aug 14, 2024

@derrabus getSupportedTypes is not about cache warmup. It is about filtering early unsupported types. But the implementation of this filtering works with types properly (meaning that reporting a type as supported considers its subtypes as supported as well there).

Note that the implementation of getSupportedTypes reporting DateTime and DateTimeImmutable in addition to DateTimeInterface (with the same cacheability value) is probably redundant.

@amcsi
Copy link
Contributor Author

amcsi commented Aug 28, 2024

Alright, I've added the simplified is_a() check that was mentioned.
Note that maybe getSupportedTypes() should also just return [DateTimeInterface::class] without the (built-in) subclasses redundantly? And then maybe we could do away with the SUPPORTED_TYPES constant entirely as well. Here's what that commit would look like.

@stof
Copy link
Member

stof commented Aug 28, 2024

@amcsi your suggested commit removing the constant is broken. It does not return the right array structure.

@amcsi
Copy link
Contributor Author

amcsi commented Aug 28, 2024

@stof what do you mean? here are the square brackets.
image

@derrabus
Copy link
Member

@stof what do you mean? here are the square brackets.

The returned array is supposed to be keyed by the supported classes. You however return them as values.

@amcsi
Copy link
Contributor Author

amcsi commented Aug 28, 2024

@derrabus damn, I totally forgot. But you did approve, so I'm assuming my intended additional change is not needed.

@derrabus
Copy link
Member

I think, we're good. 🙂

@nicolas-grekas
Copy link
Member

Thank you @amcsi.

@nicolas-grekas nicolas-grekas merged commit 61e430f into symfony:7.2 Aug 30, 2024
4 of 10 checks passed
@fabpot fabpot mentioned this pull request Oct 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants