Как заставить службу работать на kernel.terminate, как работает SwiftMailer?

Мне было интересно, как написать сервис, который выполняется только на kernel.terminate.

Например, когда я звоню:

$message = (new \Swift_Message('A new book has been added'))
            ->setFrom('system@example.com')
            ->setTo('contact@les-tilleuls.coop')
            ->setBody(sprintf('The book #%d has been added.', $book->getId()));
$this->mailer->send($message);

Я знаю, что ядро ​​отправит ответ клиенту и только ПОСЛЕ этого отправит электронное письмо.

У меня много услуг, к которым можно обратиться kernel.terminate но я не знаю, как их написать, чтобы меня вызывали только на этом мероприятии.

А пока пишу код в подписчике:

public static function getSubscribedEvents()
    {
        return [
            KernelEvents::TERMINATE => [['doHeavyStuff', EventPriorities::POST_RESPOND]]
            ];
    }

Но работа таким образом означает, что я должен иметь дело только с запросом и ответом, и я не хочу зависеть от ответа.

if($method===Request::METHOD_PUT && isset($response['@type']) && $response['@type']==='Partner'){
 $this->notificationManager->sendNotificationAboutCart($response['partner']['id'],
                                                       $response['partner']['name']);
}

Не знаю, понятно ли, но было бы здорово называть notificationManager везде, где я хочу в своем коде, и этот менеджер работает только с kernel.terminate.

1 ответ

Решение

Простая наивная реализация.

Ваш сервис send() фактически ничего не отправляет, а просто добавляет еще одно сообщение, которое нужно отправить, когда придет время.

class ShinyService {
    private $messages = [];

    public function send($message) {
        $this->messages[] = $message;
    }

    public function processMessages()
    {
        foreach ($this->messages as $message) {
            // do the actual work, e.g. sending the message
        }
    }
}

Затем подписчик, который зависит от этой службы, поэтому он вводится в экземпляр подписчика:

class ShinySubscriber implements EventSubscriberInterface
{

    private $service;

    public function __construct(ShinyService $service) {
        $this->service = $service;
    }

    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::TERMINATE => [
                ['processMessages', 10]
            ],
        ];
    }

    public function processMessages(TerminateEvent $event)
    {
        $this->service->processMessages();
    }
}

Таким образом вы можете ввести ShinyService куда угодно, звони ShinyService::send() в любой момент, и сообщения будут отправлены только на KernelEvents::TERMINATE.

Помните, что это событие запускается только после отправки ответа, если вы используете PHP-FPM. Из документов:

Внутри HttpKernel использует функцию PHP fastcgi_finish_request. Это означает, что на данный момент только API сервера PHP FPM может отправлять ответ клиенту, в то время как процесс PHP сервера все еще выполняет некоторые задачи. Со всеми другими API-интерфейсами сервера прослушиватели kernel.terminate все еще выполняются, но ответ не отправляется клиенту, пока все они не будут завершены.

Другие вопросы по тегам