1

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.

Dawid Zbiński
  • 3,970
  • 7
  • 36
  • 63
  • 1
    Always though php's destructors were a little bit flaky. Have you considered doing your flushing in a kernel.terminate listener? – Cerad Jun 25 '18 at 20:47
  • Thanks for pointing it out. I will probably need to create an EventListener for that, but I thought it would be a nice try to do it with destructor. I don't personally use them as much, so I thought it might be nice to finally try it when I had a chance and the situation required the kind of behavior destructor provides. Still, it's some really weird stuff, I mean.. how can you possibly destroy same thing twice? Nevermind, thanks again for suggestion – Dawid Zbiński Jun 25 '18 at 21:05
  • There are some old posts on destructors being called multiple times but nothing recent. Normally I would say you had two instances being created (perhaps via the _profile bar) but you indicated that was not the case. I assume this is only happening in production mode. – Cerad Jun 25 '18 at 21:17

1 Answers1

1

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.

Dawid Zbiński
  • 3,970
  • 7
  • 36
  • 63