Message Brokers in PHP: From Hundreds of Lines to Just a Few
Message broker integration in PHP can be wrestling with exchange declarations, queue bindings, consumer configurations, and endless boilerplate. Setting up RabbitMQ, Kafka, or SQS often takes more code than the actual business logic we're trying to run asynchronously.
But what if that wouldn't need to be a case, what if we can build production ready asynchronous applications with few lines of code?
The Problem We've All Faced
Integration between PHP applications using message brokers can be challenging. We enter an area where many things can break and fail. Traditional setups require:
- Declaring exchanges and queues manually
- Configuring consumer acknowledgments
- Managing serialization/deserialization
- Handling connection failures
- Setting up retry mechanisms
Most developers spend hours just getting a basic message to flow through the system.
Few Lines Is All You Need
With Ecotone Framework, setting up a RabbitMQ (or any other Broker integration) -requires only few lines of code for whole production ready consuming or publishing application.
It all starts with choosing the implementation provider for Asynchronous Message Channel:
AmqpBackedMessageChannelBuilder::create('orders')Let me show you a complete working example. First, define your command:
class PlaceOrder
{
public function __construct(
public string $orderId,
public string $product
) {}
}Then create your handler with the `#[Asynchronous]` attribute:
class OrderHandler
{
#[Asynchronous('orders')]
#[CommandHandler(endpointId: 'orderHandler')]
public function handle(PlaceOrder $command): void
{
echo "Processing order: {$command->product}\n";
}
}Your business logic stays clean. The `#[Asynchronous('orders')]` attribute is all that connects your handler to the message broker.
Bootstrap whole Ecotone Application:
$ecotone = EcotoneLite::bootstrap(
classesToResolve: [OrderHandler::class],
containerOrAvailableServices: [
new OrderHandler(),
/** Connection to Message Broker */
AmqpConnectionFactory::class => new AmqpConnectionFactory([
'dsn' => 'amqp://guest:guest@localhost:5672/%2f'
]),
],
configuration: ServiceConfiguration::createWithDefaults()
->withExtensionObjects([
/** We define Message Channel (could be Kafka, SQS etc)
AmqpBackedMessageChannelBuilder::create('orders'),
])
);That's it. No exchange declarations, no queue bindings, no consumer configuration, no serializations. Ecotone handles everything.
Publisher
Then to publish all we need to do is to send the Command
$ecotone->getCommandBus()->send(new PlaceOrder('123', 'Milk'));This will go to asynchronous Message Channel
Consumer
To run the Message Consumer, we will simply use "run" method on the Ecotone Application:
$ecotone->run('async');That will run the Consumer and start consuming Messages.
Serialization Works Out of the Box
$ecotone->getCommandBus()->send(new PlaceOrder('123', 'Milk'));Notice how we're sending a proper PHP object, not a JSON string or array? Ecotone automatically handles serialization when publishing and deserialization when consuming:

Switch Message Brokers in Seconds
Here's where it gets interesting. Want to use Kafka instead of RabbitMQ? Just change one line:
- RabbitMQ:
AmqpBackedMessageChannelBuilder::create('orders') - Amazon SQS:
SqsBackedMessageChannelBuilder::create('orders') - Redis:
RedisBackedMessageChannelBuilder::create('orders') - Kafka:
KafkaMessageChannelBuilder::create('orders') - Database:
DbalBackedMessageChannelBuilder::create('orders')
Your business logic remains completely unchanged. The handler with `#[Asynchronous('orders')]` works with any of these brokers.
Production-Ready Features Built In
When you enable the DBAL module, you get message deduplication automatically. This prevents duplicate processing when messages are redelivered:
DbalConfiguration::createWithDefaults()
->withDeduplication(true)Need automatic retries with dead letter handling? Configure it once:
public function errorConfiguration(): ErrorHandlerConfiguration
{
return ErrorHandlerConfiguration::createWithDeadLetterChannel(
'errorChannel',
RetryTemplateBuilder::exponentialBackoff(1000, 10)
->maxRetryAttempts(3),
'dbal_dead_letter'
);
}When a message fails, Ecotone retries with exponential backoff. After 3 attempts, it moves to the dead letter queue where you can inspect and replay it later.
The Power of Working at a Higher Level
The solution to message broker complexity lies in working from a higher level abstraction. Ecotone provides this abstraction so we don't need to deal with low-level message broker concepts.
What's left to write is pure business logic itself:
- Define your commands as simple PHP classes
- Mark handlers as asynchronous with an attribute
- Send messages through the Command Bus
You can actually build production ready asynchronous application in seconds, rather than days. As Ecotone will handle queues, serialization, acknowledgments, retries, and error handling.
You can see whole example under this link.