Closed FluffyDiscord closed 1 year ago
Hello @FluffyDiscord!
Can you provide a working reproducer?
You would not believe what the issue was. This is insane - I may be missing something, but I think this is symfony bug candidate.
After days of debugging I narrowed down the issue down to custom form type. Steps taken:
I stopped using my custom form extension that adds event subscriber to some custom form types which were working with entities (fetching and persisting). This resolved the weird doctrine errors (whole bunch of them, none of them made sense and showed up only in prod mode)
Then I noticed that now after persisting new entity with my modified custom form type I could see it inside the db, also could fetch it outside the form just fine, but inside the form in choices
option it was not included for some reason.
Rebooting kernel or restarting RR fixed this missing entity.
This behavior was weird, so after a bit more I randomly placed notification sender (static class that sends Discord message without any reliance on DI) inside configureOptions
of my form type to check when it was called.
To my surprise, it was called only once, on first request, then never again until kernel reboot or RR restarted.
So on first request it loaded entities from DB, placed them in choices
and then they weren't ever refreshed since options were resolved in first request and symfony didn't feel like resolving them again after that.
I checked how symfony creates these forms and found that Symfony\Component\Form\FormRegistry
is caching resolved form types. I guess symfony thinks that form types are "stateless", but you often use DI inside your form type to add additional config options or process some data dynamically.
I have "hotfixed" this behavior by registering my own copy of Symfony\Component\Form\FormRegistry
(along with Symfony\Component\Form\FormFactoryBuilder
since the original Symfony's FormRegistry is hardcoded inside this factory) and then reset the resolved form types using ResetInterface
after each request. This is not a full fix, since if I use the same "stateful" form twice within one request, I will probably run into the same issue (no idea, didn't try and I won't get in this situation)
So this isn't issue within
this bundle, but maybe this bundle should
help out and apply fix. Just like other middlewares are for.
Crazy investigation!
I see ChoiceType
supports a choice_loader
option with a CallbackChoiceLoader
class, would it help?
Also, do you know if there is the issue with EntityType
? Is it resolving entities only once?
If there is no viable workaround with Symfony, I can look to add something here.
The choice_loader
might work, I did not consider it to be honest since I have yet to use it anywhere. I will check it out and the EntityType
tomorrow. I would guess every form type, internal or custom, will be behaving the same way - load configureOptions
once.
This still will be an issue for other form options
though.
It might be also possible to replace only Symfony\Component\Form\ResolvedFormType
and refresh only the $optionResolver
instead of FormRegistry and it's Factory
Just a heads up - as long as configureOptions
are static, eg. without dependency on outside service, everything seems to work as intended. Also if you are cloning entity using the build-in php's clone
then don't, doctrine proxies do not work. Make fresh object.
Since each request uses the same booted kernel, the same entity manager is being used for each request. The issue with this is that the entities are partly still kept in memory and after few requests that touch the same entity (changing the entity and persisting) the entity manager starts throwing
A managed+dirty entity xxx can not be scheduled for insertion.
I can reproduce it consistently.This issue can not be solved without rebooting the whole kernel.