Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
HealthcheckPinger
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
4 / 4
7
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 pingSuccess
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 pingFailure
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 send
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace App\Domain\Notification\Service;
6
7use Psr\Log\LoggerInterface;
8use Throwable;
9
10/**
11 * Notifies healthchecks.io that a scheduled job ran (or didn't).
12 *
13 * One ping URL per job per env, configured in env.php under
14 * `healthcheck.urls.<jobKey>`. Empty/missing URL is a no-op so dev
15 * laptops and non-prod envs don't need an account.
16 *
17 * Both pings — success and failure — are fire-and-forget: transport
18 * errors are caught and logged at WARNING. The cron run that triggered
19 * the ping must never fail because healthchecks.io is unreachable.
20 */
21final readonly class HealthcheckPinger
22{
23    /**
24     * @param array<string, string> $urls Map of jobKey => ping URL.
25     * @param HealthcheckClientInterface $client
26     * @param LoggerInterface $logger
27     * @param int $timeoutSeconds
28     */
29    public function __construct(
30        private HealthcheckClientInterface $client,
31        private LoggerInterface $logger,
32        private array $urls,
33        private int $timeoutSeconds,
34    ) {}
35
36    /**
37     * Signal that the named job completed successfully. Healthchecks.io
38     * uses this as the "I'm alive" heartbeat — if it doesn't arrive
39     * within the configured grace period, the service alerts.
40     * @param string $jobKey
41     */
42    public function pingSuccess(string $jobKey): void
43    {
44        $url = $this->urls[$jobKey] ?? '';
45        if ($url === '') {
46            return;
47        }
48
49        $this->send($url, $jobKey);
50    }
51
52    /**
53     * Signal that the named job failed. Hits the /fail subpath so
54     * healthchecks.io alerts immediately rather than waiting for the
55     * grace period.
56     * @param string $jobKey
57     */
58    public function pingFailure(string $jobKey): void
59    {
60        $url = $this->urls[$jobKey] ?? '';
61        if ($url === '') {
62            return;
63        }
64
65        $this->send(rtrim($url, '/') . '/fail', $jobKey);
66    }
67
68    private function send(string $url, string $jobKey): void
69    {
70        try {
71            $this->client->get($url, $this->timeoutSeconds);
72        } catch (Throwable $e) {
73            $this->logger->warning('Healthcheck ping failed', [
74                'jobKey' => $jobKey,
75                'url' => $url,
76                'error' => $e->getMessage(),
77            ]);
78        }
79    }
80}