Search code examples
laravellaravel-5queuelistenerjobs

How to set listener queue name from environment variable?


I just noticed that some of my listeners do not use the queue I expected them to use. Our team upgraded from Laravel 5.2 to 5.5 a few weeks back, and I guess this is when the problem started happening. There hasn't been much load on the system, so I only discovered it by accident.

Anyway. I used to set the queue name on the listener through a queue method, like so:

public function queue(QueueManager $handler, $method, $arguments): void
{
    $handler->connection()->push($method, $arguments, Queue::getNotificationQueue());
}

This approach is not working anymore, so a default queue ends up handling the job instead of the expected notification queue.

So I looked at the documentation https://laravel.com/docs/5.5/events#queued-event-listeners, which states that the name should be set on a queue property on the listener. My problem is that I have the queue name in an environment variable, so I cannot just set it directly as a property, as shown in the documentation and it does not work to set it on the constructor, like so:

protected $queue;

public function __construct()
{
    $this->queue = Queue::getNotificationQueue();
}

Does anyone here have an idea of how I can get around this?


Solution

  • Specifically for SQS queues the $queue property acts a bit weird because it doesn't seem to refer to queues defined in queue.php, but it expects a full queue url, so even the example in the documentation seems off.

    But for dynamic queue names on queued event listeners that for example changes depending on environment, making a custom SqsConnector and SqsQueue will be one way to solve your issue.

    Here is an example of implementation.

    ACMEEventListener.php

    class ACMEEventListener implements ShouldQueue
    {
        public function handle(Event $event): void
        {
            // I'm going to a custom queue
        }
    
        public static function getQueue(): string
        {
            return 'https://sqs.eu-central-1.amazonaws.com/<account id>/<queue name>';
        }
    }
    

    CustomSqsConnector.php

    use Illuminate\Queue\Connectors\SqsConnector;
    use Aws\Sqs\SqsClient;
    
    class CustomSqsConnector extends SqsConnector
    {
        public function connect(array $config)
        {
            $sqs = new SqsClient($config);
    
            return new CustomSqsQueue($sqs, $config['queue']);
        }
    }
    

    CustomSqsQueue.php

    class CustomSqsQueue extends \Illuminate\Queue\SqsQueue
    {
    
        public function push($job, $data = '', $queue = null)
        {
            if ($job instanceof CallQueuedListener && method_exists($job->class, 'getQueue')) {
                $queue = $job->class::getQueue();
            }
    
            return $this->pushRaw($this->createPayload($job, $data), $queue);
        }
    }
    

    CustomSqsQueueServiceProvider.php

    class CustomSqsQueueServiceProvider extends ServiceProvider
    {
        public function register(): void
        {
            $this->app->booted(function () {
                $this->app['queue']->extend('custom_sqs', function () {
                    return new CustomSqsConnector;
                });
            });
        }
    }
    

    And then in your queue.php, your default SQS connection driver from sqs to custom_sqs