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:
2026-05-20 00:14:47 +02:00
parent f7f7a07162
commit cc46dd915d
4 changed files with 145 additions and 1 deletions

View File

@@ -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://'

View 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()');
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace App\Message;
final readonly class MailSyncRequested
{
public function __construct(
public ?string $folderPath = null,
) {}
}

View 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());
}
}
}