feat(mail) : MailSyncRequested message + handler + messenger.yaml transport async Doctrine
- App\Message\MailSyncRequested (optionnel folderPath) - App\MessageHandler\MailSyncRequestedHandler delegue a MailSyncService::syncFolder ou syncAll - messenger.yaml : transport async via Doctrine DSN, retry 3x exponentiel, failure transport - en test : transport in-memory (sync immediat) - migration Version20260519220000 : cree messenger_messages table (idempotente, IF NOT EXISTS) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,28 @@
|
||||
framework:
|
||||
messenger:
|
||||
failure_transport: failed
|
||||
|
||||
transports:
|
||||
sync: 'sync://'
|
||||
|
||||
routing: {}
|
||||
async:
|
||||
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||
options:
|
||||
queue_name: default
|
||||
retry_strategy:
|
||||
max_retries: 3
|
||||
delay: 1000
|
||||
multiplier: 2
|
||||
max_delay: 0
|
||||
|
||||
failed: 'doctrine://default?queue_name=failed&auto_setup=0'
|
||||
|
||||
routing:
|
||||
'App\Message\MailSyncRequested': async
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
messenger:
|
||||
transports:
|
||||
async: 'in-memory://'
|
||||
failed: 'in-memory://'
|
||||
|
||||
56
migrations/Version20260519220000.php
Normal file
56
migrations/Version20260519220000.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Cree la table messenger_messages pour le transport async Symfony Messenger.
|
||||
*/
|
||||
final class Version20260519220000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Cree la table messenger_messages pour le transport async Doctrine de Symfony Messenger.';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE IF NOT EXISTS messenger_messages (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
body TEXT NOT NULL,
|
||||
headers TEXT NOT NULL,
|
||||
queue_name VARCHAR(190) NOT NULL,
|
||||
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL
|
||||
)
|
||||
SQL);
|
||||
|
||||
$this->addSql('CREATE INDEX IF NOT EXISTS IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)');
|
||||
$this->addSql('CREATE INDEX IF NOT EXISTS IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)');
|
||||
$this->addSql('CREATE INDEX IF NOT EXISTS IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)');
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE OR REPLACE FUNCTION notify_messenger_messages() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify('messenger_messages', NEW.queue_name::text);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
SQL);
|
||||
|
||||
$this->addSql('DROP TRIGGER IF EXISTS notify_trigger ON messenger_messages;');
|
||||
$this->addSql('CREATE TRIGGER notify_trigger AFTER INSERT OR UPDATE ON messenger_messages FOR EACH ROW EXECUTE PROCEDURE notify_messenger_messages();');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE IF EXISTS messenger_messages');
|
||||
$this->addSql('DROP FUNCTION IF EXISTS notify_messenger_messages()');
|
||||
}
|
||||
}
|
||||
12
src/Message/MailSyncRequested.php
Normal file
12
src/Message/MailSyncRequested.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Message;
|
||||
|
||||
final readonly class MailSyncRequested
|
||||
{
|
||||
public function __construct(
|
||||
public ?string $folderPath = null,
|
||||
) {}
|
||||
}
|
||||
54
src/MessageHandler/MailSyncRequestedHandler.php
Normal file
54
src/MessageHandler/MailSyncRequestedHandler.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\MessageHandler;
|
||||
|
||||
use App\Message\MailSyncRequested;
|
||||
use App\Repository\MailFolderRepository;
|
||||
use App\Service\MailSyncService;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||
use Throwable;
|
||||
|
||||
#[AsMessageHandler]
|
||||
final readonly class MailSyncRequestedHandler
|
||||
{
|
||||
public function __construct(
|
||||
private MailSyncService $mailSyncService,
|
||||
private MailFolderRepository $folderRepository,
|
||||
private LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
public function __invoke(MailSyncRequested $message): void
|
||||
{
|
||||
try {
|
||||
if (null !== $message->folderPath) {
|
||||
$folder = $this->folderRepository->findByPath($message->folderPath);
|
||||
if (null !== $folder) {
|
||||
$report = $this->mailSyncService->syncFolder($folder);
|
||||
$this->logger->info(sprintf(
|
||||
'MailSyncRequested handled for folder "%s": %d created, %d updated, %d deleted',
|
||||
$message->folderPath,
|
||||
$report->createdCount,
|
||||
$report->updatedCount,
|
||||
$report->deletedCount,
|
||||
));
|
||||
} else {
|
||||
$this->logger->warning(sprintf('MailSyncRequested: folder "%s" not found in DB', $message->folderPath));
|
||||
}
|
||||
} else {
|
||||
$report = $this->mailSyncService->syncAll();
|
||||
$this->logger->info(sprintf(
|
||||
'MailSyncRequested handled (all folders): %d created, %d updated, %d deleted, %d folders scanned',
|
||||
$report->createdCount,
|
||||
$report->updatedCount,
|
||||
$report->deletedCount,
|
||||
$report->foldersScanned,
|
||||
));
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->error('MailSyncRequestedHandler failed: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user