├── RabbitMq ├── Exception │ ├── QueueNotFoundException.php │ ├── AckStopConsumerException.php │ └── StopConsumerException.php ├── DequeuerAwareInterface.php ├── Fallback.php ├── BatchConsumerInterface.php ├── ProducerInterface.php ├── AmqpPartsHolder.php ├── DequeuerInterface.php ├── AnonConsumer.php ├── ConsumerInterface.php ├── AMQPLoggedChannel.php ├── RpcServer.php ├── DynamicConsumer.php ├── Producer.php ├── Binding.php ├── MultipleConsumer.php ├── RpcClient.php ├── BaseConsumer.php ├── AMQPConnectionFactory.php ├── BaseAmqp.php ├── Consumer.php └── BatchConsumer.php ├── Event ├── AbstractAMQPEvent.php ├── OnConsumeEvent.php ├── AfterProcessingMessageEvent.php ├── BeforeProcessingMessageEvent.php ├── OnIdleEvent.php ├── AfterProducerPublishMessageEvent.php ├── BeforeProducerPublishMessageEvent.php └── AMQPEvent.php ├── CODEOWNERS ├── SECURITY.md ├── MemoryChecker ├── NativeMemoryUsageProvider.php └── MemoryConsumptionChecker.php ├── Command ├── ConsumerCommand.php ├── AnonConsumerCommand.php ├── BaseRabbitMqCommand.php ├── MultipleConsumerCommand.php ├── DynamicConsumerCommand.php ├── SetupFabricCommand.php ├── RpcServerCommand.php ├── DeleteCommand.php ├── PurgeConsumerCommand.php ├── StdInProducerCommand.php ├── BaseConsumerCommand.php └── BatchConsumerCommand.php ├── Provider ├── QueueOptionsProviderInterface.php ├── QueuesProviderInterface.php └── ConnectionParametersProviderInterface.php ├── DependencyInjection ├── Compiler │ ├── ServiceContainerPass.php │ ├── InjectEventDispatcherPass.php │ └── RegisterPartsPass.php ├── Configuration.php └── OldSoundRabbitMqExtension.php ├── LICENSE ├── DataCollector └── MessageDataCollector.php ├── OldSoundRabbitMqBundle.php ├── phpstan.neon.dist ├── composer.json ├── CHANGELOG └── Resources ├── views └── Collector │ └── collector.html.twig └── config └── rabbitmq.xml /RabbitMq/Exception/QueueNotFoundException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class NativeMemoryUsageProvider 11 | { 12 | /** 13 | * @return int 14 | */ 15 | public function getMemoryUsage() 16 | { 17 | return memory_get_usage(true); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Command/ConsumerCommand.php: -------------------------------------------------------------------------------- 1 | setDescription('Executes a consumer'); 11 | $this->setName('rabbitmq:consumer'); 12 | } 13 | 14 | protected function getConsumerService() 15 | { 16 | return 'old_sound_rabbit_mq.%s_consumer'; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /RabbitMq/AmqpPartsHolder.php: -------------------------------------------------------------------------------- 1 | parts = []; 12 | } 13 | 14 | public function addPart($type, BaseAmqp $part) 15 | { 16 | $this->parts[$type][] = $part; 17 | } 18 | 19 | public function getParts($type) 20 | { 21 | $type = (string) $type; 22 | return $this->parts[$type] ?? []; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Event/OnConsumeEvent.php: -------------------------------------------------------------------------------- 1 | setConsumer($consumer); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Provider/QueueOptionsProviderInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface QueueOptionsProviderInterface 11 | { 12 | /** 13 | * Return queue options 14 | * 15 | * Example: 16 | * array( 17 | * 'name' => 'example_context', 18 | * 'durable' => true, 19 | * 'routing_keys' => array('key.*') 20 | * ) 21 | * 22 | * @return array 23 | * 24 | */ 25 | public function getQueueOptions($context = null); 26 | } 27 | -------------------------------------------------------------------------------- /RabbitMq/DequeuerInterface.php: -------------------------------------------------------------------------------- 1 | setName('rabbitmq:anon-consumer'); 12 | $this->setDescription('Executes an anonymous consumer'); 13 | $this->getDefinition()->getOption('messages')->setDefault('1'); 14 | $this->getDefinition()->getOption('route')->setDefault('#'); 15 | } 16 | 17 | protected function getConsumerService() 18 | { 19 | return 'old_sound_rabbit_mq.%s_anon'; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /RabbitMq/AnonConsumer.php: -------------------------------------------------------------------------------- 1 | setQueueOptions([ 14 | 'name' => '', 15 | 'passive' => false, 16 | 'durable' => false, 17 | 'exclusive' => true, 18 | 'auto_delete' => true, 19 | 'nowait' => false, 20 | 'arguments' => null, 21 | 'ticket' => null, 22 | ]); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /RabbitMq/Exception/StopConsumerException.php: -------------------------------------------------------------------------------- 1 | container = $container; 18 | } 19 | 20 | /** 21 | * @return ContainerInterface 22 | */ 23 | public function getContainer() 24 | { 25 | return $this->container; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Event/AfterProcessingMessageEvent.php: -------------------------------------------------------------------------------- 1 | setConsumer($consumer); 25 | $this->setAMQPMessage($AMQPMessage); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Event/BeforeProcessingMessageEvent.php: -------------------------------------------------------------------------------- 1 | setConsumer($consumer); 25 | $this->setAMQPMessage($AMQPMessage); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Provider/QueuesProviderInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface QueuesProviderInterface 11 | { 12 | /** 13 | * Return array of queues 14 | * 15 | * Example: 16 | * array( 17 | * 'queue_name' => array( 18 | * 'durable' => false, 19 | * 'exclusive' => false, 20 | * 'passive' => false, 21 | * 'nowait' => false, 22 | * 'auto_delete' => false, 23 | * 'routing_keys' => array('key.1', 'key.2'), 24 | * 'arguments' => array(), 25 | * 'ticket' => '', 26 | * 'callback' => array($callback, 'execute') 27 | * ) 28 | * ); 29 | * @return array 30 | * 31 | */ 32 | public function getQueues(); 33 | } 34 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/ServiceContainerPass.php: -------------------------------------------------------------------------------- 1 | findTaggedServiceIds('console.command') as $id => $attributes) { 15 | $command = $container->findDefinition($id); 16 | if (is_a($command->getClass(), BaseRabbitMqCommand::class, true)) { 17 | $command->addMethodCall('setContainer', [new Reference('service_container')]); 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Command/MultipleConsumerCommand.php: -------------------------------------------------------------------------------- 1 | setDescription('Executes a consumer that uses multiple queues') 15 | ->setName('rabbitmq:multiple-consumer') 16 | ->addArgument('context', InputArgument::OPTIONAL, 'Context the consumer runs in') 17 | ; 18 | } 19 | 20 | protected function getConsumerService() 21 | { 22 | return 'old_sound_rabbit_mq.%s_multiple'; 23 | } 24 | 25 | protected function initConsumer(InputInterface $input) 26 | { 27 | parent::initConsumer($input); 28 | $this->consumer->setContext($input->getArgument('context')); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /RabbitMq/ConsumerInterface.php: -------------------------------------------------------------------------------- 1 | setConsumer($consumer); 29 | 30 | $this->forceStop = true; 31 | } 32 | 33 | /** 34 | * @return boolean 35 | */ 36 | public function isForceStop() 37 | { 38 | return $this->forceStop; 39 | } 40 | 41 | /** 42 | * @param boolean $forceStop 43 | */ 44 | public function setForceStop($forceStop) 45 | { 46 | $this->forceStop = $forceStop; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /RabbitMq/AMQPLoggedChannel.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class AMQPLoggedChannel extends AMQPChannel 13 | { 14 | private $basicPublishLog = []; 15 | 16 | public function basic_publish($msg, $exchange = '', $routingKey = '', $mandatory = false, $immediate = false, $ticket = null) 17 | { 18 | $this->basicPublishLog[] = [ 19 | 'msg' => $msg, 20 | 'exchange' => $exchange, 21 | 'routing_key' => $routingKey, 22 | 'mandatory' => $mandatory, 23 | 'immediate' => $immediate, 24 | 'ticket' => $ticket, 25 | ]; 26 | 27 | parent::basic_publish($msg, $exchange, $routingKey, $mandatory, $immediate, $ticket); 28 | } 29 | 30 | public function getBasicPublishLog() 31 | { 32 | return $this->basicPublishLog; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Event/AfterProducerPublishMessageEvent.php: -------------------------------------------------------------------------------- 1 | setProducer($producer); 30 | $this->setAMQPMessage($AMQPMessage); 31 | $this->routingKey = $routingKey; 32 | } 33 | 34 | /** 35 | * @return string 36 | */ 37 | public function getRoutingKey() 38 | { 39 | return $this->routingKey; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Event/BeforeProducerPublishMessageEvent.php: -------------------------------------------------------------------------------- 1 | setProducer($producer); 30 | $this->setAMQPMessage($AMQPMessage); 31 | $this->routingKey = $routingKey; 32 | } 33 | 34 | /** 35 | * @return string 36 | */ 37 | public function getRoutingKey() 38 | { 39 | return $this->routingKey; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Provider/ConnectionParametersProviderInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ConnectionParametersProviderInterface 11 | { 12 | /** 13 | * Return connection parameters. 14 | * 15 | * Example: 16 | * array( 17 | * 'host' => 'localhost', 18 | * 'port' => 5672, 19 | * 'user' => 'guest', 20 | * 'password' => 'guest', 21 | * 'vhost' => '/', 22 | * 'lazy' => false, 23 | * 'connection_timeout' => 3, 24 | * 'read_write_timeout' => 3, 25 | * 'keepalive' => false, 26 | * 'heartbeat' => 0, 27 | * 'use_socket' => true, 28 | * 'constructor_args' => array(...) 29 | * ) 30 | * 31 | * If constructor_args is present, all the other parameters are ignored; constructor_args are passes as constructor 32 | * arguments. 33 | * 34 | * @return array 35 | */ 36 | public function getConnectionParameters(); 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2010 Alvaro Videla 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Command/DynamicConsumerCommand.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | 12 | namespace OldSound\RabbitMqBundle\Command; 13 | 14 | use Symfony\Component\Console\Input\InputArgument; 15 | use Symfony\Component\Console\Input\InputInterface; 16 | 17 | class DynamicConsumerCommand extends BaseConsumerCommand 18 | { 19 | protected function configure(): void 20 | { 21 | parent::configure(); 22 | 23 | $this 24 | ->setName('rabbitmq:dynamic-consumer') 25 | ->setDescription('Executes context-aware consumer') 26 | ->addArgument('context', InputArgument::REQUIRED, 'Context the consumer runs in') 27 | ; 28 | } 29 | 30 | protected function getConsumerService() 31 | { 32 | return 'old_sound_rabbit_mq.%s_dynamic'; 33 | } 34 | 35 | protected function initConsumer(InputInterface $input) 36 | { 37 | parent::initConsumer($input); 38 | $this->consumer->setContext($input->getArgument('context')); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /DataCollector/MessageDataCollector.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class MessageDataCollector extends DataCollector 15 | { 16 | private $channels; 17 | 18 | public function __construct($channels) 19 | { 20 | $this->channels = $channels; 21 | $this->data = []; 22 | } 23 | 24 | public function collect(Request $request, Response $response, ?\Throwable $exception = null) 25 | { 26 | foreach ($this->channels as $channel) { 27 | foreach ($channel->getBasicPublishLog() as $log) { 28 | $this->data[] = $log; 29 | } 30 | } 31 | } 32 | 33 | public function getName() 34 | { 35 | return 'rabbit_mq'; 36 | } 37 | 38 | public function getPublishedMessagesCount() 39 | { 40 | return count($this->data); 41 | } 42 | 43 | public function getPublishedMessagesLog() 44 | { 45 | return $this->data; 46 | } 47 | 48 | public function reset() 49 | { 50 | $this->data = []; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/InjectEventDispatcherPass.php: -------------------------------------------------------------------------------- 1 | has(self::EVENT_DISPATCHER_SERVICE_ID)) { 22 | return; 23 | } 24 | $taggedConsumers = $container->findTaggedServiceIds('old_sound_rabbit_mq.base_amqp'); 25 | 26 | foreach ($taggedConsumers as $id => $tag) { 27 | $definition = $container->getDefinition($id); 28 | $definition->addMethodCall( 29 | 'setEventDispatcher', 30 | [ 31 | new Reference(self::EVENT_DISPATCHER_SERVICE_ID, ContainerInterface::IGNORE_ON_INVALID_REFERENCE), 32 | ] 33 | ); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /OldSoundRabbitMqBundle.php: -------------------------------------------------------------------------------- 1 | addCompilerPass(new RegisterPartsPass()); 18 | $container->addCompilerPass(new InjectEventDispatcherPass()); 19 | $container->addCompilerPass(new ServiceContainerPass()); 20 | } 21 | 22 | /** 23 | * {@inheritDoc} 24 | */ 25 | public function shutdown(): void 26 | { 27 | parent::shutdown(); 28 | if (!$this->container->hasParameter('old_sound_rabbit_mq.base_amqp')) { 29 | return; 30 | } 31 | $connections = $this->container->getParameter('old_sound_rabbit_mq.base_amqp'); 32 | foreach ($connections as $connection) { 33 | if ($this->container->initialized($connection)) { 34 | $this->container->get($connection)->close(); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /RabbitMq/RpcServer.php: -------------------------------------------------------------------------------- 1 | setExchangeOptions(['name' => $name, 'type' => 'direct']); 14 | $this->setQueueOptions(['name' => $name . '-queue']); 15 | } 16 | 17 | public function processMessage(AMQPMessage $msg) 18 | { 19 | try { 20 | $msg->ack(); 21 | $result = call_user_func($this->callback, $msg); 22 | $result = call_user_func($this->serializer, $result); 23 | $this->sendReply($result, $msg->get('reply_to'), $msg->get('correlation_id')); 24 | $this->consumed++; 25 | $this->maybeStopConsumer(); 26 | } catch (\Exception $e) { 27 | $this->sendReply('error: ' . $e->getMessage(), $msg->get('reply_to'), $msg->get('correlation_id')); 28 | } 29 | } 30 | 31 | protected function sendReply($result, $client, $correlationId) 32 | { 33 | $reply = new AMQPMessage($result, ['content_type' => 'text/plain', 'correlation_id' => $correlationId]); 34 | $this->getChannel()->basic_publish($reply, '', $client); 35 | } 36 | 37 | public function setSerializer($serializer) 38 | { 39 | $this->serializer = $serializer; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - vendor/phpstan/phpstan-phpunit/extension.neon 3 | - vendor/phpstan/phpstan-phpunit/rules.neon 4 | parameters: 5 | level: 5 6 | reportUnmatchedIgnoredErrors: false 7 | paths: 8 | - Command 9 | - DataCollector 10 | - DependencyInjection 11 | - Event 12 | - MemoryChecker 13 | - Provider 14 | - RabbitMq 15 | - Resources 16 | - Tests 17 | - OldSoundRabbitMqBundle.php 18 | ignoreErrors: 19 | - '#Call to an undefined method Symfony\\Component\\DependencyInjection\\Definition::((setFactoryService)|(setFactoryMethod))\(\)\.#' 20 | - '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::((children)|(append))\(\)\.#' 21 | - '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeParentInterface::((booleanNode)|(scalarNode))\(\)#' 22 | - '#Parameter \#1 \$node of method OldSound\\RabbitMqBundle\\DependencyInjection\\Configuration::addQueueNodeConfiguration\(\) expects Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition, Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition given\.#' 23 | - '#Method Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface::dispatch\(\) invoked with 2 parameters, 1 required\.#' 24 | - "#^Call to an undefined method Symfony\\\\Component\\\\Console\\\\Helper\\\\HelperInterface\\:\\:ask\\(\\)\\.$#" -------------------------------------------------------------------------------- /RabbitMq/DynamicConsumer.php: -------------------------------------------------------------------------------- 1 | queueOptionsProvider = $queueOptionsProvider; 33 | return $this; 34 | } 35 | 36 | public function setContext($context) 37 | { 38 | $this->context = $context; 39 | } 40 | 41 | 42 | protected function setupConsumer() 43 | { 44 | $this->mergeQueueOptions(); 45 | parent::setupConsumer(); 46 | } 47 | 48 | protected function mergeQueueOptions() 49 | { 50 | if (null === $this->queueOptionsProvider) { 51 | return; 52 | } 53 | $this->queueOptions = array_merge($this->queueOptions, $this->queueOptionsProvider->getQueueOptions($this->context)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Command/SetupFabricCommand.php: -------------------------------------------------------------------------------- 1 | setName('rabbitmq:setup-fabric') 16 | ->setDescription('Sets up the Rabbit MQ fabric') 17 | ->addOption('debug', 'd', InputOption::VALUE_NONE, 'Enable Debugging') 18 | ; 19 | } 20 | 21 | protected function execute(InputInterface $input, OutputInterface $output): int 22 | { 23 | if (defined('AMQP_DEBUG') === false) { 24 | define('AMQP_DEBUG', (bool) $input->getOption('debug')); 25 | } 26 | 27 | $output->writeln('Setting up the Rabbit MQ fabric'); 28 | 29 | $partsHolder = $this->getContainer()->get('old_sound_rabbit_mq.parts_holder'); 30 | 31 | foreach (['base_amqp', 'binding'] as $key) { 32 | foreach ($partsHolder->getParts('old_sound_rabbit_mq.' . $key) as $baseAmqp) { 33 | if ($baseAmqp instanceof DynamicConsumer) { 34 | continue; 35 | } 36 | $baseAmqp->setupFabric(); 37 | } 38 | } 39 | 40 | return 0; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/RegisterPartsPass.php: -------------------------------------------------------------------------------- 1 | findTaggedServiceIds('old_sound_rabbit_mq.base_amqp'); 14 | $container->setParameter('old_sound_rabbit_mq.base_amqp', array_keys($services)); 15 | if (!$container->hasDefinition('old_sound_rabbit_mq.parts_holder')) { 16 | return; 17 | } 18 | 19 | $definition = $container->getDefinition('old_sound_rabbit_mq.parts_holder'); 20 | 21 | $tags = [ 22 | 'old_sound_rabbit_mq.base_amqp', 23 | 'old_sound_rabbit_mq.binding', 24 | 'old_sound_rabbit_mq.producer', 25 | 'old_sound_rabbit_mq.consumer', 26 | 'old_sound_rabbit_mq.multi_consumer', 27 | 'old_sound_rabbit_mq.anon_consumer', 28 | 'old_sound_rabbit_mq.batch_consumer', 29 | 'old_sound_rabbit_mq.rpc_client', 30 | 'old_sound_rabbit_mq.rpc_server', 31 | ]; 32 | 33 | foreach ($tags as $tag) { 34 | foreach ($container->findTaggedServiceIds($tag) as $id => $attributes) { 35 | $definition->addMethodCall('addPart', [$tag, new Reference($id)]); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Command/RpcServerCommand.php: -------------------------------------------------------------------------------- 1 | setName('rabbitmq:rpc-server') 18 | ->setDescription('Start an RPC server') 19 | ->addArgument('name', InputArgument::REQUIRED, 'Server Name') 20 | ->addOption('messages', 'm', InputOption::VALUE_OPTIONAL, 'Messages to consume', '0') 21 | ->addOption('debug', 'd', InputOption::VALUE_OPTIONAL, 'Debug mode', false) 22 | ; 23 | } 24 | 25 | /** 26 | * Executes the current command. 27 | * 28 | * @param InputInterface $input An InputInterface instance 29 | * @param OutputInterface $output An OutputInterface instance 30 | * 31 | * @return integer 0 if everything went fine, or an error code 32 | * 33 | * @throws \InvalidArgumentException When the number of messages to consume is less than 0 34 | */ 35 | protected function execute(InputInterface $input, OutputInterface $output): int 36 | { 37 | define('AMQP_DEBUG', (bool) $input->getOption('debug')); 38 | $amount = (int)$input->getOption('messages'); 39 | 40 | if (0 > $amount) { 41 | throw new \InvalidArgumentException("The -m option should be null or greater than 0"); 42 | } 43 | 44 | $this->getContainer() 45 | ->get(sprintf('old_sound_rabbit_mq.%s_server', $input->getArgument('name'))) 46 | ->start($amount); 47 | 48 | return 0; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /MemoryChecker/MemoryConsumptionChecker.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class MemoryConsumptionChecker 11 | { 12 | /** @var NativeMemoryUsageProvider */ 13 | private $memoryUsageProvider; 14 | 15 | /** 16 | * MemoryManager constructor. 17 | * 18 | * @param NativeMemoryUsageProvider $memoryUsageProvider 19 | */ 20 | public function __construct(NativeMemoryUsageProvider $memoryUsageProvider) 21 | { 22 | $this->memoryUsageProvider = $memoryUsageProvider; 23 | } 24 | 25 | /** 26 | * @param int|string $allowedConsumptionUntil 27 | * @param int|string $maxConsumptionAllowed 28 | * 29 | * @return bool 30 | */ 31 | public function isRamAlmostOverloaded($maxConsumptionAllowed, $allowedConsumptionUntil = 0) 32 | { 33 | $allowedConsumptionUntil = $this->convertHumanUnitToNumerical($allowedConsumptionUntil); 34 | $maxConsumptionAllowed = $this->convertHumanUnitToNumerical($maxConsumptionAllowed); 35 | $currentUsage = $this->convertHumanUnitToNumerical($this->memoryUsageProvider->getMemoryUsage()); 36 | 37 | return $currentUsage > ($maxConsumptionAllowed - $allowedConsumptionUntil); 38 | } 39 | 40 | /** 41 | * @param int|string $humanUnit 42 | * 43 | * @return int 44 | */ 45 | private function convertHumanUnitToNumerical($humanUnit) 46 | { 47 | $numerical = $humanUnit; 48 | if (!is_numeric($humanUnit)) { 49 | $numerical = (int) substr($numerical, 0, -1); 50 | switch (substr($humanUnit, -1)) { 51 | case 'G': 52 | $numerical *= pow(1024, 3); 53 | break; 54 | case 'M': 55 | $numerical *= pow(1024, 2); 56 | break; 57 | case 'K': 58 | $numerical *= 1024; 59 | break; 60 | } 61 | } 62 | 63 | return (int)$numerical; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "php-amqplib/rabbitmq-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Integrates php-amqplib with Symfony & RabbitMq. Formerly emag-tech-labs/rabbitmq-bundle, oldsound/rabbitmq-bundle.", 5 | "keywords": ["symfony", "symfony4", "symfony5", "rabbitmq", "message", "queue", "amqp"], 6 | "license": "MIT", 7 | "authors": [{ 8 | "name" : "Alvaro Videla" 9 | }], 10 | "require": { 11 | "php": "^7.4|^8.0", 12 | 13 | "symfony/dependency-injection": "^4.4|^5.3|^6.0|^7.0", 14 | "symfony/event-dispatcher": "^4.4|^5.3|^6.0|^7.0", 15 | "symfony/config": "^4.4|^5.3|^6.0|^7.0", 16 | "symfony/yaml": "^4.4|^5.3|^6.0|^7.0", 17 | "symfony/console": "^4.4|^5.3|^6.0|^7.0", 18 | "php-amqplib/php-amqplib": "^2.12.2|^3.0", 19 | "psr/log": "^1.0 || ^2.0 || ^3.0", 20 | "symfony/http-kernel": "^4.4|^5.3|^6.0|^7.0", 21 | "symfony/framework-bundle": "^4.4|^5.3|^6.0|^7.0" 22 | }, 23 | "require-dev": { 24 | "symfony/serializer": "^4.4|^5.3|^6.0|^7.0", 25 | "phpunit/phpunit": "^9.5", 26 | "phpstan/phpstan": "^1.2", 27 | "phpstan/phpstan-phpunit": "^1.0" 28 | }, 29 | "replace": { 30 | "oldsound/rabbitmq-bundle": "self.version", 31 | "emag-tech-labs/rabbitmq-bundle": "self.version" 32 | }, 33 | "suggest": { 34 | "ext-pcntl": "*", 35 | "symfony/framework-bundle": "To use this lib as a full Symfony Bundle and to use the profiler data collector" 36 | }, 37 | "extra": { 38 | "branch-alias": { 39 | "dev-master": "1.10.x-dev" 40 | } 41 | }, 42 | "autoload": { 43 | "psr-4": { 44 | "OldSound\\RabbitMqBundle\\": "" 45 | }, 46 | "exclude-from-classmap": [ 47 | "/Tests/" 48 | ] 49 | }, 50 | "autoload-dev": { 51 | "psr-4": { 52 | "OldSound\\RabbitMqBundle\\Tests\\": "Tests/" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Command/DeleteCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('name', InputArgument::REQUIRED, 'Consumer Name') 19 | ->setDescription('Delete a consumer\'s queue') 20 | ->addOption('no-confirmation', null, InputOption::VALUE_NONE, 'Whether it must be confirmed before deleting'); 21 | 22 | $this->setName('rabbitmq:delete'); 23 | } 24 | 25 | protected function initialize(InputInterface $input, OutputInterface $output): void 26 | { 27 | // nothing to initialize here as BaseConsumerCommand initializes on option that is not available here 28 | } 29 | 30 | /** 31 | * @param InputInterface $input 32 | * @param OutputInterface $output 33 | * 34 | * @return int 35 | */ 36 | protected function execute(InputInterface $input, OutputInterface $output): int 37 | { 38 | $noConfirmation = (bool) $input->getOption('no-confirmation'); 39 | 40 | if (!$noConfirmation && $input->isInteractive()) { 41 | $question = new ConfirmationQuestion( 42 | sprintf( 43 | 'Are you sure you wish to delete "%s" consumer\'s queue? (y/n)', 44 | $input->getArgument('name') 45 | ), 46 | false 47 | ); 48 | 49 | if (!$this->getHelper('question')->ask($input, $output, $question)) { 50 | $output->writeln('Deletion cancelled!'); 51 | 52 | return 1; 53 | } 54 | } 55 | 56 | $this->consumer = $this->getContainer() 57 | ->get(sprintf($this->getConsumerService(), $input->getArgument('name'))); 58 | $this->consumer->delete(); 59 | 60 | return 0; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Command/PurgeConsumerCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('name', InputArgument::REQUIRED, 'Consumer Name') 19 | ->setDescription('Purge a consumer\'s queue') 20 | ->addOption('no-confirmation', null, InputOption::VALUE_NONE, 'Whether it must be confirmed before purging'); 21 | 22 | $this->setName('rabbitmq:purge'); 23 | } 24 | 25 | protected function initialize(InputInterface $input, OutputInterface $output): void 26 | { 27 | // nothing to initialize here as BaseConsumerCommand initializes on option that is not available here 28 | } 29 | 30 | /** 31 | * @param InputInterface $input 32 | * @param OutputInterface $output 33 | * 34 | * @return int 35 | */ 36 | protected function execute(InputInterface $input, OutputInterface $output): int 37 | { 38 | $noConfirmation = (bool) $input->getOption('no-confirmation'); 39 | 40 | if (!$noConfirmation && $input->isInteractive()) { 41 | $question = new ConfirmationQuestion( 42 | sprintf( 43 | 'Are you sure you wish to purge "%s" queue? (y/n)', 44 | $input->getArgument('name') 45 | ), 46 | false 47 | ); 48 | 49 | if (!$this->getHelper('question')->ask($input, $output, $question)) { 50 | $output->writeln('Purging cancelled!'); 51 | 52 | return 1; 53 | } 54 | } 55 | 56 | $this->consumer = $this->getContainer() 57 | ->get(sprintf($this->getConsumerService(), $input->getArgument('name'))); 58 | $this->consumer->purge($input->getArgument('name')); 59 | 60 | return 0; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Event/AMQPEvent.php: -------------------------------------------------------------------------------- 1 | AMQPMessage; 45 | } 46 | 47 | /** 48 | * @param AMQPMessage $AMQPMessage 49 | * 50 | * @return AMQPEvent 51 | */ 52 | public function setAMQPMessage(AMQPMessage $AMQPMessage) 53 | { 54 | $this->AMQPMessage = $AMQPMessage; 55 | 56 | return $this; 57 | } 58 | 59 | /** 60 | * @return Consumer 61 | */ 62 | public function getConsumer() 63 | { 64 | return $this->consumer; 65 | } 66 | 67 | /** 68 | * @param Consumer $consumer 69 | * 70 | * @return AMQPEvent 71 | */ 72 | public function setConsumer(Consumer $consumer) 73 | { 74 | $this->consumer = $consumer; 75 | 76 | return $this; 77 | } 78 | 79 | /** 80 | * @return Producer 81 | */ 82 | public function getProducer() 83 | { 84 | return $this->producer; 85 | } 86 | 87 | /** 88 | * @param Producer $producer 89 | * 90 | * @return AMQPEvent 91 | */ 92 | public function setProducer(Producer $producer) 93 | { 94 | $this->producer = $producer; 95 | 96 | return $this; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Command/StdInProducerCommand.php: -------------------------------------------------------------------------------- 1 | setName('rabbitmq:stdin-producer') 21 | ->addArgument('name', InputArgument::REQUIRED, 'Producer Name') 22 | ->setDescription('Executes a producer that reads data from STDIN') 23 | ->addOption('route', 'r', InputOption::VALUE_OPTIONAL, 'Routing Key', '') 24 | ->addOption('format', 'f', InputOption::VALUE_OPTIONAL, 'Payload Format', self::FORMAT_PHP) 25 | ->addOption('debug', 'd', InputOption::VALUE_OPTIONAL, 'Enable Debugging', false) 26 | ; 27 | } 28 | 29 | /** 30 | * Executes the current command. 31 | * 32 | * @param InputInterface $input An InputInterface instance 33 | * @param OutputInterface $output An OutputInterface instance 34 | * 35 | * @return integer 0 if everything went fine, or an error code 36 | */ 37 | protected function execute(InputInterface $input, OutputInterface $output): int 38 | { 39 | define('AMQP_DEBUG', (bool) $input->getOption('debug')); 40 | 41 | $producer = $this->getContainer()->get(sprintf('old_sound_rabbit_mq.%s_producer', $input->getArgument('name'))); 42 | 43 | $data = ''; 44 | while (!feof(STDIN)) { 45 | $data .= fread(STDIN, 8192); 46 | } 47 | 48 | $route = $input->getOption('route'); 49 | $format = $input->getOption('format'); 50 | 51 | switch ($format) { 52 | case self::FORMAT_RAW: 53 | break; // data as is 54 | case self::FORMAT_PHP: 55 | $data = serialize($data); 56 | break; 57 | default: 58 | throw new \InvalidArgumentException(sprintf( 59 | 'Invalid payload format "%s", expecting one of: %s.', 60 | $format, 61 | implode(', ', [self::FORMAT_PHP, self::FORMAT_RAW]) 62 | )); 63 | } 64 | 65 | $producer->publish($data, $route); 66 | 67 | return 0; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /RabbitMq/Producer.php: -------------------------------------------------------------------------------- 1 | contentType = $contentType; 23 | 24 | return $this; 25 | } 26 | 27 | public function setDeliveryMode($deliveryMode) 28 | { 29 | $this->deliveryMode = $deliveryMode; 30 | 31 | return $this; 32 | } 33 | 34 | public function setDefaultRoutingKey($defaultRoutingKey) 35 | { 36 | $this->defaultRoutingKey = $defaultRoutingKey; 37 | 38 | return $this; 39 | } 40 | 41 | protected function getBasicProperties() 42 | { 43 | return ['content_type' => $this->contentType, 'delivery_mode' => $this->deliveryMode]; 44 | } 45 | 46 | /** 47 | * Publishes the message and merges additional properties with basic properties 48 | * 49 | * @param string $msgBody 50 | * @param string $routingKey 51 | * @param array $additionalProperties 52 | * @param array $headers 53 | */ 54 | public function publish($msgBody, $routingKey = null, $additionalProperties = [], ?array $headers = null) 55 | { 56 | if ($this->autoSetupFabric) { 57 | $this->setupFabric(); 58 | } 59 | 60 | $msg = new AMQPMessage((string) $msgBody, array_merge($this->getBasicProperties(), $additionalProperties)); 61 | 62 | if (!empty($headers)) { 63 | $headersTable = new AMQPTable($headers); 64 | $msg->set('application_headers', $headersTable); 65 | } 66 | 67 | $real_routingKey = $routingKey !== null ? $routingKey : $this->defaultRoutingKey; 68 | 69 | $this->dispatchEvent( 70 | BeforeProducerPublishMessageEvent::NAME, 71 | new BeforeProducerPublishMessageEvent($this, $msg, $real_routingKey) 72 | ); 73 | 74 | $this->getChannel()->basic_publish($msg, $this->exchangeOptions['name'], (string)$real_routingKey); 75 | $this->logger->debug('AMQP message published', [ 76 | 'amqp' => [ 77 | 'body' => $msgBody, 78 | 'routingkey' => $real_routingKey, 79 | 'properties' => $additionalProperties, 80 | 'headers' => $headers, 81 | ], 82 | ]); 83 | 84 | $this->dispatchEvent( 85 | AfterProducerPublishMessageEvent::NAME, 86 | new AfterProducerPublishMessageEvent($this, $msg, $real_routingKey) 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | - 2024-03-18 2 | * Add bundle configuration for `login_method` for RabbitMQ connections, this allow to configure other values than default AMQPLAIN (PLAIN or EXTERNAL), see https://www.rabbitmq.com/docs/access-control#mechanisms 3 | 4 | - 2023-11-07 5 | * Add consumer option `no_ack` 6 | 7 | - 2021-05-15 8 | * Add possibility to use multiple RabbitMQ hosts 9 | 10 | - 2017-01-22 11 | * Add `graceful_max_execution_timeout` 12 | 13 | - 2015-02-07 14 | * Added possibility to set serialize/unserialize function for rpc servers/clients 15 | 16 | - 2014-11-27 17 | * Added interface `OldSound\RabbitMqBundle\Provider\QueuesProviderInterface` 18 | * Added `queues_provider` configuration for multiple consumer 19 | 20 | - 2014-07-21 21 | * Added `reconnect` method into `OldSound\RabbitMqBundle\RabbitMq\BaseAmqp` 22 | 23 | - 2014-02-24 24 | * Add a parameter to RPC client configuration to disable auto unserialize when adding call results to results array. 25 | 26 | - 2013-01-18 27 | * adds an an optional parameter for the AMQP Message Properties for the publish method of the Producer, so they can be set as well. For example, seeting the application_headers is now possible. 28 | 29 | - 2012-06-04 30 | * Revert PR #46. It is still possible to override parameter classes but in a proper way. 31 | * Some default options for exchanges declared in the "producers" config section 32 | have changed to match the defaults of exchanges declared in the "consumers" section. 33 | The affected settings are: 34 | * `durable` was changed from `false` to `true`, 35 | * `auto_delete` was changed from `true` to `false`. 36 | * Adds basic_reject functionality to consumers. A message can be rejected by returning `false` from a callback. 37 | 38 | - 2012-05-29 39 | * Merged PR #46 adding compiler passes for the configuration. Now the parameter classes can be overriden. 40 | * Treats queue arguments as a variableNode in the configuration to allow declaring HA Queues. 41 | 42 | - 2012-01-03 43 | * Merged PR #14 Now instances of `PhpAmqpLib\Message\AMQPMessage` are passed to consumers instead of just the message body. 44 | The message body can be obtained then by writing `$msg->body` inside the `execute` method. 45 | * Merged PR #13 removing the need for the ContainerAwareInterface on consumers. 46 | * `rabbitmq:consumer` now takes a `--route` argument that can be used to specify the routing key. 47 | 48 | - 2011-11-25: 49 | * Fixed autoload configuration example 50 | * Adds a producer that can publish data coming from STDIN. The use case will be to use it in combination with unix pipes. 51 | 52 | - 2011-11-24: 53 | * The rabbitmq:consumer command consumes messages forever unless the -m option is provided. 54 | * The -m option for the rabbitmq:consumer command must be greater null or greater than 0. 55 | * Fixed issues #2 #7 #11 and #12. 56 | * Updated the bundle to use the latest version from videlalvaro/php-amqplib. 57 | * Updated installation/setup instructions. 58 | -------------------------------------------------------------------------------- /RabbitMq/Binding.php: -------------------------------------------------------------------------------- 1 | exchange; 43 | } 44 | 45 | /** 46 | * @param string $exchange 47 | */ 48 | public function setExchange($exchange) 49 | { 50 | $this->exchange = $exchange; 51 | } 52 | 53 | /** 54 | * @return string 55 | */ 56 | public function getDestination() 57 | { 58 | return $this->destination; 59 | } 60 | 61 | /** 62 | * @param string $destination 63 | */ 64 | public function setDestination($destination) 65 | { 66 | $this->destination = $destination; 67 | } 68 | 69 | /** 70 | * @return bool 71 | */ 72 | public function getDestinationIsExchange() 73 | { 74 | return $this->destinationIsExchange; 75 | } 76 | 77 | /** 78 | * @param bool $destinationIsExchange 79 | */ 80 | public function setDestinationIsExchange($destinationIsExchange) 81 | { 82 | $this->destinationIsExchange = $destinationIsExchange; 83 | } 84 | 85 | /** 86 | * @return string 87 | */ 88 | public function getRoutingKey() 89 | { 90 | return $this->routingKey; 91 | } 92 | 93 | /** 94 | * @param string $routingKey 95 | */ 96 | public function setRoutingKey($routingKey) 97 | { 98 | $this->routingKey = $routingKey; 99 | } 100 | 101 | /** 102 | * @return boolean 103 | */ 104 | public function isNowait() 105 | { 106 | return $this->nowait; 107 | } 108 | 109 | /** 110 | * @param boolean $nowait 111 | */ 112 | public function setNowait($nowait) 113 | { 114 | $this->nowait = $nowait; 115 | } 116 | 117 | /** 118 | * @return array 119 | */ 120 | public function getArguments() 121 | { 122 | return $this->arguments; 123 | } 124 | 125 | /** 126 | * @param array $arguments 127 | */ 128 | public function setArguments($arguments) 129 | { 130 | $this->arguments = $arguments; 131 | } 132 | 133 | 134 | /** 135 | * create bindings 136 | * 137 | * @return void 138 | */ 139 | public function setupFabric() 140 | { 141 | $method = ($this->destinationIsExchange) ? 'exchange_bind' : 'queue_bind'; 142 | $channel = $this->getChannel(); 143 | call_user_func( 144 | [$channel, $method], 145 | $this->destination, 146 | $this->exchange, 147 | $this->routingKey, 148 | $this->nowait, 149 | $this->arguments 150 | ); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Resources/views/Collector/collector.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'WebProfilerBundle:Profiler:layout.html.twig' %} 2 | 3 | {% block toolbar %} 4 | {% if collector.publishedMessagesCount %} 5 | {% set icon %} 6 | RabbitMQ 7 | {{ collector.publishedMessagesCount }} 8 | {% endset %} 9 | {% set text %} 10 |
11 | Messages 12 | {{ collector.publishedMessagesCount }} 13 |
14 | {% endset %} 15 | {% include 'WebProfilerBundle:Profiler:toolbar_item.html.twig' with { 'link': profiler_url } %} 16 | {% endif %} 17 | {% endblock %} 18 | 19 | {% block menu %} 20 | 21 | RabbitMQ 22 | RabbitMQ 23 | 24 | {{ collector.publishedMessagesCount }} 25 | 26 | 27 | {% endblock %} 28 | 29 | {% block panel %} 30 |

Messages

31 | {% if collector.publishedMessagesCount %} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {% for log in collector.publishedMessagesLog %} 41 | 42 | 43 | 44 | 45 | {% endfor %} 46 | 47 |
ExchangeMessage body
{{ log.exchange }}{{ log.msg.body }}
48 | {% else %} 49 |

50 | No messages were sent. 51 |

52 | {% endif %} 53 | {% endblock %} 54 | -------------------------------------------------------------------------------- /RabbitMq/MultipleConsumer.php: -------------------------------------------------------------------------------- 1 | queuesProvider = $queuesProvider; 37 | return $this; 38 | } 39 | 40 | public function getQueueConsumerTag($queue) 41 | { 42 | return sprintf('%s-%s', $this->getConsumerTag(), $queue); 43 | } 44 | 45 | public function setQueues(array $queues) 46 | { 47 | $this->queues = $queues; 48 | } 49 | 50 | public function setContext($context) 51 | { 52 | $this->context = $context; 53 | } 54 | 55 | protected function setupConsumer() 56 | { 57 | $this->mergeQueues(); 58 | 59 | if ($this->autoSetupFabric) { 60 | $this->setupFabric(); 61 | } 62 | 63 | foreach ($this->queues as $name => $options) { 64 | //PHP 5.3 Compliant 65 | $currentObject = $this; 66 | 67 | $this->getChannel()->basic_consume($name, $this->getQueueConsumerTag($name), false, $this->consumerOptions['no_ack'], false, false, function (AMQPMessage $msg) use ($currentObject, $name) { 68 | $currentObject->processQueueMessage($name, $msg); 69 | }); 70 | } 71 | } 72 | 73 | protected function queueDeclare() 74 | { 75 | foreach ($this->queues as $name => $options) { 76 | [$queueName, , ] = $this->getChannel()->queue_declare( 77 | $name, 78 | $options['passive'], 79 | $options['durable'], 80 | $options['exclusive'], 81 | $options['auto_delete'], 82 | $options['nowait'], 83 | $options['arguments'], 84 | $options['ticket'] 85 | ); 86 | 87 | if (isset($options['routing_keys']) && count($options['routing_keys']) > 0) { 88 | foreach ($options['routing_keys'] as $routingKey) { 89 | $this->queueBind($queueName, $this->exchangeOptions['name'], $routingKey, $options['arguments'] ?? []); 90 | } 91 | } else { 92 | $this->queueBind($queueName, $this->exchangeOptions['name'], $this->routingKey, $options['arguments'] ?? []); 93 | } 94 | } 95 | 96 | $this->queueDeclared = true; 97 | } 98 | 99 | public function processQueueMessage($queueName, AMQPMessage $msg) 100 | { 101 | if (!isset($this->queues[$queueName])) { 102 | throw new QueueNotFoundException(); 103 | } 104 | 105 | $this->processMessageQueueCallback($msg, $queueName, $this->queues[$queueName]['callback']); 106 | } 107 | 108 | public function stopConsuming() 109 | { 110 | foreach ($this->queues as $name => $options) { 111 | $this->getChannel()->basic_cancel($this->getQueueConsumerTag($name), false, true); 112 | } 113 | } 114 | 115 | /** 116 | * Merges static and provided queues into one array 117 | */ 118 | protected function mergeQueues() 119 | { 120 | if ($this->queuesProvider) { 121 | $this->queues = array_merge( 122 | $this->queues, 123 | $this->queuesProvider->getQueues() 124 | ); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Command/BaseConsumerCommand.php: -------------------------------------------------------------------------------- 1 | consumer instanceof Consumer) { 24 | // Process current message, then halt consumer 25 | $this->consumer->forceStopConsumer(); 26 | 27 | // Halt consumer if waiting for a new message from the queue 28 | try { 29 | $this->consumer->stopConsuming(); 30 | } catch (AMQPTimeoutException $e) { 31 | } 32 | } 33 | } 34 | 35 | public function restartConsumer() 36 | { 37 | // TODO: Implement restarting of consumer 38 | } 39 | 40 | protected function configure(): void 41 | { 42 | parent::configure(); 43 | 44 | $this 45 | ->addArgument('name', InputArgument::REQUIRED, 'Consumer Name') 46 | ->addOption('messages', 'm', InputOption::VALUE_OPTIONAL, 'Messages to consume', '0') 47 | ->addOption('route', 'r', InputOption::VALUE_OPTIONAL, 'Routing Key', '') 48 | ->addOption('memory-limit', 'l', InputOption::VALUE_OPTIONAL, 'Allowed memory for this process (MB)') 49 | ->addOption('debug', 'd', InputOption::VALUE_NONE, 'Enable Debugging') 50 | ->addOption('without-signals', 'w', InputOption::VALUE_NONE, 'Disable catching of system signals') 51 | ; 52 | } 53 | 54 | protected function initialize(InputInterface $input, OutputInterface $output): void 55 | { 56 | $this->amount = (int)$input->getOption('messages'); 57 | if (0 > $this->amount) { 58 | throw new \InvalidArgumentException("The -m option should be null or greater than 0"); 59 | } 60 | } 61 | 62 | /** 63 | * Executes the current command. 64 | * 65 | * @param InputInterface $input An InputInterface instance 66 | * @param OutputInterface $output An OutputInterface instance 67 | * 68 | * @return integer 0 if everything went fine, or an error code 69 | * 70 | * @throws \InvalidArgumentException When the number of messages to consume is less than 0 71 | * @throws \BadFunctionCallException When the pcntl is not installed and option -s is true 72 | */ 73 | protected function execute(InputInterface $input, OutputInterface $output): int 74 | { 75 | if (defined('AMQP_WITHOUT_SIGNALS') === false) { 76 | define('AMQP_WITHOUT_SIGNALS', $input->getOption('without-signals')); 77 | } 78 | 79 | if (!AMQP_WITHOUT_SIGNALS && extension_loaded('pcntl')) { 80 | if (!function_exists('pcntl_signal')) { 81 | throw new \BadFunctionCallException("Function 'pcntl_signal' is referenced in the php.ini 'disable_functions' and can't be called."); 82 | } 83 | 84 | pcntl_signal(SIGTERM, [&$this, 'stopConsumer']); 85 | pcntl_signal(SIGINT, [&$this, 'stopConsumer']); 86 | pcntl_signal(SIGQUIT, [&$this, 'stopConsumer']); 87 | pcntl_signal(SIGHUP, [&$this, 'restartConsumer']); 88 | } 89 | 90 | if (defined('AMQP_DEBUG') === false) { 91 | define('AMQP_DEBUG', (bool) $input->getOption('debug')); 92 | } 93 | 94 | $this->initConsumer($input); 95 | 96 | return $this->consumer->consume($this->amount); 97 | } 98 | 99 | protected function initConsumer(InputInterface $input) 100 | { 101 | $this->consumer = $this->getContainer() 102 | ->get(sprintf($this->getConsumerService(), $input->getArgument('name'))); 103 | 104 | if ($input->hasOption('memory-limit')) { 105 | $memoryLimit = (int)$input->getOption('memory-limit'); 106 | if ($memoryLimit > 0) { 107 | $this->consumer->setMemoryLimit($memoryLimit); 108 | } 109 | } 110 | $this->consumer->setRoutingKey($input->getOption('route')); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /RabbitMq/RpcClient.php: -------------------------------------------------------------------------------- 1 | expectSerializedResponse = $expectSerializedResponse; 23 | } 24 | 25 | public function addRequest($msgBody, $server, $requestId = null, $routingKey = '', $expiration = 0) 26 | { 27 | if (empty($requestId)) { 28 | throw new \InvalidArgumentException('You must provide a $requestId'); 29 | } 30 | 31 | if (0 == $this->requests) { 32 | // On first addRequest() call, clear all replies 33 | $this->replies = []; 34 | 35 | if ($this->directReplyTo) { 36 | // On direct reply-to mode, make initial consume call 37 | $this->directConsumerTag = $this->getChannel()->basic_consume('amq.rabbitmq.reply-to', '', false, true, false, false, [$this, 'processMessage']); 38 | } 39 | } 40 | 41 | $msg = new AMQPMessage($msgBody, ['content_type' => 'text/plain', 42 | 'reply_to' => $this->directReplyTo 43 | ? 'amq.rabbitmq.reply-to' // On direct reply-to mode, use predefined queue name 44 | : $this->getQueueName(), 45 | 'delivery_mode' => 1, // non durable 46 | 'expiration' => $expiration * 1000, 47 | 'correlation_id' => $requestId, ]); 48 | 49 | $this->getChannel()->basic_publish($msg, $server, $routingKey); 50 | 51 | $this->requests++; 52 | 53 | if ($expiration > $this->timeout) { 54 | $this->timeout = $expiration; 55 | } 56 | } 57 | 58 | public function getReplies() 59 | { 60 | if ($this->directReplyTo) { 61 | $consumer_tag = $this->directConsumerTag; 62 | } else { 63 | $consumer_tag = $this->getChannel()->basic_consume($this->getQueueName(), '', false, true, false, false, [$this, 'processMessage']); 64 | } 65 | 66 | try { 67 | while (count($this->replies) < $this->requests) { 68 | $this->getChannel()->wait(null, false, $this->timeout); 69 | } 70 | } finally { 71 | $this->getChannel()->basic_cancel($consumer_tag); 72 | } 73 | 74 | $this->directConsumerTag = null; 75 | $this->requests = 0; 76 | $this->timeout = 0; 77 | 78 | return $this->replies; 79 | } 80 | 81 | public function processMessage(AMQPMessage $msg) 82 | { 83 | $messageBody = $msg->body; 84 | if ($this->expectSerializedResponse) { 85 | $messageBody = call_user_func($this->unserializer, $messageBody); 86 | } 87 | if ($this->notifyCallback !== null) { 88 | call_user_func($this->notifyCallback, $messageBody); 89 | } 90 | 91 | $this->replies[$msg->get('correlation_id')] = $messageBody; 92 | } 93 | 94 | protected function getQueueName() 95 | { 96 | if (null === $this->queueName) { 97 | [$this->queueName, , ] = $this->getChannel()->queue_declare("", false, false, true, false); 98 | } 99 | 100 | return $this->queueName; 101 | } 102 | 103 | public function setUnserializer($unserializer) 104 | { 105 | $this->unserializer = $unserializer; 106 | } 107 | 108 | public function notify($callback) 109 | { 110 | if (is_callable($callback)) { 111 | $this->notifyCallback = $callback; 112 | } else { 113 | throw new \InvalidArgumentException('First parameter expects to be callable'); 114 | } 115 | } 116 | 117 | public function setDirectReplyTo($directReplyTo) 118 | { 119 | $this->directReplyTo = $directReplyTo; 120 | } 121 | 122 | public function reset() 123 | { 124 | $this->replies = []; 125 | $this->requests = 0; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Command/BatchConsumerCommand.php: -------------------------------------------------------------------------------- 1 | consumer instanceof BatchConsumer) { 22 | // Process current message, then halt consumer 23 | $this->consumer->forceStopConsumer(); 24 | 25 | // Halt consumer if waiting for a new message from the queue 26 | try { 27 | $this->consumer->stopConsuming(); 28 | } catch (AMQPTimeoutException $e) { 29 | } 30 | } 31 | } 32 | 33 | protected function configure(): void 34 | { 35 | parent::configure(); 36 | 37 | $this 38 | ->setName('rabbitmq:batch:consumer') 39 | ->addArgument('name', InputArgument::REQUIRED, 'Consumer Name') 40 | ->addOption('batches', 'b', InputOption::VALUE_OPTIONAL, 'Number of batches to consume', '0') 41 | ->addOption('route', 'r', InputOption::VALUE_OPTIONAL, 'Routing Key', '') 42 | ->addOption('memory-limit', 'l', InputOption::VALUE_OPTIONAL, 'Allowed memory for this process') 43 | ->addOption('debug', 'd', InputOption::VALUE_NONE, 'Enable Debugging') 44 | ->addOption('without-signals', 'w', InputOption::VALUE_NONE, 'Disable catching of system signals') 45 | ->setDescription('Executes a Batch Consumer'); 46 | } 47 | 48 | /** 49 | * Executes the current command. 50 | * 51 | * @param InputInterface $input An InputInterface instance 52 | * @param OutputInterface $output An OutputInterface instance 53 | * 54 | * @return integer 0 if everything went fine, or an error code 55 | * 56 | * @throws \InvalidArgumentException When the number of batches to consume is less than 0 57 | * @throws \BadFunctionCallException When the pcntl is not installed and option -s is true 58 | */ 59 | protected function execute(InputInterface $input, OutputInterface $output): int 60 | { 61 | if (defined('AMQP_WITHOUT_SIGNALS') === false) { 62 | define('AMQP_WITHOUT_SIGNALS', $input->getOption('without-signals')); 63 | } 64 | 65 | if (!AMQP_WITHOUT_SIGNALS && extension_loaded('pcntl')) { 66 | if (!function_exists('pcntl_signal')) { 67 | throw new \BadFunctionCallException("Function 'pcntl_signal' is referenced in the php.ini 'disable_functions' and can't be called."); 68 | } 69 | 70 | pcntl_signal(SIGTERM, [&$this, 'stopConsumer']); 71 | pcntl_signal(SIGINT, [&$this, 'stopConsumer']); 72 | pcntl_signal(SIGQUIT, [&$this, 'stopConsumer']); 73 | } 74 | 75 | if (defined('AMQP_DEBUG') === false) { 76 | define('AMQP_DEBUG', (bool) $input->getOption('debug')); 77 | } 78 | 79 | $batchAmountTarget = (int)$input->getOption('batches'); 80 | if (0 > $batchAmountTarget) { 81 | throw new \InvalidArgumentException("The -b option should be greater than 0"); 82 | } 83 | 84 | $this->initConsumer($input); 85 | 86 | return $this->consumer->consume($batchAmountTarget); 87 | } 88 | 89 | /** 90 | * @param InputInterface $input 91 | */ 92 | protected function initConsumer(InputInterface $input) 93 | { 94 | $this->consumer = $this->getContainer() 95 | ->get(sprintf($this->getConsumerService(), $input->getArgument('name'))); 96 | 97 | if (null !== $input->getOption('memory-limit') && 98 | ctype_digit((string) $input->getOption('memory-limit')) && 99 | (int) $input->getOption('memory-limit') > 0 100 | ) { 101 | $this->consumer->setMemoryLimit($input->getOption('memory-limit')); 102 | } 103 | $this->consumer->setRoutingKey($input->getOption('route')); 104 | } 105 | 106 | /** 107 | * @return string 108 | */ 109 | protected function getConsumerService() 110 | { 111 | return 'old_sound_rabbit_mq.%s_batch'; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /RabbitMq/BaseConsumer.php: -------------------------------------------------------------------------------- 1 | callback = $callback; 30 | } 31 | 32 | /** 33 | * @return callable 34 | */ 35 | public function getCallback() 36 | { 37 | return $this->callback; 38 | } 39 | 40 | /** 41 | * @param int $msgAmount 42 | * @throws \ErrorException 43 | */ 44 | public function start($msgAmount = 0) 45 | { 46 | $this->target = $msgAmount; 47 | 48 | $this->setupConsumer(); 49 | 50 | while ($this->getChannel()->is_consuming()) { 51 | $this->getChannel()->wait(); 52 | } 53 | } 54 | 55 | /** 56 | * Tell the server you are going to stop consuming. 57 | * 58 | * It will finish up the last message and not send you any more. 59 | */ 60 | public function stopConsuming() 61 | { 62 | // This gets stuck and will not exit without the last two parameters set. 63 | $this->getChannel()->basic_cancel($this->getConsumerTag(), false, true); 64 | } 65 | 66 | protected function setupConsumer() 67 | { 68 | if ($this->autoSetupFabric) { 69 | $this->setupFabric(); 70 | } 71 | $this->getChannel()->basic_consume($this->queueOptions['name'], $this->getConsumerTag(), false, $this->consumerOptions['no_ack'], false, false, [$this, 'processMessage']); 72 | } 73 | 74 | public function processMessage(AMQPMessage $msg) 75 | { 76 | //To be implemented by descendant classes 77 | } 78 | 79 | protected function maybeStopConsumer() 80 | { 81 | if (extension_loaded('pcntl') && (defined('AMQP_WITHOUT_SIGNALS') ? !AMQP_WITHOUT_SIGNALS : true)) { 82 | if (!function_exists('pcntl_signal_dispatch')) { 83 | throw new \BadFunctionCallException("Function 'pcntl_signal_dispatch' is referenced in the php.ini 'disable_functions' and can't be called."); 84 | } 85 | 86 | pcntl_signal_dispatch(); 87 | } 88 | 89 | if ($this->forceStop || ($this->consumed == $this->target && $this->target > 0)) { 90 | $this->stopConsuming(); 91 | } 92 | } 93 | 94 | public function setConsumerTag($tag) 95 | { 96 | $this->consumerTag = $tag; 97 | } 98 | 99 | public function getConsumerTag() 100 | { 101 | return $this->consumerTag; 102 | } 103 | 104 | public function forceStopConsumer() 105 | { 106 | $this->forceStop = true; 107 | } 108 | 109 | /** 110 | * Sets the qos settings for the current channel 111 | * Consider that prefetchSize and global do not work with rabbitMQ version <= 8.0 112 | * 113 | * @param int $prefetchSize 114 | * @param int $prefetchCount 115 | * @param bool $global 116 | */ 117 | public function setQosOptions($prefetchSize = 0, $prefetchCount = 0, $global = false) 118 | { 119 | $this->getChannel()->basic_qos($prefetchSize, $prefetchCount, $global); 120 | } 121 | 122 | public function setIdleTimeout($idleTimeout) 123 | { 124 | $this->idleTimeout = $idleTimeout; 125 | } 126 | 127 | /** 128 | * Set exit code to be returned when there is a timeout exception 129 | * 130 | * @param int|null $idleTimeoutExitCode 131 | */ 132 | public function setIdleTimeoutExitCode($idleTimeoutExitCode) 133 | { 134 | $this->idleTimeoutExitCode = $idleTimeoutExitCode; 135 | } 136 | 137 | public function getIdleTimeout() 138 | { 139 | return $this->idleTimeout; 140 | } 141 | 142 | /** 143 | * Get exit code to be returned when there is a timeout exception 144 | * 145 | * @return int|null 146 | */ 147 | public function getIdleTimeoutExitCode() 148 | { 149 | return $this->idleTimeoutExitCode; 150 | } 151 | 152 | /** 153 | * Resets the consumed property. 154 | * Use when you want to call start() or consume() multiple times. 155 | */ 156 | public function resetConsumed() 157 | { 158 | $this->consumed = 0; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Resources/config/rabbitmq.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | PhpAmqpLib\Connection\AMQPStreamConnection 8 | PhpAmqpLib\Connection\AMQPSocketConnection 9 | PhpAmqpLib\Connection\AMQPLazyConnection 10 | PhpAmqpLib\Connection\AMQPLazySocketConnection 11 | OldSound\RabbitMqBundle\RabbitMq\AMQPConnectionFactory 12 | OldSound\RabbitMqBundle\RabbitMq\Binding 13 | OldSound\RabbitMqBundle\RabbitMq\Producer 14 | OldSound\RabbitMqBundle\RabbitMq\Consumer 15 | OldSound\RabbitMqBundle\RabbitMq\MultipleConsumer 16 | OldSound\RabbitMqBundle\RabbitMq\DynamicConsumer 17 | OldSound\RabbitMqBundle\RabbitMq\BatchConsumer 18 | OldSound\RabbitMqBundle\RabbitMq\AnonConsumer 19 | OldSound\RabbitMqBundle\RabbitMq\RpcClient 20 | OldSound\RabbitMqBundle\RabbitMq\RpcServer 21 | OldSound\RabbitMqBundle\RabbitMq\AMQPLoggedChannel 22 | OldSound\RabbitMqBundle\DataCollector\MessageDataCollector 23 | OldSound\RabbitMqBundle\RabbitMq\AmqpPartsHolder 24 | OldSound\RabbitMqBundle\RabbitMq\Fallback 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /RabbitMq/AMQPConnectionFactory.php: -------------------------------------------------------------------------------- 1 | '', 18 | 'host' => 'localhost', 19 | 'port' => 5672, 20 | 'user' => 'guest', 21 | 'password' => 'guest', 22 | 'vhost' => '/', 23 | 'connection_timeout' => 3, 24 | 'read_write_timeout' => 3, 25 | 'ssl_context' => null, 26 | 'keepalive' => false, 27 | 'heartbeat' => 0, 28 | 'hosts' => [], 29 | 'channel_rpc_timeout' => 0.0, 30 | ]; 31 | 32 | /** 33 | * Constructor 34 | * 35 | * @param string $class FQCN of AMQPConnection class to instantiate. 36 | * @param array $parameters Map containing parameters resolved by 37 | * Extension. 38 | * @param ConnectionParametersProviderInterface $parametersProvider Optional service providing/overriding 39 | * connection parameters. 40 | */ 41 | public function __construct( 42 | $class, 43 | array $parameters, 44 | ?ConnectionParametersProviderInterface $parametersProvider = null 45 | ) { 46 | $this->class = $class; 47 | $this->parameters = array_merge($this->parameters, $parameters); 48 | $this->parameters = $this->parseUrl($this->parameters); 49 | 50 | foreach ($this->parameters['hosts'] as $key => $hostParameters) { 51 | if (!isset($hostParameters['url'])) { 52 | continue; 53 | } 54 | 55 | $this->parameters['hosts'][$key] = $this->parseUrl($hostParameters); 56 | } 57 | 58 | if ($parametersProvider) { 59 | $this->parameters = array_merge($this->parameters, $parametersProvider->getConnectionParameters()); 60 | } 61 | 62 | if (is_array($this->parameters['ssl_context'])) { 63 | $this->parameters['context'] = !empty($this->parameters['ssl_context']) 64 | ? stream_context_create(['ssl' => $this->parameters['ssl_context']]) 65 | : null; 66 | } 67 | 68 | } 69 | 70 | /** 71 | * Creates the appropriate connection using current parameters. 72 | * 73 | * @return AbstractConnection 74 | * @throws \Exception 75 | */ 76 | public function createConnection() 77 | { 78 | if (isset($this->parameters['constructor_args']) && is_array($this->parameters['constructor_args'])) { 79 | $constructorArgs = array_values($this->parameters['constructor_args']); 80 | return new $this->class(...$constructorArgs); 81 | } 82 | 83 | $hosts = $this->parameters['hosts'] ?: [$this->parameters]; 84 | $options = $this->parameters; 85 | unset($options['hosts']); 86 | 87 | if ($this->class == AMQPSocketConnection::class || is_subclass_of($this->class, AMQPSocketConnection::class)) { 88 | $options['read_timeout'] ??= $this->parameters['read_write_timeout']; 89 | $options['write_timeout'] ??= $this->parameters['read_write_timeout']; 90 | } 91 | 92 | // No need to unpack options, they will be handled inside connection classes 93 | return $this->class::create_connection($hosts, $options); 94 | } 95 | 96 | /** 97 | * Parses connection parameters from URL parameter. 98 | * 99 | * @param array $parameters 100 | * 101 | * @return array 102 | */ 103 | private function parseUrl(array $parameters) 104 | { 105 | if (!$parameters['url']) { 106 | return $parameters; 107 | } 108 | 109 | $url = parse_url($parameters['url']); 110 | 111 | if ($url === false || !isset($url['scheme']) || !in_array($url['scheme'], ['amqp', 'amqps'], true)) { 112 | throw new InvalidConfigurationException('Malformed parameter "url".'); 113 | } 114 | 115 | // See https://www.rabbitmq.com/uri-spec.html 116 | if (isset($url['host'])) { 117 | $parameters['host'] = urldecode($url['host']); 118 | } 119 | if (isset($url['port'])) { 120 | $parameters['port'] = (int)$url['port']; 121 | } 122 | if (isset($url['user'])) { 123 | $parameters['user'] = urldecode($url['user']); 124 | } 125 | if (isset($url['pass'])) { 126 | $parameters['password'] = urldecode($url['pass']); 127 | } 128 | if (isset($url['path'])) { 129 | $parameters['vhost'] = urldecode(ltrim($url['path'], '/')); 130 | } 131 | 132 | if (isset($url['query'])) { 133 | $query = []; 134 | parse_str($url['query'], $query); 135 | $parameters = array_merge($parameters, $query); 136 | } 137 | 138 | unset($parameters['url']); 139 | 140 | return $parameters; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /RabbitMq/BaseAmqp.php: -------------------------------------------------------------------------------- 1 | 'text/plain', 'delivery_mode' => 2]; 23 | 24 | /** 25 | * @var LoggerInterface 26 | */ 27 | protected $logger; 28 | 29 | protected $exchangeOptions = [ 30 | 'passive' => false, 31 | 'durable' => true, 32 | 'auto_delete' => false, 33 | 'internal' => false, 34 | 'nowait' => false, 35 | 'arguments' => null, 36 | 'ticket' => null, 37 | 'declare' => true, 38 | ]; 39 | 40 | protected $queueOptions = [ 41 | 'name' => '', 42 | 'passive' => false, 43 | 'durable' => true, 44 | 'exclusive' => false, 45 | 'auto_delete' => false, 46 | 'nowait' => false, 47 | 'arguments' => null, 48 | 'ticket' => null, 49 | 'declare' => true, 50 | ]; 51 | 52 | protected $consumerOptions = [ 53 | 'no_ack' => false, 54 | ]; 55 | 56 | /** 57 | * @var EventDispatcherInterface|null 58 | */ 59 | protected $eventDispatcher = null; 60 | 61 | /** 62 | * @param AbstractConnection $conn 63 | * @param AMQPChannel|null $ch 64 | * @param string|null $consumerTag 65 | */ 66 | public function __construct(AbstractConnection $conn, ?AMQPChannel $ch = null, $consumerTag = null) 67 | { 68 | $this->conn = $conn; 69 | $this->ch = $ch; 70 | 71 | if ($conn->connectOnConstruct()) { 72 | $this->getChannel(); 73 | } 74 | 75 | $this->consumerTag = $consumerTag ?? sprintf("PHPPROCESS_%s_%s", gethostname(), getmypid()); 76 | 77 | $this->logger = new NullLogger(); 78 | } 79 | 80 | public function __destruct() 81 | { 82 | $this->close(); 83 | } 84 | 85 | public function close() 86 | { 87 | if ($this->ch) { 88 | try { 89 | $this->ch->close(); 90 | } catch (\Exception $e) { 91 | // ignore on shutdown 92 | } 93 | } 94 | 95 | if ($this->conn && $this->conn->isConnected()) { 96 | try { 97 | $this->conn->close(); 98 | } catch (\Exception $e) { 99 | // ignore on shutdown 100 | } 101 | } 102 | } 103 | 104 | public function reconnect() 105 | { 106 | if (!$this->conn->isConnected()) { 107 | return; 108 | } 109 | 110 | $this->conn->reconnect(); 111 | } 112 | 113 | /** 114 | * @return AMQPChannel 115 | */ 116 | public function getChannel() 117 | { 118 | if (empty($this->ch) || null === $this->ch->getChannelId()) { 119 | $this->ch = $this->conn->channel(); 120 | } 121 | 122 | return $this->ch; 123 | } 124 | 125 | /** 126 | * @param AMQPChannel $ch 127 | * 128 | * @return void 129 | */ 130 | public function setChannel(AMQPChannel $ch) 131 | { 132 | $this->ch = $ch; 133 | } 134 | 135 | /** 136 | * @throws \InvalidArgumentException 137 | * @param array $options 138 | * @return void 139 | */ 140 | public function setExchangeOptions(array $options = []) 141 | { 142 | if (!isset($options['name'])) { 143 | throw new \InvalidArgumentException('You must provide an exchange name'); 144 | } 145 | 146 | if (empty($options['type'])) { 147 | throw new \InvalidArgumentException('You must provide an exchange type'); 148 | } 149 | 150 | $this->exchangeOptions = array_merge($this->exchangeOptions, $options); 151 | } 152 | 153 | /** 154 | * @param array $options 155 | * @return void 156 | */ 157 | public function setQueueOptions(array $options = []) 158 | { 159 | $this->queueOptions = array_merge($this->queueOptions, $options); 160 | } 161 | 162 | /** 163 | * @param array $options 164 | * @return void 165 | */ 166 | public function setConsumerOptions(array $options = []) 167 | { 168 | $this->consumerOptions = array_merge($this->consumerOptions, $options); 169 | } 170 | 171 | /** 172 | * @param string $routingKey 173 | * @return void 174 | */ 175 | public function setRoutingKey($routingKey) 176 | { 177 | $this->routingKey = $routingKey; 178 | } 179 | 180 | public function setupFabric() 181 | { 182 | if (!$this->exchangeDeclared) { 183 | $this->exchangeDeclare(); 184 | } 185 | 186 | if (!$this->queueDeclared) { 187 | $this->queueDeclare(); 188 | } 189 | } 190 | 191 | /** 192 | * disables the automatic SetupFabric when using a consumer or producer 193 | */ 194 | public function disableAutoSetupFabric() 195 | { 196 | $this->autoSetupFabric = false; 197 | } 198 | 199 | /** 200 | * @param LoggerInterface $logger 201 | */ 202 | public function setLogger($logger) 203 | { 204 | $this->logger = $logger; 205 | } 206 | 207 | /** 208 | * Declares exchange 209 | */ 210 | protected function exchangeDeclare() 211 | { 212 | if ($this->exchangeOptions['declare']) { 213 | $this->getChannel()->exchange_declare( 214 | $this->exchangeOptions['name'], 215 | $this->exchangeOptions['type'], 216 | $this->exchangeOptions['passive'], 217 | $this->exchangeOptions['durable'], 218 | $this->exchangeOptions['auto_delete'], 219 | $this->exchangeOptions['internal'], 220 | $this->exchangeOptions['nowait'], 221 | $this->exchangeOptions['arguments'], 222 | $this->exchangeOptions['ticket'] 223 | ); 224 | 225 | $this->exchangeDeclared = true; 226 | } 227 | } 228 | 229 | /** 230 | * Declares queue, creates if needed 231 | */ 232 | protected function queueDeclare() 233 | { 234 | if ($this->queueOptions['declare']) { 235 | [$queueName, , ] = $this->getChannel()->queue_declare( 236 | $this->queueOptions['name'], 237 | $this->queueOptions['passive'], 238 | $this->queueOptions['durable'], 239 | $this->queueOptions['exclusive'], 240 | $this->queueOptions['auto_delete'], 241 | $this->queueOptions['nowait'], 242 | $this->queueOptions['arguments'], 243 | $this->queueOptions['ticket'] 244 | ); 245 | 246 | if (isset($this->queueOptions['routing_keys']) && count($this->queueOptions['routing_keys']) > 0) { 247 | foreach ($this->queueOptions['routing_keys'] as $routingKey) { 248 | $this->queueBind($queueName, $this->exchangeOptions['name'], $routingKey, $this->queueOptions['arguments'] ?? []); 249 | } 250 | } else { 251 | $this->queueBind($queueName, $this->exchangeOptions['name'], $this->routingKey, $this->queueOptions['arguments'] ?? []); 252 | } 253 | 254 | $this->queueDeclared = true; 255 | } 256 | } 257 | 258 | /** 259 | * Binds queue to an exchange 260 | * 261 | * @param string $queue 262 | * @param string $exchange 263 | * @param string $routing_key 264 | */ 265 | protected function queueBind($queue, $exchange, $routing_key, array $arguments = []) 266 | { 267 | // queue binding is not permitted on the default exchange 268 | if ('' !== $exchange) { 269 | $this->getChannel()->queue_bind($queue, $exchange, $routing_key, false, $arguments); 270 | } 271 | } 272 | 273 | /** 274 | * @param EventDispatcherInterface $eventDispatcher 275 | * 276 | * @return BaseAmqp 277 | */ 278 | public function setEventDispatcher(EventDispatcherInterface $eventDispatcher) 279 | { 280 | $this->eventDispatcher = $eventDispatcher; 281 | 282 | return $this; 283 | } 284 | 285 | /** 286 | * @param string $eventName 287 | * @param AMQPEvent $event 288 | */ 289 | protected function dispatchEvent($eventName, AMQPEvent $event) 290 | { 291 | if ($this->getEventDispatcher() instanceof ContractsEventDispatcherInterface) { 292 | $this->getEventDispatcher()->dispatch( 293 | $event, 294 | $eventName 295 | ); 296 | } 297 | } 298 | 299 | /** 300 | * @return EventDispatcherInterface|null 301 | */ 302 | public function getEventDispatcher() 303 | { 304 | return $this->eventDispatcher; 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /RabbitMq/Consumer.php: -------------------------------------------------------------------------------- 1 | memoryLimit = $memoryLimit; 50 | } 51 | 52 | /** 53 | * Get the memory limit 54 | * 55 | * @return int|null 56 | */ 57 | public function getMemoryLimit() 58 | { 59 | return $this->memoryLimit; 60 | } 61 | 62 | /** 63 | * Consume the message 64 | * 65 | * @param int $msgAmount 66 | * 67 | * @return int 68 | * 69 | * @throws AMQPTimeoutException 70 | */ 71 | public function consume($msgAmount) 72 | { 73 | $this->target = $msgAmount; 74 | 75 | $this->setupConsumer(); 76 | 77 | $this->setLastActivityDateTime(new \DateTime()); 78 | while ($this->getChannel()->is_consuming()) { 79 | $this->dispatchEvent(OnConsumeEvent::NAME, new OnConsumeEvent($this)); 80 | $this->maybeStopConsumer(); 81 | 82 | /* 83 | * Be careful not to trigger ::wait() with 0 or less seconds, when 84 | * graceful max execution timeout is being used. 85 | */ 86 | 87 | $waitTimeout = $this->chooseWaitTimeout(); 88 | if ($this->gracefulMaxExecutionDateTime 89 | && $waitTimeout < 1 90 | ) { 91 | return $this->gracefulMaxExecutionTimeoutExitCode; 92 | } 93 | 94 | if (!$this->forceStop) { 95 | try { 96 | $this->getChannel()->wait(null, false, $waitTimeout); 97 | $this->setLastActivityDateTime(new \DateTime()); 98 | } catch (AMQPTimeoutException $e) { 99 | $now = time(); 100 | 101 | if ($this->gracefulMaxExecutionDateTime 102 | && $this->gracefulMaxExecutionDateTime <= new \DateTime("@$now") 103 | ) { 104 | return $this->gracefulMaxExecutionTimeoutExitCode; 105 | } elseif ($this->getIdleTimeout() 106 | && ($this->getLastActivityDateTime()->getTimestamp() + $this->getIdleTimeout() <= $now) 107 | ) { 108 | $idleEvent = new OnIdleEvent($this); 109 | $this->dispatchEvent(OnIdleEvent::NAME, $idleEvent); 110 | 111 | if ($idleEvent->isForceStop()) { 112 | if (null !== $this->getIdleTimeoutExitCode()) { 113 | return $this->getIdleTimeoutExitCode(); 114 | } else { 115 | throw $e; 116 | } 117 | } 118 | } 119 | } 120 | } 121 | } 122 | 123 | return 0; 124 | } 125 | 126 | /** 127 | * Purge the queue 128 | */ 129 | public function purge() 130 | { 131 | $this->getChannel()->queue_purge($this->queueOptions['name'], true); 132 | } 133 | 134 | /** 135 | * Delete the queue 136 | */ 137 | public function delete() 138 | { 139 | $this->getChannel()->queue_delete($this->queueOptions['name'], true); 140 | } 141 | 142 | protected function processMessageQueueCallback(AMQPMessage $msg, $queueName, $callback) 143 | { 144 | $this->dispatchEvent( 145 | BeforeProcessingMessageEvent::NAME, 146 | new BeforeProcessingMessageEvent($this, $msg) 147 | ); 148 | try { 149 | $processFlag = call_user_func($callback, $msg); 150 | $this->handleProcessMessage($msg, $processFlag); 151 | $this->dispatchEvent( 152 | AfterProcessingMessageEvent::NAME, 153 | new AfterProcessingMessageEvent($this, $msg) 154 | ); 155 | $this->logger->debug('Queue message processed', [ 156 | 'amqp' => [ 157 | 'queue' => $queueName, 158 | 'message' => $msg, 159 | 'return_code' => $processFlag, 160 | ], 161 | ]); 162 | } catch (Exception\StopConsumerException $e) { 163 | $this->logger->info('Consumer requested stop', [ 164 | 'amqp' => [ 165 | 'queue' => $queueName, 166 | 'message' => $msg, 167 | 'stacktrace' => $e->getTraceAsString(), 168 | ], 169 | ]); 170 | $this->handleProcessMessage($msg, $e->getHandleCode()); 171 | $this->stopConsuming(); 172 | } catch (\Exception $e) { 173 | $this->logger->error($e->getMessage(), [ 174 | 'amqp' => [ 175 | 'queue' => $queueName, 176 | 'message' => $msg, 177 | 'stacktrace' => $e->getTraceAsString(), 178 | ], 179 | ]); 180 | throw $e; 181 | } catch (\Error $e) { 182 | $this->logger->error($e->getMessage(), [ 183 | 'amqp' => [ 184 | 'queue' => $queueName, 185 | 'message' => $msg, 186 | 'stacktrace' => $e->getTraceAsString(), 187 | ], 188 | ]); 189 | throw $e; 190 | } 191 | } 192 | 193 | public function processMessage(AMQPMessage $msg) 194 | { 195 | $this->processMessageQueueCallback($msg, $this->queueOptions['name'], $this->callback); 196 | } 197 | 198 | protected function handleProcessMessage(AMQPMessage $msg, $processFlag) 199 | { 200 | if ($processFlag === ConsumerInterface::MSG_REJECT_REQUEUE || false === $processFlag) { 201 | // Reject and requeue message to RabbitMQ 202 | $msg->reject(); 203 | } elseif ($processFlag === ConsumerInterface::MSG_SINGLE_NACK_REQUEUE) { 204 | // NACK and requeue message to RabbitMQ 205 | $msg->nack(true); 206 | } elseif ($processFlag === ConsumerInterface::MSG_REJECT) { 207 | // Reject and drop 208 | $msg->reject(false); 209 | } elseif ($processFlag !== ConsumerInterface::MSG_ACK_SENT) { 210 | // Remove message from queue only if callback return not false 211 | $msg->ack(); 212 | } 213 | 214 | $this->consumed++; 215 | $this->maybeStopConsumer(); 216 | 217 | if (!is_null($this->getMemoryLimit()) && $this->isRamAlmostOverloaded()) { 218 | $this->stopConsuming(); 219 | } 220 | } 221 | 222 | /** 223 | * Checks if memory in use is greater or equal than memory allowed for this process 224 | * 225 | * @return boolean 226 | */ 227 | protected function isRamAlmostOverloaded() 228 | { 229 | $memoryManager = new MemoryConsumptionChecker(new NativeMemoryUsageProvider()); 230 | 231 | return $memoryManager->isRamAlmostOverloaded($this->getMemoryLimit().'M', '5M'); 232 | } 233 | 234 | /** 235 | * @param \DateTime|null $dateTime 236 | */ 237 | public function setGracefulMaxExecutionDateTime(?\DateTime $dateTime = null) 238 | { 239 | $this->gracefulMaxExecutionDateTime = $dateTime; 240 | } 241 | 242 | /** 243 | * @param int $secondsInTheFuture 244 | */ 245 | public function setGracefulMaxExecutionDateTimeFromSecondsInTheFuture($secondsInTheFuture) 246 | { 247 | $this->setGracefulMaxExecutionDateTime(new \DateTime("+{$secondsInTheFuture} seconds")); 248 | } 249 | 250 | /** 251 | * @param int $exitCode 252 | */ 253 | public function setGracefulMaxExecutionTimeoutExitCode($exitCode) 254 | { 255 | $this->gracefulMaxExecutionTimeoutExitCode = $exitCode; 256 | } 257 | 258 | public function setTimeoutWait(int $timeoutWait): void 259 | { 260 | $this->timeoutWait = $timeoutWait; 261 | } 262 | 263 | /** 264 | * @return \DateTime|null 265 | */ 266 | public function getGracefulMaxExecutionDateTime() 267 | { 268 | return $this->gracefulMaxExecutionDateTime; 269 | } 270 | 271 | /** 272 | * @return int 273 | */ 274 | public function getGracefulMaxExecutionTimeoutExitCode() 275 | { 276 | return $this->gracefulMaxExecutionTimeoutExitCode; 277 | } 278 | 279 | public function getTimeoutWait(): ?int 280 | { 281 | return $this->timeoutWait; 282 | } 283 | 284 | /** 285 | * Choose the timeout wait (in seconds) to use for the $this->getChannel()->wait() method. 286 | */ 287 | private function chooseWaitTimeout(): int 288 | { 289 | if ($this->gracefulMaxExecutionDateTime) { 290 | $allowedExecutionDateInterval = $this->gracefulMaxExecutionDateTime->diff(new \DateTime()); 291 | $allowedExecutionSeconds = $allowedExecutionDateInterval->days * 86400 292 | + $allowedExecutionDateInterval->h * 3600 293 | + $allowedExecutionDateInterval->i * 60 294 | + $allowedExecutionDateInterval->s; 295 | 296 | if (!$allowedExecutionDateInterval->invert) { 297 | $allowedExecutionSeconds *= -1; 298 | } 299 | 300 | /* 301 | * Respect the idle timeout if it's set and if it's less than 302 | * the remaining allowed execution. 303 | */ 304 | if ($this->getIdleTimeout() 305 | && $this->getIdleTimeout() < $allowedExecutionSeconds 306 | ) { 307 | $waitTimeout = $this->getIdleTimeout(); 308 | } else { 309 | $waitTimeout = $allowedExecutionSeconds; 310 | } 311 | } else { 312 | $waitTimeout = $this->getIdleTimeout(); 313 | } 314 | 315 | if (!is_null($this->getTimeoutWait()) && $this->getTimeoutWait() > 0) { 316 | $waitTimeout = min($waitTimeout, $this->getTimeoutWait()); 317 | } 318 | return $waitTimeout; 319 | } 320 | 321 | public function setLastActivityDateTime(\DateTime $dateTime) 322 | { 323 | $this->lastActivityDateTime = $dateTime; 324 | } 325 | 326 | protected function getLastActivityDateTime(): ?\DateTime 327 | { 328 | return $this->lastActivityDateTime; 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /RabbitMq/BatchConsumer.php: -------------------------------------------------------------------------------- 1 | gracefulMaxExecutionDateTime = $dateTime; 87 | } 88 | 89 | /** 90 | * @param int $secondsInTheFuture 91 | */ 92 | public function setGracefulMaxExecutionDateTimeFromSecondsInTheFuture($secondsInTheFuture) 93 | { 94 | $this->setGracefulMaxExecutionDateTime(new \DateTime("+{$secondsInTheFuture} seconds")); 95 | } 96 | 97 | /** 98 | * @param \Closure|callable $callback 99 | * 100 | * @return $this 101 | */ 102 | public function setCallback($callback) 103 | { 104 | $this->callback = $callback; 105 | 106 | return $this; 107 | } 108 | 109 | public function consume(int $batchAmountTarget = 0) 110 | { 111 | $this->batchAmountTarget = $batchAmountTarget; 112 | 113 | $this->setupConsumer(); 114 | 115 | while ($this->getChannel()->is_consuming()) { 116 | if ($this->isCompleteBatch()) { 117 | $this->batchConsume(); 118 | } 119 | 120 | $this->checkGracefulMaxExecutionDateTime(); 121 | $this->maybeStopConsumer(); 122 | 123 | $timeout = $this->isEmptyBatch() ? $this->getIdleTimeout() : $this->getTimeoutWait(); 124 | 125 | try { 126 | $this->getChannel()->wait(null, false, $timeout); 127 | } catch (AMQPTimeoutException $e) { 128 | if (!$this->isEmptyBatch()) { 129 | $this->batchConsume(); 130 | $this->maybeStopConsumer(); 131 | } elseif ($this->keepAlive === true) { 132 | continue; 133 | } elseif (null !== $this->getIdleTimeoutExitCode()) { 134 | return $this->getIdleTimeoutExitCode(); 135 | } else { 136 | throw $e; 137 | } 138 | } 139 | } 140 | 141 | return 0; 142 | } 143 | 144 | private function batchConsume() 145 | { 146 | try { 147 | $processFlags = call_user_func($this->callback, $this->messages); 148 | $this->handleProcessMessages($processFlags); 149 | $this->logger->debug('Queue message processed', [ 150 | 'amqp' => [ 151 | 'queue' => $this->queueOptions['name'], 152 | 'messages' => $this->messages, 153 | 'return_codes' => $processFlags, 154 | ], 155 | ]); 156 | } catch (Exception\StopConsumerException $e) { 157 | $this->logger->info('Consumer requested stop', [ 158 | 'amqp' => [ 159 | 'queue' => $this->queueOptions['name'], 160 | 'message' => $this->messages, 161 | 'stacktrace' => $e->getTraceAsString(), 162 | ], 163 | ]); 164 | $this->handleProcessMessages($e->getHandleCode()); 165 | $this->resetBatch(); 166 | $this->stopConsuming(); 167 | } catch (\Exception $e) { 168 | $this->logger->error($e->getMessage(), [ 169 | 'amqp' => [ 170 | 'queue' => $this->queueOptions['name'], 171 | 'message' => $this->messages, 172 | 'stacktrace' => $e->getTraceAsString(), 173 | ], 174 | ]); 175 | $this->resetBatch(); 176 | throw $e; 177 | } catch (\Error $e) { 178 | $this->logger->error($e->getMessage(), [ 179 | 'amqp' => [ 180 | 'queue' => $this->queueOptions['name'], 181 | 'message' => $this->messages, 182 | 'stacktrace' => $e->getTraceAsString(), 183 | ], 184 | ]); 185 | $this->resetBatch(); 186 | throw $e; 187 | } 188 | 189 | $this->batchAmount++; 190 | $this->resetBatch(); 191 | } 192 | 193 | /** 194 | * @param mixed $processFlags 195 | * 196 | * @return void 197 | */ 198 | protected function handleProcessMessages($processFlags = null) 199 | { 200 | $processFlags = $this->analyzeProcessFlags($processFlags); 201 | foreach ($processFlags as $deliveryTag => $processFlag) { 202 | $this->handleProcessFlag($deliveryTag, $processFlag); 203 | } 204 | } 205 | 206 | /** 207 | * @param string|int $deliveryTag 208 | * @param mixed $processFlag 209 | * 210 | * @return void 211 | */ 212 | private function handleProcessFlag($deliveryTag, $processFlag) 213 | { 214 | if ($processFlag === ConsumerInterface::MSG_REJECT_REQUEUE || false === $processFlag) { 215 | // Reject and requeue message to RabbitMQ 216 | $this->getMessageChannel($deliveryTag)->basic_reject($deliveryTag, true); 217 | } elseif ($processFlag === ConsumerInterface::MSG_SINGLE_NACK_REQUEUE) { 218 | // NACK and requeue message to RabbitMQ 219 | $this->getMessageChannel($deliveryTag)->basic_nack($deliveryTag, false, true); 220 | } elseif ($processFlag === ConsumerInterface::MSG_REJECT) { 221 | // Reject and drop 222 | $this->getMessageChannel($deliveryTag)->basic_reject($deliveryTag, false); 223 | } elseif ($processFlag === ConsumerInterface::MSG_ACK_SENT) { 224 | // do nothing, ACK should be already sent 225 | } else { 226 | // Remove message from queue only if callback return not false 227 | $this->getMessageChannel($deliveryTag)->basic_ack($deliveryTag); 228 | } 229 | } 230 | 231 | /** 232 | * @return bool 233 | */ 234 | protected function isCompleteBatch() 235 | { 236 | return $this->batchCounter === $this->prefetchCount; 237 | } 238 | 239 | /** 240 | * @return bool 241 | */ 242 | protected function isEmptyBatch() 243 | { 244 | return $this->batchCounter === 0; 245 | } 246 | 247 | /** 248 | * @param AMQPMessage $msg 249 | * 250 | * @return void 251 | * 252 | * @throws \Error 253 | * @throws \Exception 254 | */ 255 | public function processMessage(AMQPMessage $msg) 256 | { 257 | $this->addMessage($msg); 258 | 259 | $this->maybeStopConsumer(); 260 | } 261 | 262 | /** 263 | * @param mixed $processFlags 264 | * 265 | * @return array 266 | */ 267 | private function analyzeProcessFlags($processFlags = null) 268 | { 269 | if (is_array($processFlags)) { 270 | if (count($processFlags) !== $this->batchCounter) { 271 | throw new AMQPRuntimeException( 272 | 'Method batchExecute() should return an array with elements equal with the number of messages processed' 273 | ); 274 | } 275 | 276 | return $processFlags; 277 | } 278 | 279 | $response = []; 280 | foreach ($this->messages as $deliveryTag => $message) { 281 | $response[$deliveryTag] = $processFlags; 282 | } 283 | 284 | return $response; 285 | } 286 | 287 | 288 | /** 289 | * @return void 290 | */ 291 | private function resetBatch() 292 | { 293 | $this->messages = []; 294 | $this->batchCounter = 0; 295 | } 296 | 297 | /** 298 | * @param AMQPMessage $message 299 | * 300 | * @return void 301 | */ 302 | private function addMessage(AMQPMessage $message) 303 | { 304 | $this->batchCounter++; 305 | $this->messages[$message->getDeliveryTag()] = $message; 306 | } 307 | 308 | /** 309 | * @param int $deliveryTag 310 | * 311 | * @return AMQPMessage|null 312 | */ 313 | private function getMessage($deliveryTag) 314 | { 315 | return $this->messages[$deliveryTag] 316 | ?? null 317 | ; 318 | } 319 | 320 | /** 321 | * @param int $deliveryTag 322 | * 323 | * @return AMQPChannel 324 | * 325 | * @throws AMQPRuntimeException 326 | */ 327 | private function getMessageChannel($deliveryTag) 328 | { 329 | $message = $this->getMessage($deliveryTag); 330 | if ($message === null) { 331 | throw new AMQPRuntimeException(sprintf('Unknown delivery_tag %d!', $deliveryTag)); 332 | } 333 | 334 | return $message->getChannel(); 335 | } 336 | 337 | /** 338 | * @return void 339 | */ 340 | public function stopConsuming() 341 | { 342 | if (!$this->isEmptyBatch()) { 343 | $this->batchConsume(); 344 | } 345 | 346 | $this->getChannel()->basic_cancel($this->getConsumerTag(), false, true); 347 | } 348 | 349 | /** 350 | * @return void 351 | */ 352 | protected function setupConsumer() 353 | { 354 | if ($this->autoSetupFabric) { 355 | $this->setupFabric(); 356 | } 357 | 358 | $this->getChannel()->basic_consume($this->queueOptions['name'], $this->getConsumerTag(), false, $this->consumerOptions['no_ack'], false, false, [$this, 'processMessage']); 359 | } 360 | 361 | /** 362 | * @return void 363 | * 364 | * @throws \BadFunctionCallException 365 | */ 366 | protected function maybeStopConsumer() 367 | { 368 | if (extension_loaded('pcntl') && (defined('AMQP_WITHOUT_SIGNALS') ? !AMQP_WITHOUT_SIGNALS : true)) { 369 | if (!function_exists('pcntl_signal_dispatch')) { 370 | throw new \BadFunctionCallException("Function 'pcntl_signal_dispatch' is referenced in the php.ini 'disable_functions' and can't be called."); 371 | } 372 | 373 | pcntl_signal_dispatch(); 374 | } 375 | 376 | if ($this->forceStop || ($this->batchAmount == $this->batchAmountTarget && $this->batchAmountTarget > 0)) { 377 | $this->stopConsuming(); 378 | } 379 | 380 | if (null !== $this->getMemoryLimit() && $this->isRamAlmostOverloaded()) { 381 | $this->stopConsuming(); 382 | } 383 | } 384 | 385 | /** 386 | * @param string $tag 387 | * 388 | * @return $this 389 | */ 390 | public function setConsumerTag($tag) 391 | { 392 | $this->consumerTag = $tag; 393 | 394 | return $this; 395 | } 396 | 397 | /** 398 | * @return string 399 | */ 400 | public function getConsumerTag() 401 | { 402 | return $this->consumerTag; 403 | } 404 | 405 | /** 406 | * @return void 407 | */ 408 | public function forceStopConsumer() 409 | { 410 | $this->forceStop = true; 411 | } 412 | 413 | /** 414 | * Sets the qos settings for the current channel 415 | * Consider that prefetchSize and global do not work with rabbitMQ version <= 8.0 416 | * 417 | * @param int $prefetchSize 418 | * @param int $prefetchCount 419 | * @param bool $global 420 | */ 421 | public function setQosOptions($prefetchSize = 0, $prefetchCount = 0, $global = false) 422 | { 423 | $this->prefetchCount = $prefetchCount; 424 | $this->getChannel()->basic_qos($prefetchSize, $prefetchCount, $global); 425 | } 426 | 427 | /** 428 | * @param int $idleTimeout 429 | * 430 | * @return $this 431 | */ 432 | public function setIdleTimeout($idleTimeout) 433 | { 434 | $this->idleTimeout = $idleTimeout; 435 | 436 | return $this; 437 | } 438 | 439 | /** 440 | * Set exit code to be returned when there is a timeout exception 441 | * 442 | * @param int $idleTimeoutExitCode 443 | * 444 | * @return $this 445 | */ 446 | public function setIdleTimeoutExitCode($idleTimeoutExitCode) 447 | { 448 | $this->idleTimeoutExitCode = $idleTimeoutExitCode; 449 | 450 | return $this; 451 | } 452 | 453 | /** 454 | * keepAlive 455 | * 456 | * @return $this 457 | */ 458 | public function keepAlive() 459 | { 460 | $this->keepAlive = true; 461 | 462 | return $this; 463 | } 464 | 465 | /** 466 | * Purge the queue 467 | */ 468 | public function purge() 469 | { 470 | $this->getChannel()->queue_purge($this->queueOptions['name'], true); 471 | } 472 | 473 | /** 474 | * Delete the queue 475 | */ 476 | public function delete() 477 | { 478 | $this->getChannel()->queue_delete($this->queueOptions['name'], true); 479 | } 480 | 481 | /** 482 | * Checks if memory in use is greater or equal than memory allowed for this process 483 | * 484 | * @return boolean 485 | */ 486 | protected function isRamAlmostOverloaded() 487 | { 488 | return (memory_get_usage(true) >= ($this->getMemoryLimit() * 1048576)); 489 | } 490 | 491 | /** 492 | * @return int 493 | */ 494 | public function getIdleTimeout() 495 | { 496 | return $this->idleTimeout; 497 | } 498 | 499 | /** 500 | * Get exit code to be returned when there is a timeout exception 501 | * 502 | * @return int|null 503 | */ 504 | public function getIdleTimeoutExitCode() 505 | { 506 | return $this->idleTimeoutExitCode; 507 | } 508 | 509 | /** 510 | * Resets the consumed property. 511 | * Use when you want to call start() or consume() multiple times. 512 | */ 513 | public function resetConsumed() 514 | { 515 | $this->consumed = 0; 516 | } 517 | 518 | /** 519 | * @param int $timeout 520 | * 521 | * @return $this 522 | */ 523 | public function setTimeoutWait($timeout) 524 | { 525 | $this->timeoutWait = $timeout; 526 | 527 | return $this; 528 | } 529 | 530 | /** 531 | * @param int $amount 532 | * 533 | * @return $this 534 | */ 535 | public function setPrefetchCount($amount) 536 | { 537 | $this->prefetchCount = $amount; 538 | 539 | return $this; 540 | } 541 | 542 | /** 543 | * @return int 544 | */ 545 | public function getTimeoutWait() 546 | { 547 | return $this->timeoutWait; 548 | } 549 | 550 | /** 551 | * @return int 552 | */ 553 | public function getPrefetchCount() 554 | { 555 | return $this->prefetchCount; 556 | } 557 | 558 | /** 559 | * Set the memory limit 560 | * 561 | * @param int $memoryLimit 562 | */ 563 | public function setMemoryLimit($memoryLimit) 564 | { 565 | $this->memoryLimit = $memoryLimit; 566 | } 567 | 568 | /** 569 | * Get the memory limit 570 | * 571 | * @return int 572 | */ 573 | public function getMemoryLimit() 574 | { 575 | return $this->memoryLimit; 576 | } 577 | 578 | /** 579 | * Check graceful max execution date time and stop if limit is reached 580 | * 581 | * @return void 582 | */ 583 | private function checkGracefulMaxExecutionDateTime() 584 | { 585 | if (!$this->gracefulMaxExecutionDateTime) { 586 | return; 587 | } 588 | 589 | $now = new \DateTime(); 590 | 591 | if ($this->gracefulMaxExecutionDateTime > $now) { 592 | return; 593 | } 594 | 595 | $this->forceStopConsumer(); 596 | } 597 | } 598 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Configuration implements ConfigurationInterface 16 | { 17 | /** 18 | * @var string 19 | */ 20 | protected $name; 21 | 22 | /** 23 | * Configuration constructor. 24 | * 25 | * @param string $name 26 | */ 27 | public function __construct($name) 28 | { 29 | $this->name = $name; 30 | } 31 | 32 | public function getConfigTreeBuilder(): TreeBuilder 33 | { 34 | $tree = new TreeBuilder($this->name); 35 | /** @var ArrayNodeDefinition $rootNode */ 36 | $rootNode = $tree->getRootNode(); 37 | 38 | $rootNode 39 | ->children() 40 | ->booleanNode('debug')->defaultValue('%kernel.debug%')->end() 41 | ->booleanNode('enable_collector')->defaultValue(false)->end() 42 | ->booleanNode('sandbox')->defaultValue(false)->end() 43 | ->end() 44 | ; 45 | 46 | $this->addConnections($rootNode); 47 | $this->addBindings($rootNode); 48 | $this->addProducers($rootNode); 49 | $this->addConsumers($rootNode); 50 | $this->addMultipleConsumers($rootNode); 51 | $this->addDynamicConsumers($rootNode); 52 | $this->addBatchConsumers($rootNode); 53 | $this->addAnonConsumers($rootNode); 54 | $this->addRpcClients($rootNode); 55 | $this->addRpcServers($rootNode); 56 | 57 | return $tree; 58 | } 59 | 60 | protected function addConnections(ArrayNodeDefinition $node) 61 | { 62 | $node 63 | ->fixXmlConfig('connection') 64 | ->children() 65 | ->arrayNode('connections') 66 | ->useAttributeAsKey('key') 67 | ->canBeUnset() 68 | ->prototype('array') 69 | ->children() 70 | ->scalarNode('url')->defaultValue('')->end() 71 | ->scalarNode('host')->defaultValue('localhost')->end() 72 | ->scalarNode('port')->defaultValue(5672)->end() 73 | ->scalarNode('user')->defaultValue('guest')->end() 74 | ->scalarNode('password')->defaultValue('guest')->end() 75 | ->scalarNode('vhost')->defaultValue('/')->end() 76 | ->enumNode('login_method') 77 | ->values([ 78 | 'AMQPLAIN', // Can be replaced by AMQPConnectionConfig constants when PhpAmqpLib 2 support is dropped 79 | 'PLAIN', 80 | 'EXTERNAL', 81 | ]) 82 | ->defaultValue('AMQPLAIN') 83 | ->end() 84 | ->arrayNode('hosts') 85 | ->info('connection_timeout, read_write_timeout, use_socket, ssl_context, keepalive, 86 | heartbeat and connection_parameters_provider should be specified globally when 87 | you are using multiple hosts') 88 | ->canBeUnset() 89 | ->prototype('array') 90 | ->children() 91 | ->scalarNode('url')->defaultValue('')->end() 92 | ->scalarNode('host')->defaultValue('localhost')->end() 93 | ->scalarNode('port')->defaultValue(5672)->end() 94 | ->scalarNode('user')->defaultValue('guest')->end() 95 | ->scalarNode('password')->defaultValue('guest')->end() 96 | ->scalarNode('vhost')->defaultValue('/')->end() 97 | ->end() 98 | ->end() 99 | ->end() 100 | ->booleanNode('lazy')->defaultFalse()->end() 101 | ->scalarNode('connection_timeout')->defaultValue(3)->end() 102 | ->scalarNode('read_write_timeout')->defaultValue(3)->end() 103 | ->scalarNode('channel_rpc_timeout')->defaultValue(0.0)->end() 104 | ->booleanNode('use_socket')->defaultValue(false)->end() 105 | ->arrayNode('ssl_context') 106 | ->useAttributeAsKey('key') 107 | ->canBeUnset() 108 | ->prototype('variable')->end() 109 | ->end() 110 | ->booleanNode('keepalive')->defaultFalse()->info('requires php-amqplib v2.4.1+ and PHP5.4+')->end() 111 | ->scalarNode('heartbeat')->defaultValue(0)->info('requires php-amqplib v2.4.1+')->end() 112 | ->scalarNode('connection_parameters_provider')->end() 113 | ->end() 114 | ->end() 115 | ->end() 116 | ->end() 117 | ; 118 | } 119 | 120 | protected function addProducers(ArrayNodeDefinition $node) 121 | { 122 | $node 123 | ->fixXmlConfig('producer') 124 | ->children() 125 | ->arrayNode('producers') 126 | ->canBeUnset() 127 | ->useAttributeAsKey('key') 128 | ->prototype('array') 129 | ->append($this->getExchangeConfiguration()) 130 | ->append($this->getQueueConfiguration()) 131 | ->children() 132 | ->scalarNode('connection')->defaultValue('default')->end() 133 | ->scalarNode('auto_setup_fabric')->defaultTrue()->end() 134 | ->scalarNode('class')->defaultValue('%old_sound_rabbit_mq.producer.class%')->end() 135 | ->scalarNode('enable_logger')->defaultFalse()->end() 136 | ->scalarNode('service_alias')->defaultValue(null)->end() 137 | ->scalarNode('default_routing_key')->defaultValue('')->end() 138 | ->scalarNode('default_content_type')->defaultValue(Producer::DEFAULT_CONTENT_TYPE)->end() 139 | ->integerNode('default_delivery_mode')->min(1)->max(2)->defaultValue(2)->end() 140 | ->end() 141 | ->end() 142 | ->end() 143 | ->end() 144 | ; 145 | } 146 | 147 | protected function addBindings(ArrayNodeDefinition $node) 148 | { 149 | $node 150 | ->fixXmlConfig('binding') 151 | ->children() 152 | ->arrayNode('bindings') 153 | ->canBeUnset() 154 | ->prototype('array') 155 | ->children() 156 | ->scalarNode('connection')->defaultValue('default')->end() 157 | ->scalarNode('exchange')->defaultNull()->end() 158 | ->scalarNode('destination')->defaultNull()->end() 159 | ->scalarNode('routing_key')->defaultNull()->end() 160 | ->booleanNode('nowait')->defaultFalse()->end() 161 | ->booleanNode('destination_is_exchange')->defaultFalse()->end() 162 | ->variableNode('arguments')->defaultNull()->end() 163 | ->scalarNode('class')->defaultValue('%old_sound_rabbit_mq.binding.class%')->end() 164 | ->end() 165 | ->end() 166 | ->end() 167 | ->end() 168 | ; 169 | } 170 | 171 | protected function addConsumers(ArrayNodeDefinition $node) 172 | { 173 | $node 174 | ->fixXmlConfig('consumer') 175 | ->children() 176 | ->arrayNode('consumers') 177 | ->canBeUnset() 178 | ->useAttributeAsKey('key') 179 | ->prototype('array') 180 | ->append($this->getExchangeConfiguration()) 181 | ->append($this->getQueueConfiguration()) 182 | ->children() 183 | ->scalarNode('connection')->defaultValue('default')->end() 184 | ->scalarNode('callback')->isRequired()->end() 185 | ->scalarNode('idle_timeout')->end() 186 | ->scalarNode('idle_timeout_exit_code')->end() 187 | ->scalarNode('timeout_wait')->end() 188 | ->arrayNode('graceful_max_execution') 189 | ->canBeUnset() 190 | ->children() 191 | ->integerNode('timeout')->end() 192 | ->integerNode('exit_code')->defaultValue(0)->end() 193 | ->end() 194 | ->end() 195 | ->scalarNode('auto_setup_fabric')->defaultTrue()->end() 196 | ->arrayNode('options') 197 | ->canBeUnset() 198 | ->children() 199 | ->booleanNode('no_ack')->defaultFalse()->end() 200 | ->end() 201 | ->end() 202 | ->arrayNode('qos_options') 203 | ->canBeUnset() 204 | ->children() 205 | ->scalarNode('prefetch_size')->defaultValue(0)->end() 206 | ->scalarNode('prefetch_count')->defaultValue(0)->end() 207 | ->booleanNode('global')->defaultFalse()->end() 208 | ->end() 209 | ->end() 210 | ->scalarNode('enable_logger')->defaultFalse()->end() 211 | ->end() 212 | ->end() 213 | ->end() 214 | ->end() 215 | ; 216 | } 217 | 218 | protected function addMultipleConsumers(ArrayNodeDefinition $node) 219 | { 220 | $node 221 | ->fixXmlConfig('multiple_consumer') 222 | ->children() 223 | ->arrayNode('multiple_consumers') 224 | ->canBeUnset() 225 | ->useAttributeAsKey('key') 226 | ->prototype('array') 227 | ->append($this->getExchangeConfiguration()) 228 | ->children() 229 | ->scalarNode('connection')->defaultValue('default')->end() 230 | ->scalarNode('idle_timeout')->end() 231 | ->scalarNode('idle_timeout_exit_code')->end() 232 | ->scalarNode('timeout_wait')->end() 233 | ->scalarNode('auto_setup_fabric')->defaultTrue()->end() 234 | ->arrayNode('options') 235 | ->canBeUnset() 236 | ->children() 237 | ->booleanNode('no_ack')->defaultFalse()->end() 238 | ->end() 239 | ->end() 240 | ->arrayNode('graceful_max_execution') 241 | ->canBeUnset() 242 | ->children() 243 | ->integerNode('timeout')->end() 244 | ->integerNode('exit_code')->defaultValue(0)->end() 245 | ->end() 246 | ->end() 247 | ->append($this->getMultipleQueuesConfiguration()) 248 | ->arrayNode('qos_options') 249 | ->canBeUnset() 250 | ->children() 251 | ->scalarNode('prefetch_size')->defaultValue(0)->end() 252 | ->scalarNode('prefetch_count')->defaultValue(0)->end() 253 | ->booleanNode('global')->defaultFalse()->end() 254 | ->end() 255 | ->end() 256 | ->scalarNode('queues_provider')->defaultNull()->end() 257 | ->scalarNode('enable_logger')->defaultFalse()->end() 258 | ->end() 259 | ->end() 260 | ->end() 261 | ; 262 | } 263 | 264 | protected function addDynamicConsumers(ArrayNodeDefinition $node) 265 | { 266 | $node 267 | ->fixXmlConfig('dynamic_consumer') 268 | ->children() 269 | ->arrayNode('dynamic_consumers') 270 | ->canBeUnset() 271 | ->useAttributeAsKey('key') 272 | ->prototype('array') 273 | ->append($this->getExchangeConfiguration()) 274 | ->children() 275 | ->scalarNode('connection')->defaultValue('default')->end() 276 | ->scalarNode('callback')->isRequired()->end() 277 | ->scalarNode('idle_timeout')->end() 278 | ->scalarNode('idle_timeout_exit_code')->end() 279 | ->scalarNode('timeout_wait')->end() 280 | ->arrayNode('graceful_max_execution') 281 | ->canBeUnset() 282 | ->children() 283 | ->integerNode('timeout')->end() 284 | ->integerNode('exit_code')->defaultValue(0)->end() 285 | ->end() 286 | ->end() 287 | ->scalarNode('auto_setup_fabric')->defaultTrue()->end() 288 | ->arrayNode('options') 289 | ->canBeUnset() 290 | ->children() 291 | ->booleanNode('no_ack')->defaultFalse()->end() 292 | ->end() 293 | ->end() 294 | ->arrayNode('qos_options') 295 | ->canBeUnset() 296 | ->children() 297 | ->scalarNode('prefetch_size')->defaultValue(0)->end() 298 | ->scalarNode('prefetch_count')->defaultValue(0)->end() 299 | ->booleanNode('global')->defaultFalse()->end() 300 | ->end() 301 | ->end() 302 | ->scalarNode('queue_options_provider')->isRequired()->end() 303 | ->scalarNode('enable_logger')->defaultFalse()->end() 304 | ->end() 305 | ->end() 306 | ->end() 307 | ->end() 308 | ; 309 | } 310 | 311 | /** 312 | * @param ArrayNodeDefinition $node 313 | * 314 | * @return void 315 | */ 316 | protected function addBatchConsumers(ArrayNodeDefinition $node) 317 | { 318 | $node 319 | ->children() 320 | ->arrayNode('batch_consumers') 321 | ->canBeUnset() 322 | ->useAttributeAsKey('key') 323 | ->prototype('array') 324 | ->append($this->getExchangeConfiguration()) 325 | ->append($this->getQueueConfiguration()) 326 | ->children() 327 | ->scalarNode('connection')->defaultValue('default')->end() 328 | ->scalarNode('callback')->isRequired()->end() 329 | ->scalarNode('idle_timeout')->end() 330 | ->scalarNode('timeout_wait')->defaultValue(3)->end() 331 | ->scalarNode('idle_timeout_exit_code')->end() 332 | ->scalarNode('keep_alive')->defaultFalse()->end() 333 | ->arrayNode('graceful_max_execution') 334 | ->canBeUnset() 335 | ->children() 336 | ->integerNode('timeout')->end() 337 | ->end() 338 | ->end() 339 | ->scalarNode('auto_setup_fabric')->defaultTrue()->end() 340 | ->arrayNode('options') 341 | ->canBeUnset() 342 | ->children() 343 | ->booleanNode('no_ack')->defaultFalse()->end() 344 | ->end() 345 | ->end() 346 | ->arrayNode('qos_options') 347 | ->children() 348 | ->scalarNode('prefetch_size')->defaultValue(0)->end() 349 | ->scalarNode('prefetch_count')->defaultValue(2)->end() 350 | ->booleanNode('global')->defaultFalse()->end() 351 | ->end() 352 | ->end() 353 | ->scalarNode('enable_logger')->defaultFalse()->end() 354 | ->end() 355 | ->end() 356 | ->end() 357 | ->end() 358 | ; 359 | } 360 | 361 | protected function addAnonConsumers(ArrayNodeDefinition $node) 362 | { 363 | $node 364 | ->fixXmlConfig('anon_consumer') 365 | ->children() 366 | ->arrayNode('anon_consumers') 367 | ->canBeUnset() 368 | ->useAttributeAsKey('key') 369 | ->prototype('array') 370 | ->append($this->getExchangeConfiguration()) 371 | ->children() 372 | ->scalarNode('connection')->defaultValue('default')->end() 373 | ->scalarNode('callback')->isRequired()->end() 374 | ->arrayNode('options') 375 | ->canBeUnset() 376 | ->children() 377 | ->booleanNode('no_ack')->defaultFalse()->end() 378 | ->end() 379 | ->end() 380 | ->end() 381 | ->end() 382 | ->end() 383 | ->end() 384 | ; 385 | } 386 | 387 | protected function addRpcClients(ArrayNodeDefinition $node) 388 | { 389 | $node 390 | ->fixXmlConfig('rpc_client') 391 | ->children() 392 | ->arrayNode('rpc_clients') 393 | ->canBeUnset() 394 | ->useAttributeAsKey('key') 395 | ->prototype('array') 396 | ->children() 397 | ->scalarNode('connection')->defaultValue('default')->end() 398 | ->booleanNode('expect_serialized_response')->defaultTrue()->end() 399 | ->scalarNode('unserializer')->defaultValue('unserialize')->end() 400 | ->booleanNode('lazy')->defaultFalse()->end() 401 | ->booleanNode('direct_reply_to')->defaultFalse()->end() 402 | ->end() 403 | ->end() 404 | ->end() 405 | ->end() 406 | ; 407 | } 408 | 409 | protected function addRpcServers(ArrayNodeDefinition $node) 410 | { 411 | $node 412 | ->fixXmlConfig('rpc_server') 413 | ->children() 414 | ->arrayNode('rpc_servers') 415 | ->canBeUnset() 416 | ->useAttributeAsKey('key') 417 | ->prototype('array') 418 | ->append($this->getExchangeConfiguration()) 419 | ->append($this->getQueueConfiguration()) 420 | ->children() 421 | ->scalarNode('connection')->defaultValue('default')->end() 422 | ->scalarNode('callback')->isRequired()->end() 423 | ->arrayNode('qos_options') 424 | ->canBeUnset() 425 | ->children() 426 | ->scalarNode('prefetch_size')->defaultValue(0)->end() 427 | ->scalarNode('prefetch_count')->defaultValue(0)->end() 428 | ->booleanNode('global')->defaultFalse()->end() 429 | ->end() 430 | ->end() 431 | ->scalarNode('serializer')->defaultValue('serialize')->end() 432 | ->scalarNode('enable_logger')->defaultFalse()->end() 433 | ->end() 434 | ->end() 435 | ->end() 436 | ->end() 437 | ; 438 | } 439 | 440 | protected function getExchangeConfiguration() 441 | { 442 | $node = new ArrayNodeDefinition('exchange_options'); 443 | 444 | return $node 445 | ->children() 446 | ->scalarNode('name')->isRequired()->end() 447 | ->scalarNode('type')->isRequired()->end() 448 | ->booleanNode('passive')->defaultValue(false)->end() 449 | ->booleanNode('durable')->defaultValue(true)->end() 450 | ->booleanNode('auto_delete')->defaultValue(false)->end() 451 | ->booleanNode('internal')->defaultValue(false)->end() 452 | ->booleanNode('nowait')->defaultValue(false)->end() 453 | ->booleanNode('declare')->defaultValue(true)->end() 454 | ->variableNode('arguments')->defaultNull()->end() 455 | ->scalarNode('ticket')->defaultNull()->end() 456 | ->end() 457 | ; 458 | } 459 | 460 | protected function getQueueConfiguration() 461 | { 462 | $node = new ArrayNodeDefinition('queue_options'); 463 | 464 | $this->addQueueNodeConfiguration($node); 465 | 466 | return $node; 467 | } 468 | 469 | protected function getMultipleQueuesConfiguration() 470 | { 471 | $node = new ArrayNodeDefinition('queues'); 472 | $prototypeNode = $node->prototype('array'); 473 | 474 | $this->addQueueNodeConfiguration($prototypeNode); 475 | 476 | $prototypeNode->children() 477 | ->scalarNode('callback')->isRequired()->end() 478 | ->end(); 479 | 480 | $prototypeNode->end(); 481 | 482 | return $node; 483 | } 484 | 485 | protected function addQueueNodeConfiguration(ArrayNodeDefinition $node) 486 | { 487 | $node 488 | ->fixXmlConfig('routing_key') 489 | ->children() 490 | ->scalarNode('name')->end() 491 | ->booleanNode('passive')->defaultFalse()->end() 492 | ->booleanNode('durable')->defaultTrue()->end() 493 | ->booleanNode('exclusive')->defaultFalse()->end() 494 | ->booleanNode('auto_delete')->defaultFalse()->end() 495 | ->booleanNode('nowait')->defaultFalse()->end() 496 | ->booleanNode('declare')->defaultTrue()->end() 497 | ->variableNode('arguments')->defaultNull()->end() 498 | ->scalarNode('ticket')->defaultNull()->end() 499 | ->arrayNode('routing_keys') 500 | ->prototype('scalar')->end() 501 | ->defaultValue([]) 502 | ->end() 503 | ->end() 504 | ; 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /DependencyInjection/OldSoundRabbitMqExtension.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class OldSoundRabbitMqExtension extends Extension 24 | { 25 | /** 26 | * @var ContainerBuilder 27 | */ 28 | private $container; 29 | 30 | /** 31 | * @var Boolean Whether the data collector is enabled 32 | */ 33 | private $collectorEnabled; 34 | 35 | private $channelIds = []; 36 | 37 | private $config = []; 38 | 39 | public function load(array $configs, ContainerBuilder $container): void 40 | { 41 | $this->container = $container; 42 | 43 | $loader = new XmlFileLoader($this->container, new FileLocator([__DIR__ . '/../Resources/config'])); 44 | $loader->load('rabbitmq.xml'); 45 | 46 | $configuration = $this->getConfiguration($configs, $container); 47 | $this->config = $this->processConfiguration($configuration, $configs); 48 | 49 | $this->collectorEnabled = $this->config['enable_collector']; 50 | 51 | $this->loadConnections(); 52 | $this->loadBindings(); 53 | $this->loadProducers(); 54 | $this->loadConsumers(); 55 | $this->loadMultipleConsumers(); 56 | $this->loadDynamicConsumers(); 57 | $this->loadBatchConsumers(); 58 | $this->loadAnonConsumers(); 59 | $this->loadRpcClients(); 60 | $this->loadRpcServers(); 61 | 62 | if ($this->collectorEnabled && $this->channelIds) { 63 | $channels = []; 64 | foreach (array_unique($this->channelIds) as $id) { 65 | $channels[] = new Reference($id); 66 | } 67 | 68 | $definition = $container->getDefinition('old_sound_rabbit_mq.data_collector'); 69 | $definition->replaceArgument(0, $channels); 70 | } else { 71 | $this->container->removeDefinition('old_sound_rabbit_mq.data_collector'); 72 | } 73 | } 74 | 75 | public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface 76 | { 77 | return new Configuration($this->getAlias()); 78 | } 79 | 80 | protected function loadConnections() 81 | { 82 | foreach ($this->config['connections'] as $key => $connection) { 83 | $connectionSuffix = $connection['use_socket'] ? 'socket_connection.class' : 'connection.class'; 84 | $classParam = 85 | $connection['lazy'] 86 | ? '%old_sound_rabbit_mq.lazy.'.$connectionSuffix.'%' 87 | : '%old_sound_rabbit_mq.'.$connectionSuffix.'%'; 88 | 89 | $definition = new Definition('%old_sound_rabbit_mq.connection_factory.class%', [ 90 | $classParam, $connection, 91 | ]); 92 | if (isset($connection['connection_parameters_provider'])) { 93 | $definition->addArgument(new Reference($connection['connection_parameters_provider'])); 94 | unset($connection['connection_parameters_provider']); 95 | } 96 | $definition->setPublic(false); 97 | $factoryName = sprintf('old_sound_rabbit_mq.connection_factory.%s', $key); 98 | $this->container->setDefinition($factoryName, $definition); 99 | 100 | $definition = new Definition($classParam); 101 | if (method_exists($definition, 'setFactory')) { 102 | // to be inlined in services.xml when dependency on Symfony DependencyInjection is bumped to 2.6 103 | $definition->setFactory([new Reference($factoryName), 'createConnection']); 104 | } else { 105 | // to be removed when dependency on Symfony DependencyInjection is bumped to 2.6 106 | $definition->setFactoryService($factoryName); 107 | $definition->setFactoryMethod('createConnection'); 108 | } 109 | $definition->addTag('old_sound_rabbit_mq.connection'); 110 | $definition->setPublic(true); 111 | 112 | $this->container->setDefinition(sprintf('old_sound_rabbit_mq.connection.%s', $key), $definition); 113 | } 114 | } 115 | 116 | protected function loadBindings() 117 | { 118 | if ($this->config['sandbox']) { 119 | return; 120 | } 121 | foreach ($this->config['bindings'] as $binding) { 122 | ksort($binding); 123 | $definition = new Definition($binding['class']); 124 | $definition->addTag('old_sound_rabbit_mq.binding'); 125 | $definition->addMethodCall('setArguments', [$binding['arguments']]); 126 | $definition->addMethodCall('setDestination', [$binding['destination']]); 127 | $definition->addMethodCall('setDestinationIsExchange', [$binding['destination_is_exchange']]); 128 | $definition->addMethodCall('setExchange', [$binding['exchange']]); 129 | $definition->addMethodCall('isNowait', [$binding['nowait']]); 130 | $definition->addMethodCall('setRoutingKey', [$binding['routing_key']]); 131 | $this->injectConnection($definition, $binding['connection']); 132 | $key = md5(json_encode($binding)); 133 | if ($this->collectorEnabled) { 134 | // in the context of a binding, I don't thing logged channels are needed? 135 | $this->injectLoggedChannel($definition, $key, $binding['connection']); 136 | } 137 | $this->container->setDefinition(sprintf('old_sound_rabbit_mq.binding.%s', $key), $definition); 138 | } 139 | } 140 | 141 | protected function loadProducers() 142 | { 143 | if ($this->config['sandbox'] == false) { 144 | foreach ($this->config['producers'] as $key => $producer) { 145 | $definition = new Definition($producer['class']); 146 | $definition->setPublic(true); 147 | $definition->addTag('old_sound_rabbit_mq.base_amqp'); 148 | $definition->addTag('old_sound_rabbit_mq.producer'); 149 | //this producer doesn't define an exchange -> using AMQP Default 150 | if (!isset($producer['exchange_options'])) { 151 | $producer['exchange_options'] = $this->getDefaultExchangeOptions(); 152 | } 153 | $definition->addMethodCall('setExchangeOptions', [$this->normalizeArgumentKeys($producer['exchange_options'])]); 154 | //this producer doesn't define a queue -> using AMQP Default 155 | if (!isset($producer['queue_options'])) { 156 | $producer['queue_options'] = $this->getDefaultQueueOptions(); 157 | } 158 | $definition->addMethodCall('setQueueOptions', [$producer['queue_options']]); 159 | $this->injectConnection($definition, $producer['connection']); 160 | if ($this->collectorEnabled) { 161 | $this->injectLoggedChannel($definition, $key, $producer['connection']); 162 | } 163 | if (!$producer['auto_setup_fabric']) { 164 | $definition->addMethodCall('disableAutoSetupFabric'); 165 | } 166 | 167 | if ($producer['enable_logger']) { 168 | $this->injectLogger($definition); 169 | } 170 | 171 | $producerServiceName = sprintf('old_sound_rabbit_mq.%s_producer', $key); 172 | 173 | $this->container->setDefinition($producerServiceName, $definition); 174 | if (null !== $producer['service_alias']) { 175 | $this->container->setAlias($producer['service_alias'], $producerServiceName); 176 | } 177 | 178 | // register alias for argument auto wiring 179 | if (method_exists($this->container, 'registerAliasForArgument')) { 180 | $argName = !str_ends_with(strtolower($key), 'producer') ? sprintf('%sProducer', $key) : $key; 181 | $this->container 182 | ->registerAliasForArgument($producerServiceName, ProducerInterface::class, $argName) 183 | ->setPublic(false); 184 | 185 | $this->container 186 | ->registerAliasForArgument($producerServiceName, $producer['class'], $argName) 187 | ->setPublic(false); 188 | } 189 | 190 | $definition->addMethodCall('setDefaultRoutingKey', [$producer['default_routing_key']]); 191 | $definition->addMethodCall('setContentType', [$producer['default_content_type']]); 192 | $definition->addMethodCall('setDeliveryMode', [$producer['default_delivery_mode']]); 193 | } 194 | } else { 195 | foreach ($this->config['producers'] as $key => $producer) { 196 | $definition = new Definition('%old_sound_rabbit_mq.fallback.class%'); 197 | $producerServiceName = sprintf('old_sound_rabbit_mq.%s_producer', $key); 198 | $this->container->setDefinition($producerServiceName, $definition); 199 | 200 | // register alias for argumen auto wiring 201 | if (method_exists($this->container, 'registerAliasForArgument')) { 202 | $argName = !str_ends_with(strtolower($key), 'producer') ? sprintf('%sProducer', $key) : $key; 203 | $this->container 204 | ->registerAliasForArgument($producerServiceName, ProducerInterface::class, $argName) 205 | ->setPublic(false); 206 | } 207 | } 208 | } 209 | } 210 | 211 | protected function loadConsumers() 212 | { 213 | foreach ($this->config['consumers'] as $key => $consumer) { 214 | $definition = new Definition('%old_sound_rabbit_mq.consumer.class%'); 215 | $definition->setPublic(true); 216 | $definition->addTag('old_sound_rabbit_mq.base_amqp'); 217 | $definition->addTag('old_sound_rabbit_mq.consumer'); 218 | //this consumer doesn't define an exchange -> using AMQP Default 219 | if (!isset($consumer['exchange_options'])) { 220 | $consumer['exchange_options'] = $this->getDefaultExchangeOptions(); 221 | } 222 | $definition->addMethodCall('setExchangeOptions', [$this->normalizeArgumentKeys($consumer['exchange_options'])]); 223 | //this consumer doesn't define a queue -> using AMQP Default 224 | if (!isset($consumer['queue_options'])) { 225 | $consumer['queue_options'] = $this->getDefaultQueueOptions(); 226 | } 227 | $definition->addMethodCall('setQueueOptions', [$this->normalizeArgumentKeys($consumer['queue_options'])]); 228 | $definition->addMethodCall('setCallback', [[new Reference($consumer['callback']), 'execute']]); 229 | 230 | if (array_key_exists('qos_options', $consumer)) { 231 | $definition->addMethodCall('setQosOptions', [ 232 | $consumer['qos_options']['prefetch_size'], 233 | $consumer['qos_options']['prefetch_count'], 234 | $consumer['qos_options']['global'], 235 | ]); 236 | } 237 | 238 | if (isset($consumer['idle_timeout'])) { 239 | $definition->addMethodCall('setIdleTimeout', [$consumer['idle_timeout']]); 240 | } 241 | if (isset($consumer['idle_timeout_exit_code'])) { 242 | $definition->addMethodCall('setIdleTimeoutExitCode', [$consumer['idle_timeout_exit_code']]); 243 | } 244 | if (isset($consumer['timeout_wait'])) { 245 | $definition->addMethodCall('setTimeoutWait', [$consumer['timeout_wait']]); 246 | } 247 | if (isset($consumer['graceful_max_execution'])) { 248 | $definition->addMethodCall( 249 | 'setGracefulMaxExecutionDateTimeFromSecondsInTheFuture', 250 | [$consumer['graceful_max_execution']['timeout']] 251 | ); 252 | $definition->addMethodCall( 253 | 'setGracefulMaxExecutionTimeoutExitCode', 254 | [$consumer['graceful_max_execution']['exit_code']] 255 | ); 256 | } 257 | if (!$consumer['auto_setup_fabric']) { 258 | $definition->addMethodCall('disableAutoSetupFabric'); 259 | } 260 | if (isset($consumer['options'])) { 261 | $definition->addMethodCall( 262 | 'setConsumerOptions', 263 | [$this->normalizeArgumentKeys($consumer['options'])] 264 | ); 265 | } 266 | 267 | $this->injectConnection($definition, $consumer['connection']); 268 | if ($this->collectorEnabled) { 269 | $this->injectLoggedChannel($definition, $key, $consumer['connection']); 270 | } 271 | 272 | if ($consumer['enable_logger']) { 273 | $this->injectLogger($definition); 274 | } 275 | 276 | $name = sprintf('old_sound_rabbit_mq.%s_consumer', $key); 277 | $this->container->setDefinition($name, $definition); 278 | $this->addDequeuerAwareCall($consumer['callback'], $name); 279 | 280 | // register alias for argument auto wiring 281 | if (method_exists($this->container, 'registerAliasForArgument')) { 282 | $argName = !str_ends_with(strtolower($key), 'consumer') ? sprintf('%sConsumer', $key) : $key; 283 | $this->container 284 | ->registerAliasForArgument($name, ConsumerInterface::class, $argName) 285 | ->setPublic(false); 286 | 287 | $this->container 288 | ->registerAliasForArgument($name, '%old_sound_rabbit_mq.consumer.class%', $argName) 289 | ->setPublic(false); 290 | } 291 | } 292 | } 293 | 294 | protected function loadMultipleConsumers() 295 | { 296 | foreach ($this->config['multiple_consumers'] as $key => $consumer) { 297 | $queues = []; 298 | $callbacks = []; 299 | 300 | if (empty($consumer['queues']) && empty($consumer['queues_provider'])) { 301 | throw new InvalidConfigurationException( 302 | "Error on loading $key multiple consumer. " . 303 | "Either 'queues' or 'queues_provider' parameters should be defined." 304 | ); 305 | } 306 | 307 | foreach ($consumer['queues'] as $queueName => $queueOptions) { 308 | $queues[$queueOptions['name']] = $queueOptions; 309 | $queues[$queueOptions['name']]['callback'] = [new Reference($queueOptions['callback']), 'execute']; 310 | $callbacks[] = $queueOptions['callback']; 311 | } 312 | 313 | $definition = new Definition('%old_sound_rabbit_mq.multi_consumer.class%'); 314 | $definition 315 | ->setPublic(true) 316 | ->addTag('old_sound_rabbit_mq.base_amqp') 317 | ->addTag('old_sound_rabbit_mq.multi_consumer') 318 | ->addMethodCall('setExchangeOptions', [$this->normalizeArgumentKeys($consumer['exchange_options'])]) 319 | ->addMethodCall('setQueues', [$this->normalizeArgumentKeys($queues)]); 320 | 321 | if ($consumer['queues_provider']) { 322 | $definition->addMethodCall( 323 | 'setQueuesProvider', 324 | [new Reference($consumer['queues_provider'])] 325 | ); 326 | } 327 | 328 | if (array_key_exists('qos_options', $consumer)) { 329 | $definition->addMethodCall('setQosOptions', [ 330 | $consumer['qos_options']['prefetch_size'], 331 | $consumer['qos_options']['prefetch_count'], 332 | $consumer['qos_options']['global'], 333 | ]); 334 | } 335 | 336 | if (isset($consumer['idle_timeout'])) { 337 | $definition->addMethodCall('setIdleTimeout', [$consumer['idle_timeout']]); 338 | } 339 | if (isset($consumer['idle_timeout_exit_code'])) { 340 | $definition->addMethodCall('setIdleTimeoutExitCode', [$consumer['idle_timeout_exit_code']]); 341 | } 342 | if (isset($consumer['timeout_wait'])) { 343 | $definition->addMethodCall('setTimeoutWait', [$consumer['timeout_wait']]); 344 | } 345 | if (isset($consumer['graceful_max_execution'])) { 346 | $definition->addMethodCall( 347 | 'setGracefulMaxExecutionDateTimeFromSecondsInTheFuture', 348 | [$consumer['graceful_max_execution']['timeout']] 349 | ); 350 | $definition->addMethodCall( 351 | 'setGracefulMaxExecutionTimeoutExitCode', 352 | [$consumer['graceful_max_execution']['exit_code']] 353 | ); 354 | } 355 | if (!$consumer['auto_setup_fabric']) { 356 | $definition->addMethodCall('disableAutoSetupFabric'); 357 | } 358 | if (isset($consumer['options'])) { 359 | $definition->addMethodCall( 360 | 'setConsumerOptions', 361 | [$this->normalizeArgumentKeys($consumer['options'])] 362 | ); 363 | } 364 | 365 | $this->injectConnection($definition, $consumer['connection']); 366 | if ($this->collectorEnabled) { 367 | $this->injectLoggedChannel($definition, $key, $consumer['connection']); 368 | } 369 | 370 | if ($consumer['enable_logger']) { 371 | $this->injectLogger($definition); 372 | } 373 | 374 | $name = sprintf('old_sound_rabbit_mq.%s_multiple', $key); 375 | $this->container->setDefinition($name, $definition); 376 | if ($consumer['queues_provider']) { 377 | $this->addDequeuerAwareCall($consumer['queues_provider'], $name); 378 | } 379 | foreach ($callbacks as $callback) { 380 | $this->addDequeuerAwareCall($callback, $name); 381 | } 382 | } 383 | } 384 | 385 | protected function loadDynamicConsumers() 386 | { 387 | foreach ($this->config['dynamic_consumers'] as $key => $consumer) { 388 | if (empty($consumer['queue_options_provider'])) { 389 | throw new InvalidConfigurationException( 390 | "Error on loading $key dynamic consumer. " . 391 | "'queue_provider' parameter should be defined." 392 | ); 393 | } 394 | 395 | $definition = new Definition('%old_sound_rabbit_mq.dynamic_consumer.class%'); 396 | $definition 397 | ->setPublic(true) 398 | ->addTag('old_sound_rabbit_mq.base_amqp') 399 | ->addTag('old_sound_rabbit_mq.consumer') 400 | ->addTag('old_sound_rabbit_mq.dynamic_consumer') 401 | ->addMethodCall('setExchangeOptions', [$this->normalizeArgumentKeys($consumer['exchange_options'])]) 402 | ->addMethodCall('setCallback', [[new Reference($consumer['callback']), 'execute']]); 403 | 404 | if (array_key_exists('qos_options', $consumer)) { 405 | $definition->addMethodCall('setQosOptions', [ 406 | $consumer['qos_options']['prefetch_size'], 407 | $consumer['qos_options']['prefetch_count'], 408 | $consumer['qos_options']['global'], 409 | ]); 410 | } 411 | 412 | $definition->addMethodCall( 413 | 'setQueueOptionsProvider', 414 | [new Reference($consumer['queue_options_provider'])] 415 | ); 416 | 417 | if (isset($consumer['idle_timeout'])) { 418 | $definition->addMethodCall('setIdleTimeout', [$consumer['idle_timeout']]); 419 | } 420 | if (isset($consumer['idle_timeout_exit_code'])) { 421 | $definition->addMethodCall('setIdleTimeoutExitCode', [$consumer['idle_timeout_exit_code']]); 422 | } 423 | if (isset($consumer['timeout_wait'])) { 424 | $definition->addMethodCall('setTimeoutWait', [$consumer['timeout_wait']]); 425 | } 426 | if (isset($consumer['graceful_max_execution'])) { 427 | $definition->addMethodCall( 428 | 'setGracefulMaxExecutionDateTimeFromSecondsInTheFuture', 429 | [$consumer['graceful_max_execution']['timeout']] 430 | ); 431 | $definition->addMethodCall( 432 | 'setGracefulMaxExecutionTimeoutExitCode', 433 | [$consumer['graceful_max_execution']['exit_code']] 434 | ); 435 | } 436 | if (!$consumer['auto_setup_fabric']) { 437 | $definition->addMethodCall('disableAutoSetupFabric'); 438 | } 439 | if (isset($consumer['options'])) { 440 | $definition->addMethodCall( 441 | 'setConsumerOptions', 442 | [$this->normalizeArgumentKeys($consumer['options'])] 443 | ); 444 | } 445 | 446 | $this->injectConnection($definition, $consumer['connection']); 447 | if ($this->collectorEnabled) { 448 | $this->injectLoggedChannel($definition, $key, $consumer['connection']); 449 | } 450 | 451 | if ($consumer['enable_logger']) { 452 | $this->injectLogger($definition); 453 | } 454 | 455 | $name = sprintf('old_sound_rabbit_mq.%s_dynamic', $key); 456 | $this->container->setDefinition($name, $definition); 457 | $this->addDequeuerAwareCall($consumer['callback'], $name); 458 | $this->addDequeuerAwareCall($consumer['queue_options_provider'], $name); 459 | } 460 | } 461 | 462 | protected function loadBatchConsumers() 463 | { 464 | foreach ($this->config['batch_consumers'] as $key => $consumer) { 465 | $definition = new Definition('%old_sound_rabbit_mq.batch_consumer.class%'); 466 | 467 | if (!isset($consumer['exchange_options'])) { 468 | $consumer['exchange_options'] = $this->getDefaultExchangeOptions(); 469 | } 470 | 471 | $definition 472 | ->setPublic(true) 473 | ->addTag('old_sound_rabbit_mq.base_amqp') 474 | ->addTag('old_sound_rabbit_mq.batch_consumer') 475 | ->addMethodCall('setTimeoutWait', [$consumer['timeout_wait']]) 476 | ->addMethodCall('setPrefetchCount', [$consumer['qos_options']['prefetch_count']]) 477 | ->addMethodCall('setCallback', [[new Reference($consumer['callback']), 'batchExecute']]) 478 | ->addMethodCall('setExchangeOptions', [$this->normalizeArgumentKeys($consumer['exchange_options'])]) 479 | ->addMethodCall('setQueueOptions', [$this->normalizeArgumentKeys($consumer['queue_options'])]) 480 | ->addMethodCall('setQosOptions', [ 481 | $consumer['qos_options']['prefetch_size'], 482 | $consumer['qos_options']['prefetch_count'], 483 | $consumer['qos_options']['global'], 484 | ]) 485 | ; 486 | 487 | if (isset($consumer['idle_timeout_exit_code'])) { 488 | $definition->addMethodCall('setIdleTimeoutExitCode', [$consumer['idle_timeout_exit_code']]); 489 | } 490 | 491 | if (isset($consumer['idle_timeout'])) { 492 | $definition->addMethodCall('setIdleTimeout', [$consumer['idle_timeout']]); 493 | } 494 | 495 | if (isset($consumer['graceful_max_execution'])) { 496 | $definition->addMethodCall( 497 | 'setGracefulMaxExecutionDateTimeFromSecondsInTheFuture', 498 | [$consumer['graceful_max_execution']['timeout']] 499 | ); 500 | } 501 | 502 | if (!$consumer['auto_setup_fabric']) { 503 | $definition->addMethodCall('disableAutoSetupFabric'); 504 | } 505 | 506 | if (isset($consumer['options'])) { 507 | $definition->addMethodCall( 508 | 'setConsumerOptions', 509 | [$this->normalizeArgumentKeys($consumer['options'])] 510 | ); 511 | } 512 | 513 | if ($consumer['keep_alive']) { 514 | $definition->addMethodCall('keepAlive'); 515 | } 516 | 517 | $this->injectConnection($definition, $consumer['connection']); 518 | if ($this->collectorEnabled) { 519 | $this->injectLoggedChannel($definition, $key, $consumer['connection']); 520 | } 521 | 522 | if ($consumer['enable_logger']) { 523 | $this->injectLogger($definition); 524 | } 525 | 526 | $this->container->setDefinition(sprintf('old_sound_rabbit_mq.%s_batch', $key), $definition); 527 | } 528 | } 529 | 530 | protected function loadAnonConsumers() 531 | { 532 | foreach ($this->config['anon_consumers'] as $key => $anon) { 533 | $definition = new Definition('%old_sound_rabbit_mq.anon_consumer.class%'); 534 | $definition 535 | ->setPublic(true) 536 | ->addTag('old_sound_rabbit_mq.base_amqp') 537 | ->addTag('old_sound_rabbit_mq.anon_consumer') 538 | ->addMethodCall('setExchangeOptions', [$this->normalizeArgumentKeys($anon['exchange_options'])]) 539 | ->addMethodCall('setCallback', [[new Reference($anon['callback']), 'execute']]); 540 | 541 | if (isset($anon['options'])) { 542 | $definition->addMethodCall( 543 | 'setConsumerOptions', 544 | [$this->normalizeArgumentKeys($anon['options'])] 545 | ); 546 | } 547 | 548 | $this->injectConnection($definition, $anon['connection']); 549 | if ($this->collectorEnabled) { 550 | $this->injectLoggedChannel($definition, $key, $anon['connection']); 551 | } 552 | 553 | $name = sprintf('old_sound_rabbit_mq.%s_anon', $key); 554 | $this->container->setDefinition($name, $definition); 555 | $this->addDequeuerAwareCall($anon['callback'], $name); 556 | } 557 | } 558 | 559 | /** 560 | * Symfony 2 converts '-' to '_' when defined in the configuration. This leads to problems when using x-ha-policy 561 | * parameter. So we revert the change for right configurations. 562 | * 563 | * @param array $config 564 | * 565 | * @return array 566 | */ 567 | private function normalizeArgumentKeys(array $config): array 568 | { 569 | if (isset($config['arguments'])) { 570 | $arguments = $config['arguments']; 571 | // support for old configuration 572 | if (is_string($arguments)) { 573 | $arguments = $this->argumentsStringAsArray($arguments); 574 | } 575 | 576 | $newArguments = []; 577 | foreach ($arguments as $key => $value) { 578 | if (strstr($key, '_')) { 579 | $key = str_replace('_', '-', $key); 580 | } 581 | $newArguments[$key] = $value; 582 | } 583 | $config['arguments'] = $newArguments; 584 | } 585 | return $config; 586 | } 587 | 588 | /** 589 | * Support for arguments provided as string. Support for old configuration files. 590 | * 591 | * @deprecated 592 | * @param string $arguments 593 | * @return array 594 | */ 595 | private function argumentsStringAsArray($arguments): array 596 | { 597 | $argumentsArray = []; 598 | 599 | $argumentPairs = explode(',', $arguments); 600 | foreach ($argumentPairs as $argument) { 601 | $argumentPair = explode(':', $argument); 602 | $type = 'S'; 603 | if (isset($argumentPair[2])) { 604 | $type = $argumentPair[2]; 605 | } 606 | $argumentsArray[$argumentPair[0]] = [$type, $argumentPair[1]]; 607 | } 608 | 609 | return $argumentsArray; 610 | } 611 | 612 | protected function loadRpcClients() 613 | { 614 | foreach ($this->config['rpc_clients'] as $key => $client) { 615 | $definition = new Definition('%old_sound_rabbit_mq.rpc_client.class%'); 616 | $definition->setLazy($client['lazy']); 617 | $definition 618 | ->addTag('old_sound_rabbit_mq.rpc_client') 619 | ->addMethodCall('initClient', [$client['expect_serialized_response']]); 620 | $this->injectConnection($definition, $client['connection']); 621 | if ($this->collectorEnabled) { 622 | $this->injectLoggedChannel($definition, $key, $client['connection']); 623 | } 624 | if (array_key_exists('unserializer', $client)) { 625 | $definition->addMethodCall('setUnserializer', [$client['unserializer']]); 626 | } 627 | if (array_key_exists('direct_reply_to', $client)) { 628 | $definition->addMethodCall('setDirectReplyTo', [$client['direct_reply_to']]); 629 | } 630 | $definition->setPublic(true); 631 | 632 | $this->container->setDefinition(sprintf('old_sound_rabbit_mq.%s_rpc', $key), $definition); 633 | } 634 | } 635 | 636 | protected function loadRpcServers() 637 | { 638 | foreach ($this->config['rpc_servers'] as $key => $server) { 639 | $definition = new Definition('%old_sound_rabbit_mq.rpc_server.class%'); 640 | $definition 641 | ->setPublic(true) 642 | ->addTag('old_sound_rabbit_mq.base_amqp') 643 | ->addTag('old_sound_rabbit_mq.rpc_server') 644 | ->addMethodCall('initServer', [$key]) 645 | ->addMethodCall('setCallback', [[new Reference($server['callback']), 'execute']]); 646 | $this->injectConnection($definition, $server['connection']); 647 | if ($this->collectorEnabled) { 648 | $this->injectLoggedChannel($definition, $key, $server['connection']); 649 | } 650 | if (array_key_exists('qos_options', $server)) { 651 | $definition->addMethodCall('setQosOptions', [ 652 | $server['qos_options']['prefetch_size'], 653 | $server['qos_options']['prefetch_count'], 654 | $server['qos_options']['global'], 655 | ]); 656 | } 657 | if (array_key_exists('exchange_options', $server)) { 658 | $definition->addMethodCall('setExchangeOptions', [$server['exchange_options']]); 659 | } 660 | if (array_key_exists('queue_options', $server)) { 661 | $definition->addMethodCall('setQueueOptions', [$server['queue_options']]); 662 | } 663 | if (array_key_exists('serializer', $server)) { 664 | $definition->addMethodCall('setSerializer', [$server['serializer']]); 665 | } 666 | $this->container->setDefinition(sprintf('old_sound_rabbit_mq.%s_server', $key), $definition); 667 | } 668 | } 669 | 670 | protected function injectLoggedChannel(Definition $definition, $name, $connectionName) 671 | { 672 | $id = sprintf('old_sound_rabbit_mq.channel.%s', $name); 673 | $channel = new Definition('%old_sound_rabbit_mq.logged.channel.class%'); 674 | $channel 675 | ->setPublic(false) 676 | ->addTag('old_sound_rabbit_mq.logged_channel'); 677 | $this->injectConnection($channel, $connectionName); 678 | 679 | $this->container->setDefinition($id, $channel); 680 | 681 | $this->channelIds[] = $id; 682 | $definition->addArgument(new Reference($id)); 683 | } 684 | 685 | protected function injectConnection(Definition $definition, $connectionName) 686 | { 687 | $definition->addArgument(new Reference(sprintf('old_sound_rabbit_mq.connection.%s', $connectionName))); 688 | } 689 | 690 | public function getAlias(): string 691 | { 692 | return 'old_sound_rabbit_mq'; 693 | } 694 | 695 | /** 696 | * Add proper dequeuer aware call 697 | * 698 | * @param string $callback 699 | * @param string $name 700 | * @throws \ReflectionException 701 | */ 702 | protected function addDequeuerAwareCall($callback, $name) 703 | { 704 | if (!$this->container->has($callback)) { 705 | return; 706 | } 707 | 708 | $callbackDefinition = $this->container->findDefinition($callback); 709 | $refClass = new \ReflectionClass($callbackDefinition->getClass()); 710 | if ($refClass->implementsInterface('OldSound\RabbitMqBundle\RabbitMq\DequeuerAwareInterface')) { 711 | $callbackDefinition->addMethodCall('setDequeuer', [new Reference($name)]); 712 | } 713 | } 714 | 715 | private function injectLogger(Definition $definition) 716 | { 717 | $definition->addTag('monolog.logger', [ 718 | 'channel' => 'phpamqplib', 719 | ]); 720 | $definition->addMethodCall('setLogger', [new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]); 721 | } 722 | 723 | /** 724 | * Get default AMQP exchange options 725 | * 726 | * @return array 727 | */ 728 | protected function getDefaultExchangeOptions(): array 729 | { 730 | return [ 731 | 'name' => '', 732 | 'type' => 'direct', 733 | 'passive' => true, 734 | 'declare' => false, 735 | ]; 736 | } 737 | 738 | /** 739 | * Get default AMQP queue options 740 | * 741 | * @return array 742 | */ 743 | protected function getDefaultQueueOptions(): array 744 | { 745 | return [ 746 | 'name' => '', 747 | 'declare' => false, 748 | ]; 749 | } 750 | } 751 | --------------------------------------------------------------------------------