Search code examples
phpsymfonyservicedestructorsymfony4

Symfony service destructor called multiple times


I've been playing with Symfony 4 for a while now and I've created a twig extension for one of my webpages recently, which is responsible for translating any given string based on data in database. Unfortunately, I've faced with a weird problem which I cannot resolve. I'll try to write down what happens chronologically, so it's more understandable.

  1. The DatabaseTranslateExtension registers a new |translate filter in Twig.
  2. |translate filter triggers a lazy-loaded TranslationService to be constructed (when it's not constructed yet, of course).
  3. There's only one instance of TranslationService created (which is expected).
  4. Constructor preloads the data, so it's not calling the database every time translation happens.
  5. The filter calls translate method, which either translates the string or (if there's no translation in the database) adds the string into an instance variable, let's call it stringsToTranslate, which is a type of array (String[]).
  6. After all strings have been translated, the service's destructor should be called, which does flush the stringsToTranslate array into a database.

I recently realized that I have many duplicates in the database, so I tried to debug the app and see what's happening. Somehow, I had no idea it's even possible, the service's destructor is called twice, not once. I'm pretty sure Symfony has something to do with it (it might be because the lazy loading) or some reflect classes it creates. I'm wondering if there's anything you can think of, which would trigger the destructor to be called twice (Yes, it's the exact same instance of a class). Thank you in advance.


I did track the code down in a built app and found the wrapper created for my service, which calls a destruct, here's the code:

public function __destruct()
{
    $this->initializer2b670 || $this->valueHolder90d49->__destruct();
}

What's interesting is that this __destruct is also called twice. It seems like it's because there's also Reflection class created and both classes call destruct. I did dump the __destructor's body. First evaluation was false, which means it needed to call a destruct on the valueHolder class and then it gets called once again what evaluated to true (that probably called destruct too). Weird.


Solution

  • In case anyone has a similar problem use KernelEvents::RESPONSE or KernelEvents::TERMINATE instead of __destructor.

    • KernelEvent::RESPONSE is fired before response is sent.
    • KernelEvent::TERMINATE is fired after response is sent.

    More about Symfony's lifecycle/events can be found here.


    For those curious about __destruct called multiple times, it's probably because of the reflection class created on top of wrapper. The normal class instance and reflected class instance are destructed I probably this calls it a couple of times.