You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Build univeros/messaging — a thin wrapper over Symfony Messenger that integrates with Altair\Container\Container and provides:
A simple dispatch(object $message) API exposed through a MessageBus shared service.
A bin/altair worker command to consume from configured transports.
A queue: block in the scaffolder (Queue Library #3) that emits job classes + handlers + bus wiring.
Like #4, we're wrapping not rewriting. Symfony Messenger has 6+ years of head start on transport adapters (AMQP, Redis Streams, SQS, Doctrine, Beanstalkd, Sync, InMemory) and a strong middleware story.
Why Messenger
The "should we build a queue package?" question came up earlier. The honest answer was: don't build the transports, ride Messenger. This issue is the bridge that makes that ergonomic.
The framework's value-add over raw Messenger:
DI'd handler resolution through the framework's container, not Symfony's
The same Action/Input shape applied to jobs — a job is just an asynchronous Action
Spec-driven scaffolding so queue: in the spec wires the whole chain
A WorkerCommand that integrates with the framework's signal handling, OpenTelemetry hooks (future), and exit-code conventions
That's it — no MessageHandlerInterface ceremony, no getHandledMessages() boilerplate. The attribute + __invoke is the contract.
Configuration
.env:
MESSENGER_TRANSPORT_DEFAULT=redis://localhost:6379/messages# orMESSENGER_TRANSPORT_DEFAULT=doctrine://default?queue_name=default# or for testsMESSENGER_TRANSPORT_DEFAULT=sync://
MessengerConfiguration parses this and wires Symfony's TransportFactory chain. Multiple transports supported via MESSENGER_TRANSPORT_HIGH=..., etc. Routing by message class via container config.
Worker CLI
bin/altair worker # consume default transport
bin/altair worker --transports=default,high # multiple
bin/altair worker --time-limit=3600 # exit after N seconds (for systemd / Kubernetes)
bin/altair worker --memory-limit=128M # exit when memory exceeds limit
bin/altair worker --limit=100 # exit after N messages
bin/altair worker:retry-failed # retry from the failure transport
bin/altair worker:show-failed # inspect failed messages
app/Messages/SendWelcomeEmail.php # message DTO (readonly)
app/Messages/SendWelcomeEmailHandler.php # handler stub with TODO
tests/Messages/SendWelcomeEmailHandlerTest.php # golden test stub
…and arranges that CreateUser (the domain class) gets a MessageBusInterface injected so it can dispatch the message when the endpoint succeeds.
Acceptance criteria
MessageBusInterface::dispatch works against sync://, redis://, and doctrine:// transports
Handlers discovered by #[AsHandler(MessageClass::class)] attribute scanning (no manual registration required for the common case)
Goal
Build
univeros/messaging— a thin wrapper over Symfony Messenger that integrates withAltair\Container\Containerand provides:dispatch(object $message)API exposed through aMessageBusshared service.bin/altair workercommand to consume from configured transports.queue:block in the scaffolder (Queue Library #3) that emits job classes + handlers + bus wiring.Like #4, we're wrapping not rewriting. Symfony Messenger has 6+ years of head start on transport adapters (AMQP, Redis Streams, SQS, Doctrine, Beanstalkd, Sync, InMemory) and a strong middleware story.
Why Messenger
The "should we build a queue package?" question came up earlier. The honest answer was: don't build the transports, ride Messenger. This issue is the bridge that makes that ergonomic.
The framework's value-add over raw Messenger:
queue:in the spec wires the whole chainWorkerCommandthat integrates with the framework's signal handling, OpenTelemetry hooks (future), and exit-code conventionsShape
API surface
Dispatching
Handlers
That's it — no
MessageHandlerInterfaceceremony, nogetHandledMessages()boilerplate. The attribute +__invokeis the contract.Configuration
.env:MessengerConfigurationparses this and wires Symfony'sTransportFactorychain. Multiple transports supported viaMESSENGER_TRANSPORT_HIGH=..., etc. Routing by message class via container config.Worker CLI
Graceful shutdown on SIGTERM / SIGINT. Exit codes follow Symfony conventions.
Scaffolder extension (depends on #3)
#3's spec format gains aqueue:block:The scaffolder now emits:
…and arranges that
CreateUser(the domain class) gets aMessageBusInterfaceinjected so it can dispatch the message when the endpoint succeeds.Acceptance criteria
MessageBusInterface::dispatchworks againstsync://,redis://, anddoctrine://transports#[AsHandler(MessageClass::class)]attribute scanning (no manual registration required for the common case)bin/altair workerconsumes messages, runs handlers, handles SIGTERM gracefullyworker:retry-failedre-dispatches themqueue:block produces a working message + handler + testMessageBus,HandlerLocator,ContainerHandlerMiddlewareOut of scope
symfony/schedulerintegration could be a follow-up)symfony/messenger-kafka)Dependencies
univeros/cli) — required (worker commands)univeros/scaffold) — required for the queue: blockNew composer deps:
symfony/messenger: ^7.0symfony/serializer: ^7.0(Messenger needs it)Optional (per-transport):
predis/predisfor redisdoctrine/dbalfor doctrine transport (already in Validation Tests #4)