Odložená pripomienka
Cieľ: poslať e-mail s pripomienkou deň pred rezerváciou. Správa sa vloží pri vytvorení rezervácie s executeAt nastaveným na čas pripomienky.
Súbory
Redis, Mailpit a src/Hermes.php ako v návode na uvítací e-mail.
{
"require": {
"php": ">=8.4",
"tomaj/hermes": "dev-master",
"predis/predis": "^2.3 || ^3.0",
"symfony/mailer": "^7.0"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"minimum-stability": "dev",
"prefer-stable": true
}<?php
declare(strict_types=1);
use App\Hermes;
use Tomaj\Hermes\Message;
require __DIR__ . '/../vendor/autoload.php';
// A customer reserved a table. Remind them one day before, or in REMIND_IN seconds while testing.
$reservation = ['id' => 77, 'email' => 'jane@example.com', 'at' => strtotime('+2 days 19:00')];
$remindAt = (float) (getenv('REMIND_IN') ? time() + (int) getenv('REMIND_IN') : $reservation['at'] - 86400);
Hermes::emitter()->emit(new Message(
'reservation.remind',
['reservation_id' => $reservation['id'], 'email' => $reservation['email']],
executeAt: $remindAt, // the worker will not run it before this time; id and created are generated
));
echo 'Reminder scheduled for ' . date('Y-m-d H:i:s', (int) $remindAt) . "\n";<?php
declare(strict_types=1);
namespace App;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Tomaj\Hermes\Handler\RetryableHandlerInterface;
use Tomaj\Hermes\Handler\RetryTrait;
use Tomaj\Hermes\MessageInterface;
final class ReminderHandler implements RetryableHandlerInterface
{
use RetryTrait;
public function __construct(private MailerInterface $mailer) {}
#[\Override]
public function handle(MessageInterface $message): bool
{
$payload = $message->getPayload() ?? [];
// The message was written a day ago. Check that it still applies.
if (!$this->stillBooked((int) $payload['reservation_id'])) {
return true;
}
$email = new Email();
$email
->from('bookings@example.com')
->to($payload['email'])
->subject('See you tomorrow')
->text('This is a reminder of your reservation tomorrow.');
$this->mailer->send($email);
return true;
}
private function stillBooked(int $reservationId): bool
{
return true; // replace with a database lookup
}
}<?php
declare(strict_types=1);
use App\Hermes;
use App\ReminderHandler;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mailer\Transport;
use Tomaj\Hermes\Dispatcher;
use Tomaj\Hermes\Shutdown\PredisShutdown;
require __DIR__ . '/../vendor/autoload.php';
$redis = Hermes::redis();
$driver = Hermes::driver($redis);
$driver->setShutdown(new PredisShutdown($redis));
$mailer = new Mailer(Transport::fromDsn(getenv('MAILER_DSN') ?: 'smtp://127.0.0.1:1025'));
$dispatcher = new Dispatcher($driver);
$dispatcher->registerHandler('reservation.remind', new ReminderHandler($mailer));
$dispatcher->handle();Spustenie
REMIND_IN prepíše oneskorenie, aby ste nemuseli čakať deň:
composer install
docker compose up -d
php bin/worker.php # terminal 1
REMIND_IN=10 php public/index.php # terminal 2
docker compose exec redis redis-cli zrange hermes_schedule 0 -1 withscoresSpráva čaká v hermes_schedule so skóre rovným času pripomienky. Asi o desať sekúnd ju worker presunie do živej fronty, spracuje ju a v Mailpite na http://localhost:8025 sa objaví „See you tomorrow“.
Úskalia
- Vo verzii 5.x spoľahlivo odkladá len Predis.
RedisSetDriver(ext-redis) v 5.x zapisuje do zoradenej množiny, ale splatné správy nikdy nevráti (#95, opravené v 6.0). RabbitMQ, SQS a Kafka ignorujúexecuteAta doručia hneď. Pozrite Odložené spracovanie. - Stav kontrolujte v čase spustenia správy. Deň je dlhý: rezervácia sa môže zrušiť alebo presunúť. V handleri ju načítajte znova a ak už neplatí, nerobte nič.
- Musí bežať worker. Splatné správy sa presúvajú len vo
wait(). Ak v čase pripomienky žiadny worker nebeží, pripomienka príde neskoro, ale nestratí sa. executeAtje Unix časová značka v sekundách ako float. Počítajte ju v UTC, napríklad cezstrtotime()aleboDateTimeImmutable::getTimestamp(), aby ju časové pásma nemohli posunúť.- Pripomienku nemeňte úpravou správy. Vložte novú a nech handler staré ignoruje, napríklad uložením id aktuálnej pripomienky k rezervácii.
Celý zdrojový kód
Všetky súbory tohto receptu, s README.md, Makefile a kontrolou e2e.sh, sú v examples/reminder na GitHube. Naklonujte repozitár, spustite cd examples/reminder a make install up, potom make e2e.
Časté otázky
Môžem na pripomienky použiť ext-redis?
Áno, od 6.0, ktorú tieto recepty vyžadujú. Vo verzii 5.x jeho krok presunu necháva splatné správy v zoradenej množine, preto tam použite Predis. Pozrite Odložené spracovanie.
Čo ak sa rezervácia zruší?
Handler ju načíta znova a ak už neplatí, nerobí nič.
Čo ak v čase pripomienky nebeží žiadny worker?
Pripomienka sa spustí neskôr, hneď ako sa worker spustí. Nestratí sa.
Ako pripomienku presunúť?
Vložte novú správu a nech handler starú ignoruje, napríklad uložením id aktuálnej pripomienky.
V akých jednotkách je executeAt?
Unix časová značka v sekundách ako float.