Skip to content

A custom middleware for Symfony Messenger to discard(not send) or not handle certain messages

Published: at 08:18 AM

A guide on how to create a custom middleware for Symfony Messenger to discard(not send) or not handle certain messages.

Table of contents

Open Table of contents

Introduction

Recently, I’ve been working on a project where I needed to implement a logic where certain messages from Symfony Messenger would not get sent or handled if they met certain criteria. It was pretty hard to find a working solution and Symfony documentation didn’t provide a clear answer, so after I found a solution, I decided to write this article to help others who might encounter the same issue.

How Symfony Messenger works

When you dispatch a message in Symfony Messenger, it goes through a couple of predefined middlewares. The order is the following:

  1. add_bus_name_stamp_middleware
  2. dispatch_after_current_bus
  3. failed_message_processing_middleware
  4. YOUR CUSTOM MIDDLEWARES
  5. send_message
  6. handle_message

Creating a custom message interface

Here is a basic example of how you can create a custom message interface that will work with your custom middleware.

<?php
namespace App\Messenger;

interface CustomMessageInterface
{
    public function shouldSendEmail(): bool;
}

Now, let’s create a message that implements this interface.

<?php

namespace App\Messenger;

class SendEmailMessage implements CustomMessageInterface
{
    public function __construct(private readonly string $email)
    {
    }

    //If the email ends with @test.com, we should notb send the email
    public function shouldSendEmail(): bool
    {
        return false === str_ends_with($this->getEmail(), '@test.com');
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }
}

Creating the custom middleware

Now, let’s create the custom middleware that will check if the message should be sent or not.

<?php

namespace App\Messenger\Middleware;

use App\Messenger\CustomMessageInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;

class SendEmailMiddleware implements MiddlewareInterface
{
    public function handle(Envelope $envelope, StackInterface $stack): Envelope
    {
        $message = $envelope->getMessage();

        if ($message instanceof CustomMessageInterface && false === $message->shouldSendEmail()) {
            $envelope = $envelope->with(new ReceivedStamp(__CLASS__));
        }

        return $this->next->handle($envelope, $stack);
    }
}

Registering the custom middleware

To register the custom middleware, you need to add it to the messenger.yaml file.

framework:
  messenger:
    buses:
      messenger.bus.default:
        middleware:
          - App\Messenger\Middleware\SendEmailMiddleware

Sending but not handling the message

If you want to send the message but not handle it, you can use the HandledStamp class instead.

$envelope = $envelope->with(new HandledStamp('Handled from email middleware',__CLASS__));

Conclusion

This is very useful when you need to implement a custom logic for lots of messages, and you don’t want to clutter your code with if statements in each handler or dispatcher. If you only have one message that needs this logic, you can implement it directly in the handler or right before the dispatch.

Note: This is also very useful when you don’t have any handlers configured for the messages, but instead sending messages async to a queueing system, like RabbitMQ. In this case, the handler can be another application which you don’t have access to