httpClient = $httpClient; $this->em = $em; $this->logger = $logger; $this->webhooks = $webhooks; $this->transformer = $transformer; $this->appOptions = $appOptions; } public function __invoke(VisitLocated $shortUrlLocated): void { if (empty($this->webhooks)) { return; } $visitId = $shortUrlLocated->visitId(); /** @var Visit|null $visit */ $visit = $this->em->find(Visit::class, $visitId); if ($visit === null) { $this->logger->warning('Tried to notify webhooks for visit with id "{visitId}", but it does not exist.', [ 'visitId' => $visitId, ]); return; } $requestOptions = $this->buildRequestOptions($visit); $requestPromises = $this->performRequests($requestOptions, $visitId); // Wait for all the promises to finish, ignoring rejections, as in those cases we only want to log the error. settle($requestPromises)->wait(); } private function buildRequestOptions(Visit $visit): array { return [ RequestOptions::TIMEOUT => 10, RequestOptions::HEADERS => [ 'User-Agent' => (string) $this->appOptions, ], RequestOptions::JSON => [ 'shortUrl' => $this->transformer->transform($visit->getShortUrl()), 'visit' => $visit->jsonSerialize(), ], ]; } /** * @param Promise[] $requestOptions */ private function performRequests(array $requestOptions, string $visitId): array { $logWebhookFailure = Closure::fromCallable([$this, 'logWebhookFailure']); return map( $this->webhooks, fn (string $webhook): PromiseInterface => $this->httpClient ->requestAsync(RequestMethodInterface::METHOD_POST, $webhook, $requestOptions) ->otherwise(partial_left($logWebhookFailure, $webhook, $visitId)), ); } private function logWebhookFailure(string $webhook, string $visitId, Throwable $e): void { $this->logger->warning('Failed to notify visit with id "{visitId}" to webhook "{webhook}". {e}', [ 'visitId' => $visitId, 'webhook' => $webhook, 'e' => $e, ]); } }