How tout use kernel.terminate Event in a Service
Asked Answered
I

2

5

I do a service that run an heavy task, this service is call in a Controller. To avoid a too long page loading, I want return the HTTP Response and run the heavy task after.

I've read we can use kernel.terminate event to do it, but I don't understand how to use it.

For the moment I try to do a Listener on KernelEvent:TERMINATE, but I don't know how to filter, for the Listener only execute the job on the good page...

Is it possible to add a function to execute on when the Event is trigger ? Then in my controller I juste use the function to add my action, and Symfony execute it later.

Thanks for your help.

Ineffectual answered 25/4, 2017 at 14:2 Comment(0)
I
12

Finally, I've find how to do it, I use the EventDispatcher in my Service and I connecting a Listener here a PHP Closure: http://symfony.com/doc/current/components/event_dispatcher.html#connecting-listeners

use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\KernelEvents;

class MyService
{
  private $eventDispatcher;

  public function __construct(TokenGenerator $tokenGenerator, EventDispatcherInterface $eventDispatcher)
  {
   $this->tokenGenerator = $tokenGenerator;
   $this->eventDispatcher = $eventDispatcher;
  }

  public function createJob($query)
 {
    // Create a job token
    $token = $this->tokenGenerator->generateToken();

    // Add the job in database
    $job = new Job();
    $job->setName($token);
    $job->setQuery($query);

    // Persist the job in database
    $this->em->persist($job);
    $this->em->flush();

    // Call an event, to process the job in background
    $this->eventDispatcher->addListener(KernelEvents::TERMINATE, function (Event $event) use ($job) {
        // Launch the job
        $this->launchJob($job);
    });

    return $job;
 }
Ineffectual answered 26/4, 2017 at 6:48 Comment(4)
Will it be executed only once after KernelEvents::TERMINATE in that controller, or it will be called every time in any controller after you added that listener ?Priapitis
In the example this is a service, when you call your service and use createJob method, you add a listener on KernelEvents::TERMINATE, but only for this Response. When the user load an other page, there is no listener on KernelEvents::TERMINATE.Ineffectual
I used your example for similar situation, Symfony 5.0.8. But it doesn't send request before job is finished - still do all the heavy job and after that send response. Any idea how to fix that?Onomatology
I've not reuse it since a long time. Because now, you can use the Messenger component to run async tasks. This is a better way because you have: logging, retry policy, failure jobs storage. symfony.com/doc/current/components/messenger.htmlIneffectual
G
0

Instead provided callback function you can also provide static method:

use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\KernelEvents;

class MyService
{
  public function __construct(private EventDispatcherInterface $eventDispatcher)
  {
   $this->eventDispatcher = $eventDispatcher;
  }

  public function createJob($query)
  {
    //...

    //createThingOnApi() method must be static
    $this->eventDispatcher->addListener(KernelEvents::TERMINATE, [self::class, 'createThingOnApi']);
  }

  public static function createThingOnApi()
  {
    //...
  }

}

Source: https://jfoucher.com/2017/08/symfony-process-custom-events-in-kernel-terminate.html

But using static method did not meet my needs because I needed many dependent services. so I saved event in Request object and processed it using Subscriber that runs on KernelEvents::TERMINATE event.

1.

//put this line in Service
/** @var Symfony\Component\HttpFoundation\RequestStack $request*/
$request->attributes->add([
   'user' => $newUserEvent
]);

Usage of Request Attributes

  1. then my KernelEvents::TERMINATE Subscriber:
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\TerminateEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class PostResponseSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::TERMINATE => 'doPostResponseProcesses',
        ];
    }

    public function doPostResponseProcesses(TerminateEvent $event): void
    {
        $request = $event->getRequest();
        $userEvent = $request->attributes->get('user');
        if (!$userEvent) {
            return;
        }
        //process event ...
    }
}

Gilbreath answered 25/8, 2022 at 15:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.