-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Serializer] Add support for auto generated custom normalizers #52905
New issue
<
8000
/summary>
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
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
93975d3
to
b91c59b
Compare
Thanks Tobias for this proposal! It's nice to see some performance improvements for this component. I did just a quick check of the code and I wonder if the code generator building blocks can be a separate component or consider https://github.com/nette/php-generator. It looks decoupled from this feature. |
Another alternative for PHP code generation |
That are some good points. Nikic is way too complex and verbose. I also looked into re-use the code generator from Config component. But that one is also very opinionated and not generic. I've also heard @mtarld is using a code generator for upcoming PR... I really think a group of contributors should look over Symfony's use of code generators, but I dont think that is a blocker for this PR. (The CodeGenerator here is marked as internal) |
Just a quick glance over the description and example, and I noticed this in the generated code: if (array_key_exists('name', $data)) {
$output->name = $data['name'];
} To my understanding Great idea though, and looking forward for this 😄 |
I finally used |
Thats only correct for older PHP versions. In recent versions the opposite is true. |
You'll learn something new every day 😄. Ran a quick PHPBench (for loop with 10M iterations doing |
Using |
Hey, we spoke a bit together during SymfonyCon hackday. I am working on JanePHP components & AutoMapper. We do a lot of code generation and I love seeing such feature coming to Symfony Serializer ! I didn't saw your PR before some tweet I saw today that's why I didn't understand well all you were talking with Mathias. In your implementation I would like to see some sort of hash corresponding to the model your generated the normalizer from, the idea is to know that you generated the normalizer with a specific version of that model and if the model changes, you will generate a new version of it. You can find this logic here if you want an example: https://github.com/jolicode/automapper/blob/9c43e0d3710f202097d30656667041c3cbf8382c/src/MapperMetadata.php#L138-L155. I'm not sure I am a fan of having the model that holds information about what is going to be generated having also the role to generate stuff, but it fits well in your pull request. Also, do you plan on replacing stuff like ArrayDenormalizer with this PR or a later PR ? I would love being able to have all the normalizer code being generated and not only some part. |
this has crazy performance impacts. me and some collegues wrote something like this a while ago for JMS serializer. if you need more inspiration: https://github.com/liip/serializer we generate code to convert between object and array and back. we did not use any code generation tool but twig :-D (but using a code generator is probably the better way to do this). for our complex models, we generate 50k or more lines of PHP that is all dead simple, and thanks to opcache it is quite ok to have such large code files. depending on event system during the on-the-fly conversion, our generated code will not do the same. it should be possible to trigger the events from the generated code, but we did not use events so we did not make the effort. |
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.
@@ -17,8 +17,6 @@ | |||
* Infers the constructor argument type. | |||
* | |||
* @author Dmitrii Poddubnyi <dpoddubny@gmail.com> | |||
* | |||
* @internal |
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'm not a huge fan, you did this because it's used in another component? Just ignore the error for internal use between components it's fine no?
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.
Yes. It is because I used it in another component.
Do you know the reason why it is internal in the first place?
src/Symfony/Component/Serializer/DependencyInjection/CustomNormalizerHelper.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/Serializer/DependencyInjection/CustomNormalizerHelper.php
Show resolved
Hide resolved
Really nice, would love to see that in symfony, i think it's simple enough for a first implementation and other features can be added in subsequent PR. More optimizations can be done also to increase performance (like directly inject the child normalizer when needed, to have done a similar thing having this direct injection normally provide between 10 to 20% performance in some cases, like a property that is an array of objects) but all of this can be added further. Also ATM there is now 3 different pull request with a similar goal with the Mapper component and the JsonEncoder component, i don't know which way the symfony team want to go but IMO there is a path were all of this PR can use the same implementation and code base which will drasticly reduce code maintenance and burden to the core team by doing something like :
With this implementation you will be able to achieve the same thing as those 3 PR :
For the JsonEncode PR this may require some more works to be able to use only specific part of the implementation but i think this is totally doable Having this will also allow each of this component to share new features (supporting groups, excluding properties, renaming properties, circular loop, etc ...) |
this is also the approach we took for our liip serializer-generator: https://github.com/liip/metadata-parser because we wanted to build a drop-in replacement for JMS, we specifically support JMS annotations, but such a metadata parser could be really generic and provide metadata from the code or any kind of attributes/annotations/metadata mapping files in yaml/xml like doctrine does etc. |
753f197
to
d10be79
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.
Also, do you plan on replacing stuff like ArrayDenormalizer with this PR or a later PR ?
That is a later PR =)
me and some collegues wrote something like this a while ago for JMS serializer. if you need more inspiration: https://github.com/liip/serializer
Yeah. I think I've seen a conference talk about that.
PR is updated. We now use nikic/PHP-Parser
@@ -17,8 +17,6 @@ | |||
* Infers the constructor argument type. | |||
* | |||
* @author Dmitrii Poddubnyi <dpoddubny@gmail.com> | |||
* | |||
* @internal |
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.
Yes. It is because I used it in another component.
Do you know the reason why it is internal in the first place?
src/Symfony/Component/Serializer/DependencyInjection/CustomNormalizerHelper.php
Outdated
Show resolved
Hide resolved
Tests are green. |
Any chances to merge into 7.2? |
Hi @Nyholm, I've tried to develop my own serializer component based on generated code, exactly the same way you're proposing to Symfony Serializer component. In that moment (5 years back), I made a lot of performance tests with the generated code and without any surprise, the benchmark showed that this is the way to go. If I can contribute with your PR, I'd say to take a look on the code generator class to see how I generated the normalizer code. Each decision on the generated code was implemented with the light of Blackfire to minimize any bottleneck. You'll notice that to increase even more the performance, some Symfony Serializer logic should be refactored to allow them to be executed by the generated code, like ignored attributes, normalized name, class inheritance and etc. All of this feature has an overhead when used through reflection at runtime and can be improved on the generated code. This tool helped me a lot when I wrote the component. |
How would this interact with #51718? |
#51718 is for specific (but very common) scenarios. Ie DTOs to/form JSON (and maybe XML). It also skips the normalizer step. This PR is for the generic use case, to/form all kind of data and does not change the architecture of the serialization process. However, it does not give as impressive results (but almost). Me and @mtarld believes they can/should co-exists. |
ObjectMapper was merged recently and @soyuka tell me that it would be nice to have generated code to improve performance of such mapper. I do believe it's possible to have the same implementation that does :
As the only difference in this is the way to write / read data The main problem is : where to put this implementation, as other components will have to require this implementation and this is something that should be fully internal (no public extensions points, they are provided by other components as their are specific to the wanted behavior) I'm not sure what your status are on this actually @Nyholm ? |
I see a few ways:
Anyways this probably needs an RFC issue to prevent polluting this topic |
This is an opt-in performance boost for the serializer component. The trick is to move work from runtime to build-time by generating custom normalizers.
The Serializer is slow because every time we serialize or deserialize an object we are trying to figure out what this object really is. By marking the classes we are interested in serializeing, we can do all this "figure out" at build time. This saves LOADs of time.
The more complex the value objects are, the more time we will save. On a simple "hello word" object, we reduce the time with about 80%.
This feature is not complete. We do not yet support the Serializer attributes like
Ignore
andSerializerName
. But since this is opt-in AND experimental I think it is safe to review, try and maybe even merge this PR. Because many large PRs on this component have stalled the passed few years.Usage
App\Model value objects
Output:
Generated classes
The tests have a lot of example classes and expected outputs. See https://github.com/Nyholm/symfony/tree/better-serializer/src/Symfony/Component/Serializer/Tests/Fixtures/CustomNormalizer
See generated classes for PR example