Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
16 / 16 |
|
100.00% |
4 / 4 |
CRAP | |
100.00% |
1 / 1 |
| HealthcheckPinger | |
100.00% |
16 / 16 |
|
100.00% |
4 / 4 |
7 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| pingSuccess | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| pingFailure | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| send | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace App\Domain\Notification\Service; |
| 6 | |
| 7 | use Psr\Log\LoggerInterface; |
| 8 | use 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 | */ |
| 21 | final 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 | } |