├── .github └── workflows │ └── test.yaml ├── .gitignore ├── .scrutinizer.yml ├── CHANGELOG ├── CODEOWNERS ├── Command ├── AnonConsumerCommand.php ├── BaseConsumerCommand.php ├── BaseRabbitMqCommand.php ├── BatchConsumerCommand.php ├── ConsumerCommand.php ├── DeleteCommand.php ├── DynamicConsumerCommand.php ├── MultipleConsumerCommand.php ├── PurgeConsumerCommand.php ├── RpcServerCommand.php ├── SetupFabricCommand.php └── StdInProducerCommand.php ├── DataCollector └── MessageDataCollector.php ├── DependencyInjection ├── Compiler │ ├── InjectEventDispatcherPass.php │ └── RegisterPartsPass.php ├── Configuration.php └── OldSoundRabbitMqExtension.php ├── Event ├── AMQPEvent.php ├── AbstractAMQPEvent.php ├── AfterProcessingMessageEvent.php ├── BeforeProcessingMessageEvent.php ├── OnConsumeEvent.php └── OnIdleEvent.php ├── MemoryChecker ├── MemoryConsumptionChecker.php └── NativeMemoryUsageProvider.php ├── OldSoundRabbitMqBundle.php ├── Provider ├── ConnectionParametersProviderInterface.php ├── QueueOptionsProviderInterface.php └── QueuesProviderInterface.php ├── README.md ├── RabbitMq ├── AMQPConnectionFactory.php ├── AMQPLoggedChannel.php ├── AmqpPartsHolder.php ├── AnonConsumer.php ├── BaseAmqp.php ├── BaseConsumer.php ├── BatchConsumer.php ├── BatchConsumerInterface.php ├── Binding.php ├── Consumer.php ├── ConsumerInterface.php ├── DequeuerAwareInterface.php ├── DequeuerInterface.php ├── DynamicConsumer.php ├── Exception │ ├── AckStopConsumerException.php │ ├── QueueNotFoundException.php │ └── StopConsumerException.php ├── Fallback.php ├── MultipleConsumer.php ├── Producer.php ├── ProducerInterface.php ├── RpcClient.php └── RpcServer.php ├── Resources ├── config │ └── rabbitmq.xml ├── meta │ └── LICENSE.md └── views │ └── Collector │ └── collector.html.twig ├── Tests ├── Command │ ├── BaseCommandTest.php │ ├── ConsumerCommandTest.php │ ├── DynamicConsumerCommandTest.php │ ├── MultipleConsumerCommandTest.php │ └── PurgeCommandTest.php ├── DependencyInjection │ ├── Fixtures │ │ ├── collector.yml │ │ ├── collector_disabled.yml │ │ ├── config_with_enable_logger.yml │ │ ├── exchange_arguments.yml │ │ ├── no_collector.yml │ │ ├── no_exchange_options.yml │ │ ├── rpc-clients.yml │ │ └── test.yml │ └── OldSoundRabbitMqExtensionTest.php ├── Event │ ├── AfterProcessingMessageEventTest.php │ ├── BeforeProcessingMessageEventTest.php │ └── OnIdleEventTest.php ├── Manager │ └── MemoryConsumptionCheckerTest.php ├── RabbitMq │ ├── AMQPConnectionFactoryTest.php │ ├── BaseAmqpTest.php │ ├── BaseConsumerTest.php │ ├── BindingTest.php │ ├── ConsumerTest.php │ ├── DynamicConsumerTest.php │ ├── Fixtures │ │ ├── AMQPConnection.php │ │ └── AMQPSocketConnection.php │ ├── MultipleConsumerTest.php │ ├── RpcClientTest.php │ └── RpcServerTest.php └── bootstrap.php ├── composer.json ├── phpstan.neon.dist └── phpunit.xml.dist /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: PHP ${{ matrix.php-version }} + Symfony ${{ matrix.symfony-version }} 8 | 9 | runs-on: ubuntu-18.04 10 | 11 | continue-on-error: ${{ matrix.experimental }} 12 | 13 | strategy: 14 | matrix: 15 | include: 16 | - php-version: '7.1' 17 | symfony-version: '^4.3' 18 | composer-version: v1 19 | stability: stable 20 | coverage: none 21 | experimental: false 22 | - php-version: '7.2' 23 | symfony-version: '^4.3' 24 | composer-version: v1 25 | stability: stable 26 | coverage: none 27 | experimental: false 28 | - php-version: '7.2' 29 | symfony-version: '^5.0' 30 | composer-version: v1 31 | stability: stable 32 | coverage: none 33 | experimental: false 34 | - php-version: '7.3' 35 | symfony-version: '^5.0' 36 | composer-version: v1 37 | stability: stable 38 | coverage: none 39 | experimental: false 40 | - php-version: '7.4' 41 | symfony-version: '^5.0' 42 | composer-version: v2 43 | stability: stable 44 | coverage: xdebug 45 | experimental: false 46 | - php-version: '8.0' 47 | symfony-version: '^5.0' 48 | composer-version: v2 49 | stability: RC 50 | coverage: none 51 | experimental: false 52 | 53 | steps: 54 | - name: Checkout 55 | uses: actions/checkout@v2 56 | 57 | - name: Setup PHP 58 | uses: shivammathur/setup-php@v2 59 | with: 60 | coverage: ${{ matrix.coverage }} 61 | ini-values: "memory_limit=-1" 62 | php-version: ${{ matrix.php-version }} 63 | tools: composer:${{ matrix.composer-version }} 64 | 65 | - name: Validate composer.json 66 | run: composer validate --no-check-lock 67 | 68 | - name: Configure Symfony version 69 | run: composer require --no-update symfony/framework-bundle "${{ matrix.symfony-version }}" 70 | 71 | - name: Configure composer stability 72 | if: matrix.stability != 'stable' 73 | run: composer config minimum-stability "${{ matrix.stability }}" 74 | 75 | - name: Install Composer dependencies 76 | uses: ramsey/composer-install@v1 77 | with: 78 | composer-options: "--prefer-dist" 79 | 80 | - name: Setup problem matchers for PHP 81 | run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" 82 | 83 | - name: Setup problem matchers for PHPUnit 84 | run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 85 | 86 | - name: Run PHPUnit 87 | if: matrix.coverage == 'none' 88 | run: vendor/bin/phpunit 89 | 90 | - name: Run PHPUnit with coverage 91 | if: matrix.coverage != 'none' 92 | run: vendor/bin/phpunit --coverage-clover=coverage.clover 93 | 94 | - name: Run PHPStan 95 | run: vendor/bin/phpstan analyse 96 | 97 | - name: Upload Scrutinizer coverage 98 | if: matrix.coverage != 'none' 99 | continue-on-error: true 100 | uses: sudo-bot/action-scrutinizer@latest 101 | with: 102 | cli-args: "--format=php-clover coverage.clover" 103 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | composer.phar 3 | vendor -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | build: 2 | dependencies: 3 | before: 4 | - composer config minimum-stability RC 5 | nodes: 6 | analysis: 7 | tests: 8 | override: 9 | - php-scrutinizer-run 10 | filter: 11 | excluded_paths: 12 | - tests/* 13 | - vendor/* 14 | checks: 15 | php: 16 | code_rating: true 17 | remove_extra_empty_lines: true 18 | remove_php_closing_tag: true 19 | remove_trailing_whitespace: true 20 | fix_use_statements: 21 | remove_unused: true 22 | preserve_multiple: false 23 | preserve_blanklines: true 24 | order_alphabetically: true 25 | fix_php_opening_tag: true 26 | fix_linefeed: true 27 | fix_line_ending: true 28 | fix_identation_4spaces: true 29 | fix_doc_comments: true 30 | tools: 31 | external_code_coverage: true 32 | php_code_sniffer: 33 | config: 34 | standard: PSR2 35 | filter: 36 | paths: ['src'] 37 | php_loc: 38 | enabled: true 39 | excluded_dirs: [vendor, tests, examples] 40 | php_cpd: 41 | enabled: true 42 | excluded_dirs: [vendor, tests, examples] 43 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | - 2017-01-22 2 | * Add `graceful_max_execution_timeout` 3 | 4 | - 2015-02-07 5 | * Added possibility to set serialize/unserialize function for rpc servers/clients 6 | 7 | - 2014-11-27 8 | * Added interface `OldSound\RabbitMqBundle\Provider\QueuesProviderInterface` 9 | * Added `queues_provider` configuration for multiple consumer 10 | 11 | - 2014-07-21 12 | * Added `reconnect` method into `OldSound\RabbitMqBundle\RabbitMq\BaseAmqp` 13 | 14 | - 2014-02-24 15 | * Add a parameter to RPC client configuration to disable auto unserialize when adding call results to results array. 16 | 17 | - 2013-01-18 18 | * 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. 19 | 20 | - 2012-06-04 21 | * Revert PR #46. It is still possible to override parameter classes but in a proper way. 22 | * Some default options for exchanges declared in the "producers" config section 23 | have changed to match the defaults of exchanges declared in the "consumers" section. 24 | The affected settings are: 25 | * `durable` was changed from `false` to `true`, 26 | * `auto_delete` was changed from `true` to `false`. 27 | * Adds basic_reject functionality to consumers. A message can be rejected by returning `false` from a callback. 28 | 29 | - 2012-05-29 30 | * Merged PR #46 adding compiler passes for the configuration. Now the parameter classes can be overriden. 31 | * Treats queue arguments as a variableNode in the configuration to allow declaring HA Queues. 32 | 33 | - 2012-01-03 34 | * Merged PR #14 Now instances of `PhpAmqpLib\Message\AMQPMessage` are passed to consumers instead of just the message body. 35 | The message body can be obtained then by writing `$msg->body` inside the `execute` method. 36 | * Merged PR #13 removing the need for the ContainerAwareInterface on consumers. 37 | * `rabbitmq:consumer` now takes a `--route` argument that can be used to specify the routing key. 38 | 39 | - 2011-11-25: 40 | * Fixed autoload configuration example 41 | * Adds a producer that can publish data coming from STDIN. The use case will be to use it in combination with unix pipes. 42 | 43 | - 2011-11-24: 44 | * The rabbitmq:consumer command consumes messages forever unless the -m option is provided. 45 | * The -m option for the rabbitmq:consumer command must be greater null or greater than 0. 46 | * Fixed issues #2 #7 #11 and #12. 47 | * Updated the bundle to use the latest version from videlalvaro/php-amqplib. 48 | * Updated installation/setup instructions. 49 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | ##################################################### 2 | # 3 | # List of approvers for EmagTechLabs/RabbitMQBundle 4 | # 5 | ##################################################### 6 | # 7 | # Learn about CODEOWNERS file format: 8 | # https://help.github.com/en/articles/about-code-owners 9 | # 10 | 11 | * @mihaileu 12 | -------------------------------------------------------------------------------- /Command/AnonConsumerCommand.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 | 18 | protected function getConsumerService() 19 | { 20 | return 'old_sound_rabbit_mq.%s_anon'; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Command/BaseConsumerCommand.php: -------------------------------------------------------------------------------- 1 | consumer instanceof Consumer) { 23 | // Process current message, then halt consumer 24 | $this->consumer->forceStopConsumer(); 25 | 26 | // Halt consumer if waiting for a new message from the queue 27 | try { 28 | $this->consumer->stopConsuming(); 29 | } catch (AMQPTimeoutException $e) {} 30 | } 31 | } 32 | 33 | public function restartConsumer() 34 | { 35 | // TODO: Implement restarting of consumer 36 | } 37 | 38 | protected function configure() 39 | { 40 | parent::configure(); 41 | 42 | $this 43 | ->addArgument('name', InputArgument::REQUIRED, 'Consumer Name') 44 | ->addOption('messages', 'm', InputOption::VALUE_OPTIONAL, 'Messages to consume', 0) 45 | ->addOption('route', 'r', InputOption::VALUE_OPTIONAL, 'Routing Key', '') 46 | ->addOption('memory-limit', 'l', InputOption::VALUE_OPTIONAL, 'Allowed memory for this process (MB)', null) 47 | ->addOption('debug', 'd', InputOption::VALUE_NONE, 'Enable Debugging') 48 | ->addOption('without-signals', 'w', InputOption::VALUE_NONE, 'Disable catching of system signals') 49 | ; 50 | } 51 | 52 | /** 53 | * Executes the current command. 54 | * 55 | * @param InputInterface $input An InputInterface instance 56 | * @param OutputInterface $output An OutputInterface instance 57 | * 58 | * @return integer 0 if everything went fine, or an error code 59 | * 60 | * @throws \InvalidArgumentException When the number of messages to consume is less than 0 61 | * @throws \BadFunctionCallException When the pcntl is not installed and option -s is true 62 | */ 63 | protected function execute(InputInterface $input, OutputInterface $output) 64 | { 65 | if (defined('AMQP_WITHOUT_SIGNALS') === false) { 66 | define('AMQP_WITHOUT_SIGNALS', $input->getOption('without-signals')); 67 | } 68 | 69 | if (!AMQP_WITHOUT_SIGNALS && extension_loaded('pcntl')) { 70 | if (!function_exists('pcntl_signal')) { 71 | throw new \BadFunctionCallException("Function 'pcntl_signal' is referenced in the php.ini 'disable_functions' and can't be called."); 72 | } 73 | 74 | pcntl_signal(SIGTERM, array(&$this, 'stopConsumer')); 75 | pcntl_signal(SIGINT, array(&$this, 'stopConsumer')); 76 | pcntl_signal(SIGHUP, array(&$this, 'restartConsumer')); 77 | } 78 | 79 | if (defined('AMQP_DEBUG') === false) { 80 | define('AMQP_DEBUG', (bool) $input->getOption('debug')); 81 | } 82 | 83 | $this->amount = $input->getOption('messages'); 84 | 85 | if (0 > (int) $this->amount) { 86 | throw new \InvalidArgumentException("The -m option should be null or greater than 0"); 87 | } 88 | $this->initConsumer($input); 89 | 90 | return $this->consumer->consume($this->amount); 91 | } 92 | 93 | protected function initConsumer($input) 94 | { 95 | $this->consumer = $this->getContainer() 96 | ->get(sprintf($this->getConsumerService(), $input->getArgument('name'))); 97 | 98 | if (!is_null($input->getOption('memory-limit')) && ctype_digit((string) $input->getOption('memory-limit')) && $input->getOption('memory-limit') > 0) { 99 | $this->consumer->setMemoryLimit($input->getOption('memory-limit')); 100 | } 101 | $this->consumer->setRoutingKey($input->getOption('route')); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Command/BaseRabbitMqCommand.php: -------------------------------------------------------------------------------- 1 | container = $container; 22 | } 23 | 24 | /** 25 | * @return ContainerInterface 26 | */ 27 | public function getContainer() 28 | { 29 | return $this->container; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /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 | protected function configure() 33 | { 34 | parent::configure(); 35 | 36 | $this 37 | ->setName('rabbitmq:batch:consumer') 38 | ->addArgument('name', InputArgument::REQUIRED, 'Consumer Name') 39 | ->addOption('batches', 'b', InputOption::VALUE_OPTIONAL, 'Number of batches to consume', 0) 40 | ->addOption('route', 'r', InputOption::VALUE_OPTIONAL, 'Routing Key', '') 41 | ->addOption('memory-limit', 'l', InputOption::VALUE_OPTIONAL, 'Allowed memory for this process', null) 42 | ->addOption('debug', 'd', InputOption::VALUE_NONE, 'Enable Debugging') 43 | ->addOption('without-signals', 'w', InputOption::VALUE_NONE, 'Disable catching of system signals') 44 | ->setDescription('Executes a Batch Consumer'); 45 | ; 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) 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, array(&$this, 'stopConsumer')); 71 | pcntl_signal(SIGINT, array(&$this, 'stopConsumer')); 72 | } 73 | 74 | if (defined('AMQP_DEBUG') === false) { 75 | define('AMQP_DEBUG', (bool) $input->getOption('debug')); 76 | } 77 | 78 | $batchAmountTarget = (int) $input->getOption('batches'); 79 | 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | /** 26 | * @param InputInterface $input 27 | * @param OutputInterface $output 28 | * 29 | * @return int 30 | */ 31 | protected function execute(InputInterface $input, OutputInterface $output) 32 | { 33 | $noConfirmation = (bool) $input->getOption('no-confirmation'); 34 | 35 | if (!$noConfirmation && $input->isInteractive()) { 36 | $question = new ConfirmationQuestion( 37 | sprintf( 38 | 'Are you sure you wish to delete "%s" consumer\'s queue? (y/n)', 39 | $input->getArgument('name') 40 | ), 41 | false 42 | ); 43 | 44 | if (!$this->getHelper('question')->ask($input, $output, $question)) { 45 | $output->writeln('Deletion cancelled!'); 46 | 47 | return 1; 48 | } 49 | } 50 | 51 | $this->consumer = $this->getContainer() 52 | ->get(sprintf($this->getConsumerService(), $input->getArgument('name'))); 53 | $this->consumer->delete(); 54 | 55 | return 0; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Command/DynamicConsumerCommand.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | namespace OldSound\RabbitMqBundle\Command; 12 | 13 | use Symfony\Component\Console\Input\InputArgument; 14 | 15 | class DynamicConsumerCommand extends BaseConsumerCommand 16 | { 17 | protected function configure() 18 | { 19 | parent::configure(); 20 | 21 | $this 22 | ->setName('rabbitmq:dynamic-consumer') 23 | ->setDescription('Executes context-aware consumer') 24 | ->addArgument('context', InputArgument::REQUIRED, 'Context the consumer runs in') 25 | ; 26 | } 27 | 28 | protected function getConsumerService() 29 | { 30 | return 'old_sound_rabbit_mq.%s_dynamic'; 31 | } 32 | 33 | protected function initConsumer($input) 34 | { 35 | parent::initConsumer($input); 36 | $this->consumer->setContext($input->getArgument('context')); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Command/MultipleConsumerCommand.php: -------------------------------------------------------------------------------- 1 | setDescription('Executes a consumer that uses multiple queues') 14 | ->setName('rabbitmq:multiple-consumer') 15 | ->addArgument('context', InputArgument::OPTIONAL, 'Context the consumer runs in') 16 | ; 17 | } 18 | 19 | protected function getConsumerService() 20 | { 21 | return 'old_sound_rabbit_mq.%s_multiple'; 22 | } 23 | 24 | protected function initConsumer($input) 25 | { 26 | parent::initConsumer($input); 27 | $this->consumer->setContext($input->getArgument('context')); 28 | } 29 | } -------------------------------------------------------------------------------- /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 | /** 26 | * @param InputInterface $input 27 | * @param OutputInterface $output 28 | * 29 | * @return int 30 | */ 31 | protected function execute(InputInterface $input, OutputInterface $output) 32 | { 33 | $noConfirmation = (bool) $input->getOption('no-confirmation'); 34 | 35 | if (!$noConfirmation && $input->isInteractive()) { 36 | $question = new ConfirmationQuestion( 37 | sprintf( 38 | 'Are you sure you wish to purge "%s" queue? (y/n)', 39 | $input->getArgument('name') 40 | ), 41 | false 42 | ); 43 | 44 | if (!$this->getHelper('question')->ask($input, $output, $question)) { 45 | $output->writeln('Purging cancelled!'); 46 | 47 | return 1; 48 | } 49 | } 50 | 51 | $this->consumer = $this->getContainer() 52 | ->get(sprintf($this->getConsumerService(), $input->getArgument('name'))); 53 | $this->consumer->purge($input->getArgument('name')); 54 | 55 | return 0; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /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) 36 | { 37 | define('AMQP_DEBUG', (bool) $input->getOption('debug')); 38 | $amount = $input->getOption('messages'); 39 | 40 | if (0 > (int) $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 | -------------------------------------------------------------------------------- /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) 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 (array('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 | } 44 | -------------------------------------------------------------------------------- /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) 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('Invalid payload format "%s", expecting one of: %s.', 59 | $format, implode(', ', array(self::FORMAT_PHP, self::FORMAT_RAW)))); 60 | } 61 | 62 | $producer->publish($data, $route); 63 | 64 | return 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /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 = array(); 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)) { 25 | return; 26 | } 27 | $taggedConsumers = $container->findTaggedServiceIds('old_sound_rabbit_mq.base_amqp'); 28 | 29 | foreach ($taggedConsumers as $id => $tag) { 30 | $definition = $container->getDefinition($id); 31 | $definition->addMethodCall( 32 | 'setEventDispatcher', 33 | array( 34 | new Reference(self::EVENT_DISPATCHER_SERVICE_ID, ContainerInterface::IGNORE_ON_INVALID_REFERENCE) 35 | ) 36 | ); 37 | } 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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 = array( 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', array($tag, new Reference($id))); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Event/AMQPEvent.php: -------------------------------------------------------------------------------- 1 | AMQPMessage; 38 | } 39 | 40 | /** 41 | * @param AMQPMessage $AMQPMessage 42 | * 43 | * @return AMQPEvent 44 | */ 45 | public function setAMQPMessage(AMQPMessage $AMQPMessage) 46 | { 47 | $this->AMQPMessage = $AMQPMessage; 48 | 49 | return $this; 50 | } 51 | 52 | /** 53 | * @return Consumer 54 | */ 55 | public function getConsumer() 56 | { 57 | return $this->consumer; 58 | } 59 | 60 | /** 61 | * @param Consumer $consumer 62 | * 63 | * @return AMQPEvent 64 | */ 65 | public function setConsumer(Consumer $consumer) 66 | { 67 | $this->consumer = $consumer; 68 | 69 | return $this; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Event/AbstractAMQPEvent.php: -------------------------------------------------------------------------------- 1 | setConsumer($consumer); 25 | $this->setAMQPMessage($AMQPMessage); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Event/BeforeProcessingMessageEvent.php: -------------------------------------------------------------------------------- 1 | setConsumer($consumer); 25 | $this->setAMQPMessage($AMQPMessage); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Event/OnConsumeEvent.php: -------------------------------------------------------------------------------- 1 | setConsumer($consumer); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Event/OnIdleEvent.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 | -------------------------------------------------------------------------------- /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 | $this->memoryUsageProvider = $memoryUsageProvider; 22 | } 23 | 24 | /** 25 | * @param int|string $allowedConsumptionUntil 26 | * @param int|string $maxConsumptionAllowed 27 | * 28 | * @return bool 29 | */ 30 | public function isRamAlmostOverloaded($maxConsumptionAllowed, $allowedConsumptionUntil = 0) 31 | { 32 | $allowedConsumptionUntil = $this->convertHumanUnitToNumerical($allowedConsumptionUntil); 33 | $maxConsumptionAllowed = $this->convertHumanUnitToNumerical($maxConsumptionAllowed); 34 | $currentUsage = $this->convertHumanUnitToNumerical($this->memoryUsageProvider->getMemoryUsage()); 35 | 36 | return $currentUsage > ($maxConsumptionAllowed - $allowedConsumptionUntil); 37 | } 38 | 39 | /** 40 | * @param int|string $humanUnit 41 | * 42 | * @return int 43 | */ 44 | private function convertHumanUnitToNumerical($humanUnit) 45 | { 46 | $numerical = $humanUnit; 47 | if (!is_numeric($humanUnit)) { 48 | $numerical = (int) substr($numerical, 0, -1); 49 | switch (substr($humanUnit, -1)) { 50 | case 'G': 51 | $numerical *= pow(1024, 3); 52 | break; 53 | case 'M': 54 | $numerical *= pow(1024, 2); 55 | break; 56 | case 'K': 57 | $numerical *= 1024; 58 | break; 59 | } 60 | } 61 | 62 | return (int)$numerical; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /MemoryChecker/NativeMemoryUsageProvider.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 | -------------------------------------------------------------------------------- /OldSoundRabbitMqBundle.php: -------------------------------------------------------------------------------- 1 | addCompilerPass(new RegisterPartsPass()); 17 | $container->addCompilerPass(new InjectEventDispatcherPass()); 18 | } 19 | 20 | /** 21 | * {@inheritDoc} 22 | */ 23 | public function shutdown() 24 | { 25 | parent::shutdown(); 26 | if (!$this->container->hasParameter('old_sound_rabbit_mq.base_amqp')) { 27 | return; 28 | } 29 | $connections = $this->container->getParameter('old_sound_rabbit_mq.base_amqp'); 30 | foreach ($connections as $connection) { 31 | if ($this->container->initialized($connection)) { 32 | $this->container->get($connection)->close(); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /RabbitMq/AMQPConnectionFactory.php: -------------------------------------------------------------------------------- 1 | '', 17 | 'host' => 'localhost', 18 | 'port' => 5672, 19 | 'user' => 'guest', 20 | 'password' => 'guest', 21 | 'vhost' => '/', 22 | 'connection_timeout' => 3, 23 | 'read_write_timeout' => 3, 24 | 'ssl_context' => null, 25 | 'keepalive' => false, 26 | 'heartbeat' => 0, 27 | ); 28 | 29 | /** 30 | * Constructor 31 | * 32 | * @param string $class FQCN of AMQPConnection class to instantiate. 33 | * @param array $parameters Map containing parameters resolved by 34 | * Extension. 35 | * @param ConnectionParametersProviderInterface $parametersProvider Optional service providing/overriding 36 | * connection parameters. 37 | */ 38 | public function __construct( 39 | $class, 40 | array $parameters, 41 | ConnectionParametersProviderInterface $parametersProvider = null 42 | ) { 43 | $this->class = $class; 44 | $this->parameters = array_merge($this->parameters, $parameters); 45 | $this->parameters = $this->parseUrl($this->parameters); 46 | if (is_array($this->parameters['ssl_context'])) { 47 | $this->parameters['ssl_context'] = ! empty($this->parameters['ssl_context']) 48 | ? stream_context_create(array('ssl' => $this->parameters['ssl_context'])) 49 | : null; 50 | } 51 | if ($parametersProvider) { 52 | $this->parameters = array_merge($this->parameters, $parametersProvider->getConnectionParameters()); 53 | } 54 | } 55 | 56 | /** 57 | * Creates the appropriate connection using current parameters. 58 | * 59 | * @return AbstractConnection 60 | */ 61 | public function createConnection() 62 | { 63 | $ref = new \ReflectionClass($this->class); 64 | 65 | if (isset($this->parameters['constructor_args']) && is_array($this->parameters['constructor_args'])) { 66 | return $ref->newInstanceArgs($this->parameters['constructor_args']); 67 | } 68 | 69 | if ($this->class == 'PhpAmqpLib\Connection\AMQPSocketConnection' || is_subclass_of($this->class, 'PhpAmqpLib\Connection\AMQPSocketConnection')) { 70 | return $ref->newInstanceArgs([ 71 | $this->parameters['host'], 72 | $this->parameters['port'], 73 | $this->parameters['user'], 74 | $this->parameters['password'], 75 | $this->parameters['vhost'], 76 | false, // insist 77 | 'AMQPLAIN', // login_method 78 | null, // login_response 79 | 'en_US', // locale 80 | isset($this->parameters['read_timeout']) ? $this->parameters['read_timeout'] : $this->parameters['read_write_timeout'], 81 | $this->parameters['keepalive'], 82 | isset($this->parameters['write_timeout']) ? $this->parameters['write_timeout'] : $this->parameters['read_write_timeout'], 83 | $this->parameters['heartbeat'] 84 | ] 85 | ); 86 | } else { 87 | return $ref->newInstanceArgs([ 88 | $this->parameters['host'], 89 | $this->parameters['port'], 90 | $this->parameters['user'], 91 | $this->parameters['password'], 92 | $this->parameters['vhost'], 93 | false, // insist 94 | 'AMQPLAIN', // login_method 95 | null, // login_response 96 | 'en_US', // locale 97 | $this->parameters['connection_timeout'], 98 | $this->parameters['read_write_timeout'], 99 | $this->parameters['ssl_context'], 100 | $this->parameters['keepalive'], 101 | $this->parameters['heartbeat'] 102 | ]); 103 | } 104 | } 105 | 106 | /** 107 | * Parses connection parameters from URL parameter. 108 | * 109 | * @param array $parameters 110 | * 111 | * @return array 112 | */ 113 | private function parseUrl(array $parameters) 114 | { 115 | if (!$parameters['url']) { 116 | return $parameters; 117 | } 118 | 119 | $url = parse_url($parameters['url']); 120 | 121 | if ($url === false || !isset($url['scheme']) || !in_array($url['scheme'], ['amqp', 'amqps'], true)) { 122 | throw new InvalidConfigurationException('Malformed parameter "url".'); 123 | } 124 | 125 | // See https://www.rabbitmq.com/uri-spec.html 126 | if (isset($url['host'])) { 127 | $parameters['host'] = urldecode($url['host']); 128 | } 129 | if (isset($url['port'])) { 130 | $parameters['port'] = (int)$url['port']; 131 | } 132 | if (isset($url['user'])) { 133 | $parameters['user'] = urldecode($url['user']); 134 | } 135 | if (isset($url['pass'])) { 136 | $parameters['password'] = urldecode($url['pass']); 137 | } 138 | if (isset($url['path'])) { 139 | $parameters['vhost'] = urldecode(ltrim($url['path'], '/')); 140 | } 141 | 142 | if (isset($url['query'])) { 143 | $query = array(); 144 | parse_str($url['query'], $query); 145 | $parameters = array_merge($parameters, $query); 146 | } 147 | 148 | unset($parameters['url']); 149 | 150 | return $parameters; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /RabbitMq/AMQPLoggedChannel.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class AMQPLoggedChannel extends AMQPChannel 13 | { 14 | private $basicPublishLog = array(); 15 | 16 | public function basic_publish($msg, $exchange = '', $routingKey = '', $mandatory = false, $immediate = false, $ticket = NULL) 17 | { 18 | $this->basicPublishLog[] = array( 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 | -------------------------------------------------------------------------------- /RabbitMq/AmqpPartsHolder.php: -------------------------------------------------------------------------------- 1 | parts = array(); 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 isset($this->parts[$type]) ? $this->parts[$type] : array(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /RabbitMq/AnonConsumer.php: -------------------------------------------------------------------------------- 1 | setQueueOptions(array( 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/BaseAmqp.php: -------------------------------------------------------------------------------- 1 | 'text/plain', 'delivery_mode' => 2); 23 | 24 | /** 25 | * @var LoggerInterface 26 | */ 27 | protected $logger; 28 | 29 | protected $exchangeOptions = array( 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 = array( 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 | /** 53 | * @var EventDispatcherInterface|null 54 | */ 55 | protected $eventDispatcher = null; 56 | 57 | /** 58 | * @param AbstractConnection $conn 59 | * @param AMQPChannel|null $ch 60 | * @param null $consumerTag 61 | */ 62 | public function __construct(AbstractConnection $conn, AMQPChannel $ch = null, $consumerTag = null) 63 | { 64 | $this->conn = $conn; 65 | $this->ch = $ch; 66 | 67 | if ($conn->connectOnConstruct()) { 68 | $this->getChannel(); 69 | } 70 | 71 | $this->consumerTag = empty($consumerTag) ? sprintf("PHPPROCESS_%s_%s", gethostname(), getmypid()) : $consumerTag; 72 | 73 | $this->logger = new NullLogger(); 74 | } 75 | 76 | public function __destruct() 77 | { 78 | $this->close(); 79 | } 80 | 81 | public function close() 82 | { 83 | if ($this->ch) { 84 | try { 85 | $this->ch->close(); 86 | } catch (\Exception $e) { 87 | // ignore on shutdown 88 | } 89 | } 90 | 91 | if ($this->conn && $this->conn->isConnected()) { 92 | try { 93 | $this->conn->close(); 94 | } catch (\Exception $e) { 95 | // ignore on shutdown 96 | } 97 | } 98 | } 99 | 100 | public function reconnect() 101 | { 102 | if (!$this->conn->isConnected()) { 103 | return; 104 | } 105 | 106 | $this->conn->reconnect(); 107 | } 108 | 109 | /** 110 | * @return AMQPChannel 111 | */ 112 | public function getChannel() 113 | { 114 | if (empty($this->ch) || null === $this->ch->getChannelId()) { 115 | $this->ch = $this->conn->channel(); 116 | } 117 | 118 | return $this->ch; 119 | } 120 | 121 | /** 122 | * @param AMQPChannel $ch 123 | * 124 | * @return void 125 | */ 126 | public function setChannel(AMQPChannel $ch) 127 | { 128 | $this->ch = $ch; 129 | } 130 | 131 | /** 132 | * @throws \InvalidArgumentException 133 | * @param array $options 134 | * @return void 135 | */ 136 | public function setExchangeOptions(array $options = array()) 137 | { 138 | if (!isset($options['name'])) { 139 | throw new \InvalidArgumentException('You must provide an exchange name'); 140 | } 141 | 142 | if (empty($options['type'])) { 143 | throw new \InvalidArgumentException('You must provide an exchange type'); 144 | } 145 | 146 | $this->exchangeOptions = array_merge($this->exchangeOptions, $options); 147 | } 148 | 149 | /** 150 | * @param array $options 151 | * @return void 152 | */ 153 | public function setQueueOptions(array $options = array()) 154 | { 155 | $this->queueOptions = array_merge($this->queueOptions, $options); 156 | } 157 | 158 | /** 159 | * @param string $routingKey 160 | * @return void 161 | */ 162 | public function setRoutingKey($routingKey) 163 | { 164 | $this->routingKey = $routingKey; 165 | } 166 | 167 | public function setupFabric() 168 | { 169 | if (!$this->exchangeDeclared) { 170 | $this->exchangeDeclare(); 171 | } 172 | 173 | if (!$this->queueDeclared) { 174 | $this->queueDeclare(); 175 | } 176 | } 177 | 178 | /** 179 | * disables the automatic SetupFabric when using a consumer or producer 180 | */ 181 | public function disableAutoSetupFabric() 182 | { 183 | $this->autoSetupFabric = false; 184 | } 185 | 186 | /** 187 | * @param LoggerInterface $logger 188 | */ 189 | public function setLogger($logger) 190 | { 191 | $this->logger = $logger; 192 | } 193 | 194 | /** 195 | * Declares exchange 196 | */ 197 | protected function exchangeDeclare() 198 | { 199 | if ($this->exchangeOptions['declare']) { 200 | $this->getChannel()->exchange_declare( 201 | $this->exchangeOptions['name'], 202 | $this->exchangeOptions['type'], 203 | $this->exchangeOptions['passive'], 204 | $this->exchangeOptions['durable'], 205 | $this->exchangeOptions['auto_delete'], 206 | $this->exchangeOptions['internal'], 207 | $this->exchangeOptions['nowait'], 208 | $this->exchangeOptions['arguments'], 209 | $this->exchangeOptions['ticket']); 210 | 211 | $this->exchangeDeclared = true; 212 | } 213 | } 214 | 215 | /** 216 | * Declares queue, creates if needed 217 | */ 218 | protected function queueDeclare() 219 | { 220 | if ($this->queueOptions['declare']) { 221 | list($queueName, ,) = $this->getChannel()->queue_declare($this->queueOptions['name'], $this->queueOptions['passive'], 222 | $this->queueOptions['durable'], $this->queueOptions['exclusive'], 223 | $this->queueOptions['auto_delete'], $this->queueOptions['nowait'], 224 | $this->queueOptions['arguments'], $this->queueOptions['ticket']); 225 | 226 | if (isset($this->queueOptions['routing_keys']) && count($this->queueOptions['routing_keys']) > 0) { 227 | foreach ($this->queueOptions['routing_keys'] as $routingKey) { 228 | $this->queueBind($queueName, $this->exchangeOptions['name'], $routingKey, $this->queueOptions['arguments'] ?? []); 229 | } 230 | } else { 231 | $this->queueBind($queueName, $this->exchangeOptions['name'], $this->routingKey, $this->queueOptions['arguments'] ?? []); 232 | } 233 | 234 | $this->queueDeclared = true; 235 | } 236 | } 237 | 238 | /** 239 | * Binds queue to an exchange 240 | * 241 | * @param string $queue 242 | * @param string $exchange 243 | * @param string $routing_key 244 | */ 245 | protected function queueBind($queue, $exchange, $routing_key, array $arguments = array()) 246 | { 247 | // queue binding is not permitted on the default exchange 248 | if ('' !== $exchange) { 249 | $this->getChannel()->queue_bind($queue, $exchange, $routing_key, false, $arguments); 250 | } 251 | } 252 | 253 | /** 254 | * @param EventDispatcherInterface $eventDispatcher 255 | * 256 | * @return BaseAmqp 257 | */ 258 | public function setEventDispatcher(EventDispatcherInterface $eventDispatcher) 259 | { 260 | $this->eventDispatcher = $eventDispatcher; 261 | 262 | return $this; 263 | } 264 | 265 | /** 266 | * @param string $eventName 267 | * @param AMQPEvent $event 268 | */ 269 | protected function dispatchEvent($eventName, AMQPEvent $event) 270 | { 271 | if ($this->getEventDispatcher() instanceof ContractsEventDispatcherInterface) { 272 | $this->getEventDispatcher()->dispatch( 273 | $event, 274 | $eventName 275 | ); 276 | } 277 | } 278 | 279 | /** 280 | * @return EventDispatcherInterface|null 281 | */ 282 | public function getEventDispatcher() 283 | { 284 | return $this->eventDispatcher; 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /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 (count($this->getChannel()->callbacks)) { 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, false, false, false, array($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 | -------------------------------------------------------------------------------- /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 (count($this->getChannel()->callbacks)) { 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', array( 150 | 'amqp' => array( 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 restart', array( 158 | 'amqp' => array( 159 | 'queue' => $this->queueOptions['name'], 160 | 'message' => $this->messages, 161 | 'stacktrace' => $e->getTraceAsString() 162 | ) 163 | )); 164 | $this->resetBatch(); 165 | $this->stopConsuming(); 166 | } catch (\Exception $e) { 167 | $this->logger->error($e->getMessage(), array( 168 | 'amqp' => array( 169 | 'queue' => $this->queueOptions['name'], 170 | 'message' => $this->messages, 171 | 'stacktrace' => $e->getTraceAsString() 172 | ) 173 | )); 174 | $this->resetBatch(); 175 | throw $e; 176 | } catch (\Error $e) { 177 | $this->logger->error($e->getMessage(), array( 178 | 'amqp' => array( 179 | 'queue' => $this->queueOptions['name'], 180 | 'message' => $this->messages, 181 | 'stacktrace' => $e->getTraceAsString() 182 | ) 183 | )); 184 | $this->resetBatch(); 185 | throw $e; 186 | } 187 | 188 | $this->batchAmount++; 189 | $this->resetBatch(); 190 | } 191 | 192 | /** 193 | * @param mixed $processFlags 194 | * 195 | * @return void 196 | */ 197 | protected function handleProcessMessages($processFlags = null) 198 | { 199 | $processFlags = $this->analyzeProcessFlags($processFlags); 200 | foreach ($processFlags as $deliveryTag => $processFlag) { 201 | $this->handleProcessFlag($deliveryTag, $processFlag); 202 | } 203 | } 204 | 205 | /** 206 | * @param string|int $deliveryTag 207 | * @param mixed $processFlag 208 | * 209 | * @return void 210 | */ 211 | private function handleProcessFlag($deliveryTag, $processFlag) 212 | { 213 | if ($processFlag === ConsumerInterface::MSG_REJECT_REQUEUE || false === $processFlag) { 214 | // Reject and requeue message to RabbitMQ 215 | $this->getMessageChannel($deliveryTag)->basic_reject($deliveryTag, true); 216 | } else if ($processFlag === ConsumerInterface::MSG_SINGLE_NACK_REQUEUE) { 217 | // NACK and requeue message to RabbitMQ 218 | $this->getMessageChannel($deliveryTag)->basic_nack($deliveryTag, false, true); 219 | } else if ($processFlag === ConsumerInterface::MSG_REJECT) { 220 | // Reject and drop 221 | $this->getMessageChannel($deliveryTag)->basic_reject($deliveryTag, false); 222 | } else { 223 | // Remove message from queue only if callback return not false 224 | $this->getMessageChannel($deliveryTag)->basic_ack($deliveryTag); 225 | } 226 | 227 | } 228 | 229 | /** 230 | * @return bool 231 | */ 232 | protected function isCompleteBatch() 233 | { 234 | return $this->batchCounter === $this->prefetchCount; 235 | } 236 | 237 | /** 238 | * @return bool 239 | */ 240 | protected function isEmptyBatch() 241 | { 242 | return $this->batchCounter === 0; 243 | } 244 | 245 | /** 246 | * @param AMQPMessage $msg 247 | * 248 | * @return void 249 | * 250 | * @throws \Error 251 | * @throws \Exception 252 | */ 253 | public function processMessage(AMQPMessage $msg) 254 | { 255 | $this->addMessage($msg); 256 | 257 | $this->maybeStopConsumer(); 258 | } 259 | 260 | /** 261 | * @param mixed $processFlags 262 | * 263 | * @return array 264 | */ 265 | private function analyzeProcessFlags($processFlags = null) 266 | { 267 | if (is_array($processFlags)) { 268 | if (count($processFlags) !== $this->batchCounter) { 269 | throw new AMQPRuntimeException( 270 | 'Method batchExecute() should return an array with elements equal with the number of messages processed' 271 | ); 272 | } 273 | 274 | return $processFlags; 275 | } 276 | 277 | $response = array(); 278 | foreach ($this->messages as $deliveryTag => $message) { 279 | $response[$deliveryTag] = $processFlags; 280 | } 281 | 282 | return $response; 283 | } 284 | 285 | 286 | /** 287 | * @return void 288 | */ 289 | private function resetBatch() 290 | { 291 | $this->messages = array(); 292 | $this->batchCounter = 0; 293 | } 294 | 295 | /** 296 | * @param AMQPMessage $message 297 | * 298 | * @return void 299 | */ 300 | private function addMessage(AMQPMessage $message) 301 | { 302 | $this->batchCounter++; 303 | $this->messages[(int)$message->delivery_info['delivery_tag']] = $message; 304 | } 305 | 306 | /** 307 | * @param int $deliveryTag 308 | * 309 | * @return AMQPMessage|null 310 | */ 311 | private function getMessage($deliveryTag) 312 | { 313 | return isset($this->messages[$deliveryTag]) 314 | ? $this->messages[$deliveryTag] 315 | : null 316 | ; 317 | } 318 | 319 | /** 320 | * @param int $deliveryTag 321 | * 322 | * @return AMQPChannel 323 | * 324 | * @throws AMQPRuntimeException 325 | */ 326 | private function getMessageChannel($deliveryTag) 327 | { 328 | $message = $this->getMessage($deliveryTag); 329 | if ($message === null) { 330 | throw new AMQPRuntimeException(sprintf('Unknown delivery_tag %d!', $deliveryTag)); 331 | } 332 | 333 | return $message->delivery_info['channel']; 334 | } 335 | 336 | /** 337 | * @return void 338 | */ 339 | public function stopConsuming() 340 | { 341 | if (!$this->isEmptyBatch()) { 342 | $this->batchConsume(); 343 | } 344 | 345 | $this->getChannel()->basic_cancel($this->getConsumerTag(), false, true); 346 | } 347 | 348 | /** 349 | * @return void 350 | */ 351 | protected function setupConsumer() 352 | { 353 | if ($this->autoSetupFabric) { 354 | $this->setupFabric(); 355 | } 356 | 357 | $this->getChannel()->basic_consume($this->queueOptions['name'], $this->getConsumerTag(), false, false, false, false, array($this, 'processMessage')); 358 | } 359 | 360 | /** 361 | * @return void 362 | * 363 | * @throws \BadFunctionCallException 364 | */ 365 | protected function maybeStopConsumer() 366 | { 367 | if (extension_loaded('pcntl') && (defined('AMQP_WITHOUT_SIGNALS') ? !AMQP_WITHOUT_SIGNALS : true)) { 368 | if (!function_exists('pcntl_signal_dispatch')) { 369 | throw new \BadFunctionCallException("Function 'pcntl_signal_dispatch' is referenced in the php.ini 'disable_functions' and can't be called."); 370 | } 371 | 372 | pcntl_signal_dispatch(); 373 | } 374 | 375 | if ($this->forceStop || ($this->batchAmount == $this->batchAmountTarget && $this->batchAmountTarget > 0)) { 376 | $this->stopConsuming(); 377 | } 378 | 379 | if (null !== $this->getMemoryLimit() && $this->isRamAlmostOverloaded()) { 380 | $this->stopConsuming(); 381 | } 382 | } 383 | 384 | /** 385 | * @param string $tag 386 | * 387 | * @return $this 388 | */ 389 | public function setConsumerTag($tag) 390 | { 391 | $this->consumerTag = $tag; 392 | 393 | return $this; 394 | } 395 | 396 | /** 397 | * @return string 398 | */ 399 | public function getConsumerTag() 400 | { 401 | return $this->consumerTag; 402 | } 403 | 404 | /** 405 | * @return void 406 | */ 407 | public function forceStopConsumer() 408 | { 409 | $this->forceStop = true; 410 | } 411 | 412 | /** 413 | * Sets the qos settings for the current channel 414 | * Consider that prefetchSize and global do not work with rabbitMQ version <= 8.0 415 | * 416 | * @param int $prefetchSize 417 | * @param int $prefetchCount 418 | * @param bool $global 419 | */ 420 | public function setQosOptions($prefetchSize = 0, $prefetchCount = 0, $global = false) 421 | { 422 | $this->prefetchCount = $prefetchCount; 423 | $this->getChannel()->basic_qos($prefetchSize, $prefetchCount, $global); 424 | } 425 | 426 | /** 427 | * @param int $idleTimeout 428 | * 429 | * @return $this 430 | */ 431 | public function setIdleTimeout($idleTimeout) 432 | { 433 | $this->idleTimeout = $idleTimeout; 434 | 435 | return $this; 436 | } 437 | 438 | /** 439 | * Set exit code to be returned when there is a timeout exception 440 | * 441 | * @param int $idleTimeoutExitCode 442 | * 443 | * @return $this 444 | */ 445 | public function setIdleTimeoutExitCode($idleTimeoutExitCode) 446 | { 447 | $this->idleTimeoutExitCode = $idleTimeoutExitCode; 448 | 449 | return $this; 450 | } 451 | 452 | /** 453 | * keepAlive 454 | * 455 | * @return $this 456 | */ 457 | public function keepAlive() 458 | { 459 | $this->keepAlive = true; 460 | 461 | return $this; 462 | } 463 | 464 | /** 465 | * Purge the queue 466 | */ 467 | public function purge() 468 | { 469 | $this->getChannel()->queue_purge($this->queueOptions['name'], true); 470 | } 471 | 472 | /** 473 | * Delete the queue 474 | */ 475 | public function delete() 476 | { 477 | $this->getChannel()->queue_delete($this->queueOptions['name'], true); 478 | } 479 | 480 | /** 481 | * Checks if memory in use is greater or equal than memory allowed for this process 482 | * 483 | * @return boolean 484 | */ 485 | protected function isRamAlmostOverloaded() 486 | { 487 | return (memory_get_usage(true) >= ($this->getMemoryLimit() * 1048576)); 488 | } 489 | 490 | /** 491 | * @return int 492 | */ 493 | public function getIdleTimeout() 494 | { 495 | return $this->idleTimeout; 496 | } 497 | 498 | /** 499 | * Get exit code to be returned when there is a timeout exception 500 | * 501 | * @return int|null 502 | */ 503 | public function getIdleTimeoutExitCode() 504 | { 505 | return $this->idleTimeoutExitCode; 506 | } 507 | 508 | /** 509 | * Resets the consumed property. 510 | * Use when you want to call start() or consume() multiple times. 511 | */ 512 | public function resetConsumed() 513 | { 514 | $this->consumed = 0; 515 | } 516 | 517 | /** 518 | * @param int $timeout 519 | * 520 | * @return $this 521 | */ 522 | public function setTimeoutWait($timeout) 523 | { 524 | $this->timeoutWait = $timeout; 525 | 526 | return $this; 527 | } 528 | 529 | /** 530 | * @param int $amount 531 | * 532 | * @return $this 533 | */ 534 | public function setPrefetchCount($amount) 535 | { 536 | $this->prefetchCount = $amount; 537 | 538 | return $this; 539 | } 540 | 541 | /** 542 | * @return int 543 | */ 544 | public function getTimeoutWait() 545 | { 546 | return $this->timeoutWait; 547 | } 548 | 549 | /** 550 | * @return int 551 | */ 552 | public function getPrefetchCount() 553 | { 554 | return $this->prefetchCount; 555 | } 556 | 557 | /** 558 | * Set the memory limit 559 | * 560 | * @param int $memoryLimit 561 | */ 562 | public function setMemoryLimit($memoryLimit) 563 | { 564 | $this->memoryLimit = $memoryLimit; 565 | } 566 | 567 | /** 568 | * Get the memory limit 569 | * 570 | * @return int 571 | */ 572 | public function getMemoryLimit() 573 | { 574 | return $this->memoryLimit; 575 | } 576 | 577 | /** 578 | * Check graceful max execution date time and stop if limit is reached 579 | * 580 | * @return void 581 | */ 582 | private function checkGracefulMaxExecutionDateTime() 583 | { 584 | if (!$this->gracefulMaxExecutionDateTime) { 585 | return; 586 | } 587 | 588 | $now = new \DateTime(); 589 | 590 | if ($this->gracefulMaxExecutionDateTime > $now) { 591 | return; 592 | } 593 | 594 | $this->forceStopConsumer(); 595 | } 596 | } 597 | -------------------------------------------------------------------------------- /RabbitMq/BatchConsumerInterface.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 | array($channel, $method), 145 | $this->destination, 146 | $this->exchange, 147 | $this->routingKey, 148 | $this->nowait, 149 | $this->arguments 150 | ); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /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 (count($this->getChannel()->callbacks)) { 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 | $waitTimeout = $this->chooseWaitTimeout(); 87 | if ($this->gracefulMaxExecutionDateTime 88 | && $waitTimeout < 1 89 | ) { 90 | return $this->gracefulMaxExecutionTimeoutExitCode; 91 | } 92 | 93 | if (!$this->forceStop) { 94 | try { 95 | $this->getChannel()->wait(null, false, $waitTimeout); 96 | $this->setLastActivityDateTime(new \DateTime()); 97 | } catch (AMQPTimeoutException $e) { 98 | $now = time(); 99 | 100 | if ($this->gracefulMaxExecutionDateTime 101 | && $this->gracefulMaxExecutionDateTime <= new \DateTime("@$now") 102 | ) { 103 | return $this->gracefulMaxExecutionTimeoutExitCode; 104 | } elseif ($this->getIdleTimeout() 105 | && ($this->getLastActivityDateTime()->getTimestamp() + $this->getIdleTimeout() <= $now) 106 | ) { 107 | $idleEvent = new OnIdleEvent($this); 108 | $this->dispatchEvent(OnIdleEvent::NAME, $idleEvent); 109 | 110 | if ($idleEvent->isForceStop()) { 111 | if (null !== $this->getIdleTimeoutExitCode()) { 112 | return $this->getIdleTimeoutExitCode(); 113 | } else { 114 | throw $e; 115 | } 116 | } 117 | } 118 | } 119 | } 120 | } 121 | 122 | return 0; 123 | } 124 | 125 | /** 126 | * Purge the queue 127 | */ 128 | public function purge() 129 | { 130 | $this->getChannel()->queue_purge($this->queueOptions['name'], true); 131 | } 132 | 133 | /** 134 | * Delete the queue 135 | */ 136 | public function delete() 137 | { 138 | $this->getChannel()->queue_delete($this->queueOptions['name'], true); 139 | } 140 | 141 | protected function processMessageQueueCallback(AMQPMessage $msg, $queueName, $callback) 142 | { 143 | $this->dispatchEvent(BeforeProcessingMessageEvent::NAME, 144 | new BeforeProcessingMessageEvent($this, $msg) 145 | ); 146 | try { 147 | $processFlag = call_user_func($callback, $msg); 148 | $this->handleProcessMessage($msg, $processFlag); 149 | $this->dispatchEvent( 150 | AfterProcessingMessageEvent::NAME, 151 | new AfterProcessingMessageEvent($this, $msg) 152 | ); 153 | $this->logger->debug('Queue message processed', array( 154 | 'amqp' => array( 155 | 'queue' => $queueName, 156 | 'message' => $msg, 157 | 'return_code' => $processFlag 158 | ) 159 | )); 160 | } catch (Exception\StopConsumerException $e) { 161 | $this->logger->info('Consumer requested restart', array( 162 | 'amqp' => array( 163 | 'queue' => $queueName, 164 | 'message' => $msg, 165 | 'stacktrace' => $e->getTraceAsString() 166 | ) 167 | )); 168 | $this->handleProcessMessage($msg, $e->getHandleCode()); 169 | $this->stopConsuming(); 170 | } catch (\Exception $e) { 171 | $this->logger->error($e->getMessage(), array( 172 | 'amqp' => array( 173 | 'queue' => $queueName, 174 | 'message' => $msg, 175 | 'stacktrace' => $e->getTraceAsString() 176 | ) 177 | )); 178 | throw $e; 179 | } catch (\Error $e) { 180 | $this->logger->error($e->getMessage(), array( 181 | 'amqp' => array( 182 | 'queue' => $queueName, 183 | 'message' => $msg, 184 | 'stacktrace' => $e->getTraceAsString() 185 | ) 186 | )); 187 | throw $e; 188 | } 189 | } 190 | 191 | public function processMessage(AMQPMessage $msg) 192 | { 193 | $this->processMessageQueueCallback($msg, $this->queueOptions['name'], $this->callback); 194 | } 195 | 196 | protected function handleProcessMessage(AMQPMessage $msg, $processFlag) 197 | { 198 | if ($processFlag === ConsumerInterface::MSG_REJECT_REQUEUE || false === $processFlag) { 199 | // Reject and requeue message to RabbitMQ 200 | $msg->delivery_info['channel']->basic_reject($msg->delivery_info['delivery_tag'], true); 201 | } else if ($processFlag === ConsumerInterface::MSG_SINGLE_NACK_REQUEUE) { 202 | // NACK and requeue message to RabbitMQ 203 | $msg->delivery_info['channel']->basic_nack($msg->delivery_info['delivery_tag'], false, true); 204 | } else if ($processFlag === ConsumerInterface::MSG_REJECT) { 205 | // Reject and drop 206 | $msg->delivery_info['channel']->basic_reject($msg->delivery_info['delivery_tag'], false); 207 | } else if ($processFlag !== ConsumerInterface::MSG_ACK_SENT) { 208 | // Remove message from queue only if callback return not false 209 | $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']); 210 | } 211 | 212 | $this->consumed++; 213 | $this->maybeStopConsumer(); 214 | 215 | if (!is_null($this->getMemoryLimit()) && $this->isRamAlmostOverloaded()) { 216 | $this->stopConsuming(); 217 | } 218 | } 219 | 220 | /** 221 | * Checks if memory in use is greater or equal than memory allowed for this process 222 | * 223 | * @return boolean 224 | */ 225 | protected function isRamAlmostOverloaded() 226 | { 227 | $memoryManager = new MemoryConsumptionChecker(new NativeMemoryUsageProvider()); 228 | 229 | return $memoryManager->isRamAlmostOverloaded($this->getMemoryLimit().'M', '5M'); 230 | } 231 | 232 | /** 233 | * @param \DateTime|null $dateTime 234 | */ 235 | public function setGracefulMaxExecutionDateTime(\DateTime $dateTime = null) 236 | { 237 | $this->gracefulMaxExecutionDateTime = $dateTime; 238 | } 239 | 240 | /** 241 | * @param int $secondsInTheFuture 242 | */ 243 | public function setGracefulMaxExecutionDateTimeFromSecondsInTheFuture($secondsInTheFuture) 244 | { 245 | $this->setGracefulMaxExecutionDateTime(new \DateTime("+{$secondsInTheFuture} seconds")); 246 | } 247 | 248 | /** 249 | * @param int $exitCode 250 | */ 251 | public function setGracefulMaxExecutionTimeoutExitCode($exitCode) 252 | { 253 | $this->gracefulMaxExecutionTimeoutExitCode = $exitCode; 254 | } 255 | 256 | public function setTimeoutWait(int $timeoutWait): void 257 | { 258 | $this->timeoutWait = $timeoutWait; 259 | } 260 | 261 | /** 262 | * @return \DateTime|null 263 | */ 264 | public function getGracefulMaxExecutionDateTime() 265 | { 266 | return $this->gracefulMaxExecutionDateTime; 267 | } 268 | 269 | /** 270 | * @return int 271 | */ 272 | public function getGracefulMaxExecutionTimeoutExitCode() 273 | { 274 | return $this->gracefulMaxExecutionTimeoutExitCode; 275 | } 276 | 277 | public function getTimeoutWait(): ?int 278 | { 279 | return $this->timeoutWait; 280 | } 281 | 282 | /** 283 | * Choose the timeout wait (in seconds) to use for the $this->getChannel()->wait() method. 284 | */ 285 | private function chooseWaitTimeout(): int 286 | { 287 | if ($this->gracefulMaxExecutionDateTime) { 288 | $allowedExecutionDateInterval = $this->gracefulMaxExecutionDateTime->diff(new \DateTime()); 289 | $allowedExecutionSeconds = $allowedExecutionDateInterval->days * 86400 290 | + $allowedExecutionDateInterval->h * 3600 291 | + $allowedExecutionDateInterval->i * 60 292 | + $allowedExecutionDateInterval->s; 293 | 294 | if (!$allowedExecutionDateInterval->invert) { 295 | $allowedExecutionSeconds *= -1; 296 | } 297 | 298 | /* 299 | * Respect the idle timeout if it's set and if it's less than 300 | * the remaining allowed execution. 301 | */ 302 | if ($this->getIdleTimeout() 303 | && $this->getIdleTimeout() < $allowedExecutionSeconds 304 | ) { 305 | $waitTimeout = $this->getIdleTimeout(); 306 | } else { 307 | $waitTimeout = $allowedExecutionSeconds; 308 | } 309 | } else { 310 | $waitTimeout = $this->getIdleTimeout(); 311 | } 312 | 313 | if (!is_null($this->getTimeoutWait()) && $this->getTimeoutWait() > 0) { 314 | $waitTimeout = min($waitTimeout, $this->getTimeoutWait()); 315 | } 316 | return $waitTimeout; 317 | } 318 | 319 | public function setLastActivityDateTime(\DateTime $dateTime) 320 | { 321 | $this->lastActivityDateTime = $dateTime; 322 | } 323 | 324 | protected function getLastActivityDateTime(): ?\DateTime 325 | { 326 | return $this->lastActivityDateTime; 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /RabbitMq/ConsumerInterface.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 | } -------------------------------------------------------------------------------- /RabbitMq/Exception/AckStopConsumerException.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, false, 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 | list($queueName, ,) = $this->getChannel()->queue_declare($name, $options['passive'], 77 | $options['durable'], $options['exclusive'], 78 | $options['auto_delete'], $options['nowait'], 79 | $options['arguments'], $options['ticket']); 80 | 81 | if (isset($options['routing_keys']) && count($options['routing_keys']) > 0) { 82 | foreach ($options['routing_keys'] as $routingKey) { 83 | $this->queueBind($queueName, $this->exchangeOptions['name'], $routingKey, $options['arguments'] ?? []); 84 | } 85 | } else { 86 | $this->queueBind($queueName, $this->exchangeOptions['name'], $this->routingKey, $options['arguments'] ?? []); 87 | } 88 | } 89 | 90 | $this->queueDeclared = true; 91 | } 92 | 93 | public function processQueueMessage($queueName, AMQPMessage $msg) 94 | { 95 | if (!isset($this->queues[$queueName])) { 96 | throw new QueueNotFoundException(); 97 | } 98 | 99 | $this->processMessageQueueCallback($msg, $queueName, $this->queues[$queueName]['callback']); 100 | } 101 | 102 | public function stopConsuming() 103 | { 104 | foreach ($this->queues as $name => $options) { 105 | $this->getChannel()->basic_cancel($this->getQueueConsumerTag($name), false, true); 106 | } 107 | } 108 | 109 | /** 110 | * Merges static and provided queues into one array 111 | */ 112 | protected function mergeQueues() 113 | { 114 | if ($this->queuesProvider) { 115 | $this->queues = array_merge( 116 | $this->queues, 117 | $this->queuesProvider->getQueues() 118 | ); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /RabbitMq/Producer.php: -------------------------------------------------------------------------------- 1 | contentType = $contentType; 19 | 20 | return $this; 21 | } 22 | 23 | public function setDeliveryMode($deliveryMode) 24 | { 25 | $this->deliveryMode = $deliveryMode; 26 | 27 | return $this; 28 | } 29 | 30 | protected function getBasicProperties() 31 | { 32 | return array('content_type' => $this->contentType, 'delivery_mode' => $this->deliveryMode); 33 | } 34 | 35 | /** 36 | * Publishes the message and merges additional properties with basic properties 37 | * 38 | * @param string $msgBody 39 | * @param string $routingKey 40 | * @param array $additionalProperties 41 | * @param array $headers 42 | */ 43 | public function publish($msgBody, $routingKey = '', $additionalProperties = array(), array $headers = null) 44 | { 45 | if ($this->autoSetupFabric) { 46 | $this->setupFabric(); 47 | } 48 | 49 | $msg = new AMQPMessage((string) $msgBody, array_merge($this->getBasicProperties(), $additionalProperties)); 50 | 51 | if (!empty($headers)) { 52 | $headersTable = new AMQPTable($headers); 53 | $msg->set('application_headers', $headersTable); 54 | } 55 | 56 | $this->getChannel()->basic_publish($msg, $this->exchangeOptions['name'], (string)$routingKey); 57 | $this->logger->debug('AMQP message published', array( 58 | 'amqp' => array( 59 | 'body' => $msgBody, 60 | 'routingkeys' => $routingKey, 61 | 'properties' => $additionalProperties, 62 | 'headers' => $headers 63 | ) 64 | )); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /RabbitMq/ProducerInterface.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 = array(); 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, array($this, 'processMessage')); 38 | } 39 | } 40 | 41 | $msg = new AMQPMessage($msgBody, array('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, array($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 | list($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 = array(); 125 | $this->requests = 0; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /RabbitMq/RpcServer.php: -------------------------------------------------------------------------------- 1 | setExchangeOptions(array('name' => $name, 'type' => 'direct')); 14 | $this->setQueueOptions(array('name' => $name . '-queue')); 15 | } 16 | 17 | public function processMessage(AMQPMessage $msg) 18 | { 19 | try { 20 | $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']); 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, array('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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Resources/meta/LICENSE.md: -------------------------------------------------------------------------------- 1 | The 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Tests/Command/BaseCommandTest.php: -------------------------------------------------------------------------------- 1 | application = $this->getMockBuilder('Symfony\\Component\\Console\\Application') 17 | ->disableOriginalConstructor() 18 | ->getMock(); 19 | $this->definition = $this->getMockBuilder('Symfony\\Component\\Console\\Input\\InputDefinition') 20 | ->disableOriginalConstructor() 21 | ->getMock(); 22 | $this->helperSet = $this->getMockBuilder('Symfony\\Component\\Console\\Helper\\HelperSet')->getMock(); 23 | 24 | $this->application->expects($this->any()) 25 | ->method('getDefinition') 26 | ->will($this->returnValue($this->definition)); 27 | $this->definition->expects($this->any()) 28 | ->method('getArguments') 29 | ->will($this->returnValue(array())); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/Command/ConsumerCommandTest.php: -------------------------------------------------------------------------------- 1 | definition->expects($this->any()) 14 | ->method('getOptions') 15 | ->will($this->returnValue(array( 16 | new InputOption('--verbose', '-v', InputOption::VALUE_NONE, 'Increase verbosity of messages.'), 17 | new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', 'dev'), 18 | new InputOption('--no-debug', null, InputOption::VALUE_NONE, 'Switches off debug mode.'), 19 | ))); 20 | $this->application->expects($this->once()) 21 | ->method('getHelperSet') 22 | ->will($this->returnValue($this->helperSet)); 23 | 24 | $this->command = new ConsumerCommand(); 25 | $this->command->setApplication($this->application); 26 | } 27 | 28 | /** 29 | * testInputsDefinitionCommand 30 | */ 31 | public function testInputsDefinitionCommand() 32 | { 33 | $definition = $this->command->getDefinition(); 34 | // check argument 35 | $this->assertTrue($definition->hasArgument('name')); 36 | $this->assertTrue($definition->getArgument('name')->isRequired()); // Name is required to find the service 37 | 38 | //check options 39 | $this->assertTrue($definition->hasOption('messages')); 40 | $this->assertTrue($definition->getOption('messages')->isValueOptional()); // It should accept value 41 | 42 | $this->assertTrue($definition->hasOption('route')); 43 | $this->assertTrue($definition->getOption('route')->isValueOptional()); // It should accept value 44 | 45 | $this->assertTrue($definition->hasOption('without-signals')); 46 | $this->assertFalse($definition->getOption('without-signals')->acceptValue()); // It shouldn't accept value because it is a true/false input 47 | 48 | $this->assertTrue($definition->hasOption('debug')); 49 | $this->assertFalse($definition->getOption('debug')->acceptValue()); // It shouldn't accept value because it is a true/false input 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Tests/Command/DynamicConsumerCommandTest.php: -------------------------------------------------------------------------------- 1 | definition->expects($this->any()) 15 | ->method('getOptions') 16 | ->will($this->returnValue(array( 17 | new InputOption('--verbose', '-v', InputOption::VALUE_NONE, 'Increase verbosity of messages.'), 18 | new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', 'dev'), 19 | new InputOption('--no-debug', null, InputOption::VALUE_NONE, 'Switches off debug mode.'), 20 | ))); 21 | $this->application->expects($this->once()) 22 | ->method('getHelperSet') 23 | ->will($this->returnValue($this->helperSet)); 24 | 25 | $this->command = new DynamicConsumerCommand(); 26 | $this->command->setApplication($this->application); 27 | } 28 | 29 | /** 30 | * testInputsDefinitionCommand 31 | */ 32 | public function testInputsDefinitionCommand() 33 | { 34 | // check argument 35 | $definition = $this->command->getDefinition(); 36 | $this->assertTrue($definition->hasArgument('name')); 37 | $this->assertTrue($definition->getArgument('name')->isRequired()); // Name is required to find the service 38 | 39 | $this->assertTrue($definition->hasArgument('context')); 40 | $this->assertTrue($definition->getArgument('context')->isRequired()); // Context is required for the queue options provider 41 | 42 | //check options 43 | $this->assertTrue($definition->hasOption('messages')); 44 | $this->assertTrue($definition->getOption('messages')->isValueOptional()); // It should accept value 45 | 46 | $this->assertTrue($definition->hasOption('route')); 47 | $this->assertTrue($definition->getOption('route')->isValueOptional()); // It should accept value 48 | 49 | $this->assertTrue($definition->hasOption('without-signals')); 50 | $this->assertFalse($definition->getOption('without-signals')->acceptValue()); // It shouldn't accept value because it is a true/false input 51 | 52 | $this->assertTrue($definition->hasOption('debug')); 53 | $this->assertFalse($definition->getOption('debug')->acceptValue()); // It shouldn't accept value because it is a true/false input 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Tests/Command/MultipleConsumerCommandTest.php: -------------------------------------------------------------------------------- 1 | definition->expects($this->any()) 15 | ->method('getOptions') 16 | ->will($this->returnValue(array( 17 | new InputOption('--verbose', '-v', InputOption::VALUE_NONE, 'Increase verbosity of messages.'), 18 | new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', 'dev'), 19 | new InputOption('--no-debug', null, InputOption::VALUE_NONE, 'Switches off debug mode.'), 20 | ))); 21 | $this->application->expects($this->once()) 22 | ->method('getHelperSet') 23 | ->will($this->returnValue($this->helperSet)); 24 | 25 | $this->command = new MultipleConsumerCommand(); 26 | $this->command->setApplication($this->application); 27 | } 28 | 29 | /** 30 | * testInputsDefinitionCommand 31 | */ 32 | public function testInputsDefinitionCommand() 33 | { 34 | // check argument 35 | $definition = $this->command->getDefinition(); 36 | $this->assertTrue($definition->hasArgument('name')); 37 | $this->assertTrue($definition->getArgument('name')->isRequired()); // Name is required to find the service 38 | 39 | $this->assertTrue($definition->hasArgument('context')); 40 | $this->assertFalse($definition->getArgument('context')->isRequired()); // Context is required for the queue options provider 41 | 42 | //check options 43 | $this->assertTrue($definition->hasOption('messages')); 44 | $this->assertTrue($definition->getOption('messages')->isValueOptional()); // It should accept value 45 | 46 | $this->assertTrue($definition->hasOption('route')); 47 | $this->assertTrue($definition->getOption('route')->isValueOptional()); // It should accept value 48 | 49 | $this->assertTrue($definition->hasOption('without-signals')); 50 | $this->assertFalse($definition->getOption('without-signals')->acceptValue()); // It shouldn't accept value because it is a true/false input 51 | 52 | $this->assertTrue($definition->hasOption('debug')); 53 | $this->assertFalse($definition->getOption('debug')->acceptValue()); // It shouldn't accept value because it is a true/false input 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Tests/Command/PurgeCommandTest.php: -------------------------------------------------------------------------------- 1 | definition->expects($this->any()) 14 | ->method('getOptions') 15 | ->will($this->returnValue(array( 16 | new InputOption('--no-confirmation', null, InputOption::VALUE_NONE, 'Switches off confirmation mode.'), 17 | ))); 18 | $this->application->expects($this->once()) 19 | ->method('getHelperSet') 20 | ->will($this->returnValue($this->helperSet)); 21 | 22 | $this->command = new PurgeConsumerCommand(); 23 | $this->command->setApplication($this->application); 24 | } 25 | 26 | /** 27 | * testInputsDefinitionCommand 28 | */ 29 | public function testInputsDefinitionCommand() 30 | { 31 | // check argument 32 | $definition = $this->command->getDefinition(); 33 | $this->assertTrue($definition->hasArgument('name')); 34 | $this->assertTrue($definition->getArgument('name')->isRequired()); // Name is required to find the service 35 | 36 | //check options 37 | $this->assertTrue($definition->hasOption('no-confirmation')); 38 | $this->assertFalse($definition->getOption('no-confirmation')->acceptValue()); // It shouldn't accept value because it is a true/false input 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/DependencyInjection/Fixtures/collector.yml: -------------------------------------------------------------------------------- 1 | old_sound_rabbit_mq: 2 | 3 | enable_collector: true 4 | 5 | connections: 6 | default: 7 | 8 | producers: 9 | default_producer: 10 | exchange_options: 11 | name: default_exchange 12 | type: direct 13 | 14 | consumers: 15 | default_consumer: 16 | exchange_options: 17 | name: default_exchange 18 | type: direct 19 | queue_options: 20 | name: default_queue 21 | callback: default.callback 22 | 23 | services: 24 | default.callback: 25 | -------------------------------------------------------------------------------- /Tests/DependencyInjection/Fixtures/collector_disabled.yml: -------------------------------------------------------------------------------- 1 | old_sound_rabbit_mq: 2 | 3 | enable_collector: false 4 | 5 | connections: 6 | default: 7 | 8 | producers: 9 | default_producer: 10 | exchange_options: 11 | name: default_exchange 12 | type: direct 13 | 14 | consumers: 15 | default_consumer: 16 | exchange_options: 17 | name: default_exchange 18 | type: direct 19 | queue_options: 20 | name: default_queue 21 | callback: default.callback 22 | services: 23 | default.callback: 24 | 25 | -------------------------------------------------------------------------------- /Tests/DependencyInjection/Fixtures/config_with_enable_logger.yml: -------------------------------------------------------------------------------- 1 | old_sound_rabbit_mq: 2 | 3 | enable_collector: true 4 | 5 | connections: 6 | default: 7 | 8 | producers: 9 | default_producer: 10 | exchange_options: 11 | name: default_exchange 12 | type: direct 13 | 14 | consumers: 15 | default_consumer: 16 | exchange_options: 17 | name: default_exchange 18 | type: direct 19 | queue_options: 20 | name: default_queue 21 | callback: default.callback 22 | enable_logger: true 23 | services: 24 | logger: 25 | class: \stdClass 26 | default.callback: 27 | 28 | -------------------------------------------------------------------------------- /Tests/DependencyInjection/Fixtures/exchange_arguments.yml: -------------------------------------------------------------------------------- 1 | old_sound_rabbit_mq: 2 | 3 | enable_collector: true 4 | 5 | connections: 6 | default: 7 | 8 | producers: 9 | producer: 10 | exchange_options: 11 | name: default_exchange 12 | type: direct 13 | arguments: {name: bar} 14 | 15 | consumers: 16 | consumer: 17 | exchange_options: 18 | name: default_exchange 19 | type: direct 20 | arguments: {name: bar} 21 | queue_options: 22 | name: foo_queue 23 | callback: consumer.callback 24 | services: 25 | consumer.callback: 26 | -------------------------------------------------------------------------------- /Tests/DependencyInjection/Fixtures/no_collector.yml: -------------------------------------------------------------------------------- 1 | old_sound_rabbit_mq: 2 | 3 | enable_collector: true 4 | 5 | connections: 6 | default: 7 | -------------------------------------------------------------------------------- /Tests/DependencyInjection/Fixtures/no_exchange_options.yml: -------------------------------------------------------------------------------- 1 | old_sound_rabbit_mq: 2 | 3 | connections: 4 | default: 5 | 6 | producers: 7 | producer: 8 | connection: default -------------------------------------------------------------------------------- /Tests/DependencyInjection/Fixtures/rpc-clients.yml: -------------------------------------------------------------------------------- 1 | old_sound_rabbit_mq: 2 | enable_collector: true 3 | connections: 4 | foo_connection: 5 | host: foo_host 6 | port: 123 7 | user: foo_user 8 | password: foo_password 9 | vhost: /foo 10 | default: 11 | rpc_clients: 12 | foo_client: 13 | connection: foo_connection 14 | unserializer: json_decode 15 | direct_reply_to: true 16 | lazy_client: 17 | connection: default 18 | lazy: true 19 | 20 | default_client: 21 | -------------------------------------------------------------------------------- /Tests/DependencyInjection/Fixtures/test.yml: -------------------------------------------------------------------------------- 1 | old_sound_rabbit_mq: 2 | 3 | enable_collector: true 4 | 5 | connections: 6 | foo_connection: 7 | host: foo_host 8 | port: 123 9 | user: foo_user 10 | password: foo_password 11 | vhost: /foo 12 | 13 | ssl_connection: 14 | host: ssl_host 15 | port: 123 16 | user: ssl_user 17 | password: ssl_password 18 | vhost: /ssl 19 | ssl_context: 20 | verify_peer: false 21 | 22 | lazy_connection: 23 | host: lazy_host 24 | port: 456 25 | user: lazy_user 26 | password: lazy_password 27 | vhost: /lazy 28 | lazy: true 29 | 30 | socket_connection: 31 | host: bar_host 32 | port: 789 33 | user: socket_user 34 | password: socket_password 35 | vhost: /socket 36 | lazy: false 37 | use_socket: true 38 | 39 | lazy_socket: 40 | host: joe_host 41 | port: 987 42 | user: lazy_socket_user 43 | password: lazy_socket_password 44 | vhost: /lazy_socket 45 | lazy: true 46 | use_socket: true 47 | 48 | default: 49 | default2: 50 | foo_default: 51 | bar_default: 52 | 53 | producers: 54 | foo_producer: 55 | class: My\Foo\Producer 56 | connection: foo_connection 57 | exchange_options: 58 | name: foo_exchange 59 | type: direct 60 | passive: true 61 | durable: false 62 | auto_delete: true 63 | internal: true 64 | nowait: true 65 | arguments: null 66 | ticket: null 67 | 68 | foo_producer_aliased: 69 | class: My\Foo\Producer 70 | connection: foo_connection 71 | exchange_options: 72 | name: foo_exchange 73 | type: direct 74 | passive: true 75 | durable: false 76 | auto_delete: true 77 | internal: true 78 | nowait: true 79 | arguments: null 80 | ticket: null 81 | service_alias: foo_producer_alias 82 | 83 | 84 | default_producer: 85 | exchange_options: 86 | name: default_exchange 87 | type: direct 88 | 89 | consumers: 90 | foo_consumer: 91 | connection: foo_connection 92 | timeout_wait: 3 93 | exchange_options: 94 | name: foo_exchange 95 | type: direct 96 | passive: true 97 | durable: false 98 | auto_delete: true 99 | internal: true 100 | nowait: true 101 | arguments: null 102 | ticket: null 103 | queue_options: 104 | name: foo_queue 105 | passive: true 106 | durable: false 107 | exclusive: true 108 | auto_delete: true 109 | nowait: true 110 | arguments: null 111 | ticket: null 112 | routing_keys: 113 | - 'android.#.upload' 114 | - 'iphone.upload' 115 | callback: foo.callback 116 | 117 | default_consumer: 118 | exchange_options: 119 | name: default_exchange 120 | type: direct 121 | queue_options: 122 | name: default_queue 123 | callback: default.callback 124 | 125 | qos_test_consumer: 126 | connection: foo_connection 127 | exchange_options: 128 | name: foo_exchange 129 | type: direct 130 | queue_options: 131 | name: foo_queue 132 | qos_options: 133 | prefetch_size: 1024 134 | prefetch_count: 1 135 | global: true 136 | callback: foo.callback 137 | 138 | multiple_consumers: 139 | multi_test_consumer: 140 | connection: foo_connection 141 | timeout_wait: 3 142 | exchange_options: 143 | name: foo_multiple_exchange 144 | type: direct 145 | queues: 146 | multi-test-1: 147 | name: multi_test_1 148 | callback: foo.multiple_test1.callback 149 | multi-test-2: 150 | name: foo_bar_2 151 | passive: true 152 | durable: false 153 | exclusive: true 154 | auto_delete: true 155 | nowait: true 156 | arguments: null 157 | ticket: null 158 | routing_keys: 159 | - 'android.upload' 160 | - 'iphone.upload' 161 | callback: foo.multiple_test2.callback 162 | queues_provider: foo.queues_provider 163 | 164 | dynamic_consumers: 165 | foo_dyn_consumer: 166 | connection: foo_default 167 | exchange_options: 168 | name: foo_dynamic_exchange 169 | type: direct 170 | callback: foo.dynamic.callback 171 | queue_options_provider: foo.dynamic.provider 172 | bar_dyn_consumer: 173 | connection: bar_default 174 | exchange_options: 175 | name: bar_dynamic_exchange 176 | type: direct 177 | callback: bar.dynamic.callback 178 | queue_options_provider: bar.dynamic.provider 179 | bindings: 180 | - {exchange: foo, destination: bar, routing_key: baz} 181 | - {exchange: moo, connection: default2, destination: cow, nowait: true, destination_is_exchange: true, arguments: {moo: cow}} 182 | anon_consumers: 183 | foo_anon_consumer: 184 | connection: foo_connection 185 | exchange_options: 186 | name: foo_anon_exchange 187 | type: direct 188 | passive: true 189 | durable: false 190 | auto_delete: true 191 | internal: true 192 | nowait: true 193 | arguments: null 194 | ticket: null 195 | callback: foo_anon.callback 196 | 197 | default_anon_consumer: 198 | exchange_options: 199 | name: default_anon_exchange 200 | type: direct 201 | callback: default_anon.callback 202 | 203 | rpc_clients: 204 | foo_client: 205 | connection: foo_connection 206 | unserializer: json_decode 207 | direct_reply_to: true 208 | 209 | default_client: 210 | 211 | rpc_servers: 212 | foo_server: 213 | connection: foo_connection 214 | callback: foo_server.callback 215 | serializer: json_encode 216 | 217 | default_server: 218 | callback: default_server.callback 219 | 220 | server_with_queue_options: 221 | callback: server_with_queue_options.callback 222 | queue_options: 223 | name: "server_with_queue_options-queue" 224 | 225 | server_with_exchange_options: 226 | callback: server_with_exchange_options.callback 227 | exchange_options: 228 | name: exchange 229 | type: topic 230 | services: 231 | foo.callback: 232 | default.callback: 233 | foo.multiple_test1.callback: 234 | foo.multiple_test2.callback: 235 | foo.queues_provider: 236 | foo.dynamic.callback: 237 | foo.dynamic.provider: 238 | bar.dynamic.provider: 239 | bar.dynamic.callback: 240 | foo_anon.callback: 241 | default_anon.callback: 242 | foo_server.callback: 243 | default_server.callback: 244 | server_with_queue_options.callback: 245 | server_with_exchange_options.callback: 246 | -------------------------------------------------------------------------------- /Tests/Event/AfterProcessingMessageEventTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('\PhpAmqpLib\Connection\AMQPStreamConnection') 21 | ->disableOriginalConstructor() 22 | ->getMock(), 23 | $this->getMockBuilder('\PhpAmqpLib\Channel\AMQPChannel') 24 | ->disableOriginalConstructor() 25 | ->getMock() 26 | ); 27 | } 28 | 29 | public function testEvent() 30 | { 31 | $AMQPMessage = new AMQPMessage('body'); 32 | $consumer = $this->getConsumer(); 33 | $event = new AfterProcessingMessageEvent($consumer, $AMQPMessage); 34 | $this->assertSame($AMQPMessage, $event->getAMQPMessage()); 35 | $this->assertSame($consumer, $event->getConsumer()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/Event/BeforeProcessingMessageEventTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('\PhpAmqpLib\Connection\AMQPStreamConnection') 21 | ->disableOriginalConstructor() 22 | ->getMock(), 23 | $this->getMockBuilder('\PhpAmqpLib\Channel\AMQPChannel') 24 | ->disableOriginalConstructor() 25 | ->getMock() 26 | ); 27 | } 28 | 29 | public function testEvent() 30 | { 31 | $AMQPMessage = new AMQPMessage('body'); 32 | $consumer = $this->getConsumer(); 33 | $event = new BeforeProcessingMessageEvent($consumer, $AMQPMessage); 34 | $this->assertSame($AMQPMessage, $event->getAMQPMessage()); 35 | $this->assertSame($consumer, $event->getConsumer()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/Event/OnIdleEventTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('\PhpAmqpLib\Connection\AMQPStreamConnection') 20 | ->disableOriginalConstructor() 21 | ->getMock(), 22 | $this->getMockBuilder('\PhpAmqpLib\Channel\AMQPChannel') 23 | ->disableOriginalConstructor() 24 | ->getMock() 25 | ); 26 | } 27 | 28 | public function testShouldAllowGetConsumerSetInConstructor() 29 | { 30 | $consumer = $this->getConsumer(); 31 | $event = new OnIdleEvent($consumer); 32 | 33 | $this->assertSame($consumer, $event->getConsumer()); 34 | } 35 | 36 | public function testShouldSetForceStopToTrueInConstructor() 37 | { 38 | $consumer = $this->getConsumer(); 39 | $event = new OnIdleEvent($consumer); 40 | 41 | $this->assertTrue($event->isForceStop()); 42 | } 43 | 44 | public function testShouldReturnPreviouslySetForceStop() 45 | { 46 | $consumer = $this->getConsumer(); 47 | $event = new OnIdleEvent($consumer); 48 | 49 | //guard 50 | $this->assertTrue($event->isForceStop()); 51 | 52 | $event->setForceStop(false); 53 | $this->assertFalse($event->isForceStop()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Tests/Manager/MemoryConsumptionCheckerTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('OldSound\\RabbitMqBundle\\MemoryChecker\\NativeMemoryUsageProvider')->getMock(); 23 | $memoryUsageProvider->expects($this->any())->method('getMemoryUsage')->willReturn($currentMemoryUsage); 24 | 25 | $memoryManager = new MemoryConsumptionChecker($memoryUsageProvider); 26 | 27 | $this->assertFalse($memoryManager->isRamAlmostOverloaded($maxConsumptionAllowed, $allowedConsumptionUntil)); 28 | } 29 | 30 | public function testMemoryIsAlmostOverloaded() 31 | { 32 | $currentMemoryUsage = '9M'; 33 | $allowedConsumptionUntil = '2M'; 34 | $maxConsumptionAllowed = '10M'; 35 | 36 | $memoryUsageProvider = $this->getMockBuilder('OldSound\\RabbitMqBundle\\MemoryChecker\\NativeMemoryUsageProvider')->getMock(); 37 | $memoryUsageProvider->expects($this->any())->method('getMemoryUsage')->willReturn($currentMemoryUsage); 38 | 39 | $memoryManager = new MemoryConsumptionChecker($memoryUsageProvider); 40 | 41 | $this->assertTrue($memoryManager->isRamAlmostOverloaded($maxConsumptionAllowed, $allowedConsumptionUntil)); 42 | } 43 | 44 | public function testMemoryExactValueIsNotAlmostOverloaded() 45 | { 46 | $currentMemoryUsage = '7M'; 47 | $maxConsumptionAllowed = '10M'; 48 | 49 | $memoryUsageProvider = $this->getMockBuilder('OldSound\\RabbitMqBundle\\MemoryChecker\\NativeMemoryUsageProvider')->getMock(); 50 | $memoryUsageProvider->expects($this->any())->method('getMemoryUsage')->willReturn($currentMemoryUsage); 51 | 52 | $memoryManager = new MemoryConsumptionChecker($memoryUsageProvider); 53 | 54 | $this->assertFalse($memoryManager->isRamAlmostOverloaded($maxConsumptionAllowed)); 55 | } 56 | 57 | public function testMemoryExactValueIsAlmostOverloaded() 58 | { 59 | $currentMemoryUsage = '11M'; 60 | $maxConsumptionAllowed = '10M'; 61 | 62 | $memoryUsageProvider = $this->getMockBuilder('OldSound\\RabbitMqBundle\\MemoryChecker\\NativeMemoryUsageProvider')->getMock(); 63 | $memoryUsageProvider->expects($this->any())->method('getMemoryUsage')->willReturn($currentMemoryUsage); 64 | 65 | $memoryManager = new MemoryConsumptionChecker($memoryUsageProvider); 66 | 67 | $this->assertTrue($memoryManager->isRamAlmostOverloaded($maxConsumptionAllowed)); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/RabbitMq/AMQPConnectionFactoryTest.php: -------------------------------------------------------------------------------- 1 | createConnection(); 23 | $this->assertInstanceOf('OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPConnection', $instance); 24 | $this->assertEquals(array( 25 | 'localhost', // host 26 | 5672, // port 27 | 'guest', // user 28 | 'guest', // password 29 | '/', // vhost 30 | false, // insist 31 | "AMQPLAIN", // login method 32 | null, // login response 33 | "en_US", // locale 34 | 3, // connection timeout 35 | 3, // read write timeout 36 | null, // context 37 | false, // keepalive 38 | 0, // heartbeat 39 | ), $instance->constructParams); 40 | } 41 | 42 | public function testSocketConnection() 43 | { 44 | $factory = new AMQPConnectionFactory( 45 | 'OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPSocketConnection', 46 | array() 47 | ); 48 | 49 | /** @var AMQPSocketConnection $instance */ 50 | $instance = $factory->createConnection(); 51 | $this->assertInstanceOf('PhpAmqpLib\Connection\AMQPSocketConnection', $instance); 52 | $this->assertEquals(array( 53 | 'localhost', // host 54 | 5672, // port 55 | 'guest', // user 56 | 'guest', // password 57 | '/', // vhost 58 | false, // insist 59 | "AMQPLAIN", // login method 60 | null, // login response 61 | "en_US", // locale 62 | 3, // read_timeout 63 | false, // keepalive 64 | 3, // write_timeout 65 | 0, // heartbeat 66 | ), $instance->constructParams); 67 | } 68 | 69 | public function testSocketConnectionWithParams() 70 | { 71 | $factory = new AMQPConnectionFactory( 72 | 'OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPSocketConnection', 73 | array( 74 | 'read_timeout' => 31, 75 | 'write_timeout' => 32, 76 | ) 77 | ); 78 | 79 | /** @var AMQPSocketConnection $instance */ 80 | $instance = $factory->createConnection(); 81 | $this->assertInstanceOf('PhpAmqpLib\Connection\AMQPSocketConnection', $instance); 82 | $this->assertEquals(array( 83 | 'localhost', // host 84 | 5672, // port 85 | 'guest', // user 86 | 'guest', // password 87 | '/', // vhost 88 | false, // insist 89 | "AMQPLAIN", // login method 90 | null, // login response 91 | "en_US", // locale 92 | 31, // read_timeout 93 | false, // keepalive 94 | 32, // write_timeout 95 | 0, // heartbeat 96 | ), $instance->constructParams); 97 | } 98 | 99 | public function testStandardConnectionParameters() 100 | { 101 | $factory = new AMQPConnectionFactory( 102 | 'OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPConnection', 103 | array( 104 | 'host' => 'foo_host', 105 | 'port' => 123, 106 | 'user' => 'foo_user', 107 | 'password' => 'foo_password', 108 | 'vhost' => '/vhost', 109 | ) 110 | ); 111 | 112 | /** @var AMQPConnection $instance */ 113 | $instance = $factory->createConnection(); 114 | $this->assertInstanceOf('OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPConnection', $instance); 115 | $this->assertEquals(array( 116 | 'foo_host', // host 117 | 123, // port 118 | 'foo_user', // user 119 | 'foo_password', // password 120 | '/vhost', // vhost 121 | false, // insist 122 | "AMQPLAIN", // login method 123 | null, // login response 124 | "en_US", // locale 125 | 3, // connection timeout 126 | 3, // read write timeout 127 | null, // context 128 | false, // keepalive 129 | 0, // heartbeat 130 | ), $instance->constructParams); 131 | } 132 | 133 | public function testSetConnectionParametersWithUrl() 134 | { 135 | $factory = new AMQPConnectionFactory( 136 | 'OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPConnection', 137 | array( 138 | 'url' => 'amqp://bar_user:bar_password@bar_host:321/whost?keepalive=1&connection_timeout=6&read_write_timeout=6', 139 | 'host' => 'foo_host', 140 | 'port' => 123, 141 | 'user' => 'foo_user', 142 | 'password' => 'foo_password', 143 | 'vhost' => '/vhost', 144 | ) 145 | ); 146 | 147 | /** @var AMQPConnection $instance */ 148 | $instance = $factory->createConnection(); 149 | $this->assertInstanceOf('OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPConnection', $instance); 150 | $this->assertEquals(array( 151 | 'bar_host', // host 152 | 321, // port 153 | 'bar_user', // user 154 | 'bar_password', // password 155 | 'whost', // vhost 156 | false, // insist 157 | "AMQPLAIN", // login method 158 | null, // login response 159 | "en_US", // locale 160 | 6, // connection timeout 161 | 6, // read write timeout 162 | null, // context 163 | true, // keepalive 164 | 0, // heartbeat 165 | ), $instance->constructParams); 166 | } 167 | 168 | public function testSetConnectionParametersWithUrlEncoded() 169 | { 170 | $factory = new AMQPConnectionFactory( 171 | 'OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPConnection', 172 | array( 173 | 'url' => 'amqp://user%61:%61pass@ho%61st:10000/v%2fhost?keepalive=1&connection_timeout=6&read_write_timeout=6', 174 | ) 175 | ); 176 | 177 | /** @var AMQPConnection $instance */ 178 | $instance = $factory->createConnection(); 179 | $this->assertInstanceOf('OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPConnection', $instance); 180 | $this->assertEquals(array( 181 | 'hoast', // host 182 | 10000, // port 183 | 'usera', // user 184 | 'apass', // password 185 | 'v/host', // vhost 186 | false, // insist 187 | "AMQPLAIN", // login method 188 | null, // login response 189 | "en_US", // locale 190 | 6, // connection timeout 191 | 6, // read write timeout 192 | null, // context 193 | true, // keepalive 194 | 0, // heartbeat 195 | ), $instance->constructParams); 196 | } 197 | 198 | public function testSetConnectionParametersWithUrlWithoutVhost() 199 | { 200 | $factory = new AMQPConnectionFactory( 201 | 'OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPConnection', 202 | array( 203 | 'url' => 'amqp://user:pass@host:321/?keepalive=1&connection_timeout=6&read_write_timeout=6', 204 | ) 205 | ); 206 | 207 | /** @var AMQPConnection $instance */ 208 | $instance = $factory->createConnection(); 209 | $this->assertInstanceOf('OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPConnection', $instance); 210 | $this->assertEquals(array( 211 | 'host', // host 212 | 321, // port 213 | 'user', // user 214 | 'pass', // password 215 | '', // vhost 216 | false, // insist 217 | "AMQPLAIN", // login method 218 | null, // login response 219 | "en_US", // locale 220 | 6, // connection timeout 221 | 6, // read write timeout 222 | null, // context 223 | true, // keepalive 224 | 0, // heartbeat 225 | ), $instance->constructParams); 226 | } 227 | 228 | public function testSSLConnectionParameters() 229 | { 230 | $factory = new AMQPConnectionFactory( 231 | 'OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPConnection', 232 | array( 233 | 'host' => 'ssl_host', 234 | 'port' => 123, 235 | 'user' => 'ssl_user', 236 | 'password' => 'ssl_password', 237 | 'vhost' => '/ssl', 238 | 'ssl_context' => array( 239 | 'verify_peer' => false, 240 | ), 241 | ) 242 | ); 243 | 244 | /** @var AMQPConnection $instance */ 245 | $instance = $factory->createConnection(); 246 | $this->assertInstanceOf('OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPConnection', $instance); 247 | $this->assertArrayHasKey(11, $instance->constructParams); 248 | $context = $instance->constructParams[11]; 249 | // unset to check whole array at once later 250 | $instance->constructParams[11] = null; 251 | $this->assertIsResource($context); 252 | $this->assertEquals('stream-context', get_resource_type($context)); 253 | $options = stream_context_get_options($context); 254 | $this->assertEquals(array('ssl' => array('verify_peer' => false)), $options); 255 | $this->assertEquals(array( 256 | 'ssl_host', // host 257 | 123, // port 258 | 'ssl_user', // user 259 | 'ssl_password', // password 260 | '/ssl', // vhost 261 | false, // insist 262 | "AMQPLAIN", // login method 263 | null, // login response 264 | "en_US", // locale 265 | 3, // connection timeout 266 | 3, // read write timeout 267 | null, // context checked earlier 268 | false, // keepalive 269 | 0, // heartbeat 270 | ), $instance->constructParams); 271 | } 272 | 273 | public function testConnectionsParametersProviderWithConstructorArgs() 274 | { 275 | $connectionParametersProvider = $this->prepareConnectionParametersProvider(); 276 | $connectionParametersProvider->expects($this->once()) 277 | ->method('getConnectionParameters') 278 | ->will($this->returnValue( 279 | array( 280 | 'constructor_args' => array(1,2,3,4) 281 | ) 282 | )); 283 | $factory = new AMQPConnectionFactory( 284 | 'OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPConnection', 285 | array(), 286 | $connectionParametersProvider 287 | ); 288 | 289 | /** @var AMQPConnection $instance */ 290 | $instance = $factory->createConnection(); 291 | $this->assertInstanceOf('OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPConnection', $instance); 292 | $this->assertEquals(array(1,2,3,4), $instance->constructParams); 293 | } 294 | 295 | public function testConnectionsParametersProvider() 296 | { 297 | $connectionParametersProvider = $this->prepareConnectionParametersProvider(); 298 | $connectionParametersProvider->expects($this->once()) 299 | ->method('getConnectionParameters') 300 | ->will($this->returnValue( 301 | array( 302 | 'host' => '1.2.3.4', 303 | 'port' => 5678, 304 | 'user' => 'admin', 305 | 'password' => 'admin', 306 | 'vhost' => 'foo', 307 | ) 308 | )); 309 | $factory = new AMQPConnectionFactory( 310 | 'OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPConnection', 311 | array(), 312 | $connectionParametersProvider 313 | ); 314 | 315 | /** @var AMQPConnection $instance */ 316 | $instance = $factory->createConnection(); 317 | $this->assertInstanceOf('OldSound\RabbitMqBundle\Tests\RabbitMq\Fixtures\AMQPConnection', $instance); 318 | $this->assertEquals(array( 319 | '1.2.3.4', // host 320 | 5678, // port 321 | 'admin', // user 322 | 'admin', // password 323 | 'foo', // vhost 324 | false, // insist 325 | "AMQPLAIN", // login method 326 | null, // login response 327 | "en_US", // locale 328 | 3, // connection timeout 329 | 3, // read write timeout 330 | null, // context 331 | false, // keepalive 332 | 0, // heartbeat 333 | ), $instance->constructParams); 334 | } 335 | 336 | /** 337 | * Preparing ConnectionParametersProviderInterface instance 338 | * 339 | * @return ConnectionParametersProviderInterface|MockObject 340 | */ 341 | private function prepareConnectionParametersProvider() 342 | { 343 | return $this->getMockBuilder('OldSound\RabbitMqBundle\Provider\ConnectionParametersProviderInterface') 344 | ->getMock(); 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /Tests/RabbitMq/BaseAmqpTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('PhpAmqpLib\Connection\AbstractConnection') 18 | ->disableOriginalConstructor() 19 | ->getMock(); 20 | 21 | $connection 22 | ->method('connectOnConstruct') 23 | ->willReturn(false); 24 | $connection 25 | ->expects(static::never()) 26 | ->method('channel'); 27 | 28 | new Consumer($connection, null); 29 | } 30 | 31 | public function testNotLazyConnection() 32 | { 33 | $connection = $this->getMockBuilder('PhpAmqpLib\Connection\AbstractConnection') 34 | ->disableOriginalConstructor() 35 | ->getMock(); 36 | 37 | $connection 38 | ->method('connectOnConstruct') 39 | ->willReturn(true); 40 | $connection 41 | ->expects(static::once()) 42 | ->method('channel'); 43 | 44 | new Consumer($connection, null); 45 | } 46 | 47 | public function testDispatchEvent() 48 | { 49 | /** @var BaseAmqp|MockObject $baseAmqpConsumer */ 50 | $baseAmqpConsumer = $this->getMockBuilder('OldSound\RabbitMqBundle\RabbitMq\BaseAmqp') 51 | ->disableOriginalConstructor() 52 | ->getMock(); 53 | 54 | $eventDispatcher = $this->getMockBuilder('Symfony\Contracts\EventDispatcher\EventDispatcherInterface') 55 | ->disableOriginalConstructor() 56 | ->getMock(); 57 | 58 | $baseAmqpConsumer->expects($this->atLeastOnce()) 59 | ->method('getEventDispatcher') 60 | ->willReturn($eventDispatcher); 61 | 62 | $eventDispatcher->expects($this->once()) 63 | ->method('dispatch') 64 | ->with(new AMQPEvent(), AMQPEvent::ON_CONSUME) 65 | ->willReturn(new AMQPEvent()); 66 | 67 | $this->invokeMethod('dispatchEvent', $baseAmqpConsumer, array(AMQPEvent::ON_CONSUME, new AMQPEvent())); 68 | } 69 | 70 | /** 71 | * @param string $name 72 | * @param MockObject $obj 73 | * @param array $params 74 | * 75 | * @return mixed 76 | */ 77 | protected function invokeMethod($name, $obj, $params) 78 | { 79 | $class = new \ReflectionClass(get_class($obj)); 80 | $method = $class->getMethod($name); 81 | $method->setAccessible(true); 82 | 83 | return $method->invokeArgs($obj, $params); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Tests/RabbitMq/BaseConsumerTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('\PhpAmqpLib\Connection\AMQPStreamConnection') 16 | ->disableOriginalConstructor() 17 | ->getMock(); 18 | 19 | $this->consumer = $this->getMockBuilder('\OldSound\RabbitMqBundle\RabbitMq\BaseConsumer') 20 | ->setConstructorArgs(array($amqpConnection)) 21 | ->getMockForAbstractClass(); 22 | } 23 | 24 | public function testItExtendsBaseAmqpInterface() 25 | { 26 | $this->assertInstanceOf('OldSound\RabbitMqBundle\RabbitMq\BaseAmqp', $this->consumer); 27 | } 28 | 29 | public function testItImplementsDequeuerInterface() 30 | { 31 | $this->assertInstanceOf('OldSound\RabbitMqBundle\RabbitMq\DequeuerInterface', $this->consumer); 32 | } 33 | 34 | public function testItsIdleTimeoutIsMutable() 35 | { 36 | $this->assertEquals(0, $this->consumer->getIdleTimeout()); 37 | $this->consumer->setIdleTimeout(42); 38 | $this->assertEquals(42, $this->consumer->getIdleTimeout()); 39 | } 40 | 41 | public function testItsIdleTimeoutExitCodeIsMutable() 42 | { 43 | $this->assertEquals(0, $this->consumer->getIdleTimeoutExitCode()); 44 | $this->consumer->setIdleTimeoutExitCode(43); 45 | $this->assertEquals(43, $this->consumer->getIdleTimeoutExitCode()); 46 | } 47 | 48 | public function testForceStopConsumer() 49 | { 50 | $this->assertAttributeEquals(false, 'forceStop', $this->consumer); 51 | $this->consumer->forceStopConsumer(); 52 | $this->assertAttributeEquals(true, 'forceStop', $this->consumer); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/RabbitMq/BindingTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('\PhpAmqpLib\Connection\AMQPStreamConnection') 24 | ->disableOriginalConstructor() 25 | ->getMock(); 26 | } 27 | 28 | protected function prepareAMQPChannel($channelId = null) 29 | { 30 | $channelMock = $this->getMockBuilder('\PhpAmqpLib\Channel\AMQPChannel') 31 | ->disableOriginalConstructor() 32 | ->getMock(); 33 | 34 | $channelMock->expects($this->any()) 35 | ->method('getChannelId') 36 | ->willReturn($channelId); 37 | return $channelMock; 38 | } 39 | 40 | public function testQueueBind() 41 | { 42 | $ch = $this->prepareAMQPChannel('channel_id'); 43 | $con = $this->prepareAMQPConnection(); 44 | 45 | $source = 'example_source'; 46 | $destination = 'example_destination'; 47 | $key = 'example_key'; 48 | $ch->expects($this->once()) 49 | ->method('queue_bind') 50 | ->will($this->returnCallback(function ($d, $s, $k, $n, $a) use ($destination, $source, $key) { 51 | Assert::assertSame($destination, $d); 52 | Assert::assertSame($source, $s); 53 | Assert::assertSame($key, $k); 54 | Assert::assertFalse($n); 55 | Assert::assertNull($a); 56 | })); 57 | 58 | $binding = $this->getBinding($con, $ch); 59 | $binding->setExchange($source); 60 | $binding->setDestination($destination); 61 | $binding->setRoutingKey($key); 62 | $binding->setupFabric(); 63 | } 64 | 65 | public function testExhangeBind() 66 | { 67 | $ch = $this->prepareAMQPChannel('channel_id'); 68 | $con = $this->prepareAMQPConnection(); 69 | 70 | $source = 'example_source'; 71 | $destination = 'example_destination'; 72 | $key = 'example_key'; 73 | $ch->expects($this->once()) 74 | ->method('exchange_bind') 75 | ->will($this->returnCallback(function ($d, $s, $k, $n, $a) use ($destination, $source, $key) { 76 | Assert::assertSame($destination, $d); 77 | Assert::assertSame($source, $s); 78 | Assert::assertSame($key, $k); 79 | Assert::assertFalse($n); 80 | Assert::assertNull($a); 81 | })); 82 | 83 | $binding = $this->getBinding($con, $ch); 84 | $binding->setExchange($source); 85 | $binding->setDestination($destination); 86 | $binding->setRoutingKey($key); 87 | $binding->setDestinationIsExchange(true); 88 | $binding->setupFabric(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Tests/RabbitMq/DynamicConsumerTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('\OldSound\RabbitMqBundle\Provider\QueueOptionsProviderInterface') 24 | ->getMock(); 25 | } 26 | 27 | public function testQueueOptionsPrivider() 28 | { 29 | $amqpConnection = $this->prepareAMQPConnection(); 30 | $amqpChannel = $this->prepareAMQPChannel(); 31 | $consumer = $this->getConsumer($amqpConnection, $amqpChannel); 32 | $consumer->setContext('foo'); 33 | 34 | $queueOptionsProvider = $this->prepareQueueOptionsProvider(); 35 | $queueOptionsProvider->expects($this->once()) 36 | ->method('getQueueOptions') 37 | ->will($this->returnValue( 38 | array( 39 | 'name' => 'queue_foo', 40 | 'routing_keys' => array( 41 | 'foo.*' 42 | ) 43 | ) 44 | )); 45 | 46 | $consumer->setQueueOptionsProvider($queueOptionsProvider); 47 | 48 | $reflectionClass = new \ReflectionClass(get_class($consumer)); 49 | $reflectionMethod = $reflectionClass->getMethod('mergeQueueOptions'); 50 | $reflectionMethod->setAccessible(true); 51 | $reflectionMethod->invoke($consumer); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Tests/RabbitMq/Fixtures/AMQPConnection.php: -------------------------------------------------------------------------------- 1 | constructParams = func_get_args(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Tests/RabbitMq/Fixtures/AMQPSocketConnection.php: -------------------------------------------------------------------------------- 1 | constructParams = func_get_args(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Tests/RabbitMq/MultipleConsumerTest.php: -------------------------------------------------------------------------------- 1 | amqpConnection = $this->prepareAMQPConnection(); 46 | $this->amqpChannel = $this->prepareAMQPChannel(); 47 | $this->multipleConsumer = new MultipleConsumer($this->amqpConnection, $this->amqpChannel); 48 | } 49 | 50 | /** 51 | * Check if the message is requeued or not correctly. 52 | * 53 | * @dataProvider processMessageProvider 54 | */ 55 | public function testProcessMessage($processFlag, $expectedMethod, $expectedRequeue = null) 56 | { 57 | $callback = $this->prepareCallback($processFlag); 58 | 59 | $this->multipleConsumer->setQueues( 60 | array( 61 | 'test-1' => array('callback' => $callback), 62 | 'test-2' => array('callback' => $callback) 63 | ) 64 | ); 65 | 66 | $this->prepareAMQPChannelExpectations($expectedMethod, $expectedRequeue); 67 | 68 | // Create a default message 69 | $amqpMessage = new AMQPMessage('foo body'); 70 | $amqpMessage->delivery_info['channel'] = $this->amqpChannel; 71 | $amqpMessage->delivery_info['delivery_tag'] = 0; 72 | 73 | $this->multipleConsumer->processQueueMessage('test-1', $amqpMessage); 74 | $this->multipleConsumer->processQueueMessage('test-2', $amqpMessage); 75 | } 76 | 77 | /** 78 | * Check queues provider works well 79 | * 80 | * @dataProvider processMessageProvider 81 | */ 82 | public function testQueuesProvider($processFlag, $expectedMethod, $expectedRequeue = null) 83 | { 84 | $callback = $this->prepareCallback($processFlag); 85 | 86 | $queuesProvider = $this->prepareQueuesProvider(); 87 | $queuesProvider->expects($this->once()) 88 | ->method('getQueues') 89 | ->will($this->returnValue( 90 | array( 91 | 'test-1' => array('callback' => $callback), 92 | 'test-2' => array('callback' => $callback) 93 | ) 94 | )); 95 | 96 | $this->multipleConsumer->setQueuesProvider($queuesProvider); 97 | 98 | /** 99 | * We don't test consume method, which merges queues by calling $this->setupConsumer(); 100 | * So we need to invoke it manually 101 | */ 102 | $reflectionClass = new \ReflectionClass(get_class($this->multipleConsumer)); 103 | $reflectionMethod = $reflectionClass->getMethod('mergeQueues'); 104 | $reflectionMethod->setAccessible(true); 105 | $reflectionMethod->invoke($this->multipleConsumer); 106 | 107 | $this->prepareAMQPChannelExpectations($expectedMethod, $expectedRequeue); 108 | 109 | // Create a default message 110 | $amqpMessage = new AMQPMessage('foo body'); 111 | $amqpMessage->delivery_info['channel'] = $this->amqpChannel; 112 | $amqpMessage->delivery_info['delivery_tag'] = 0; 113 | 114 | $this->multipleConsumer->processQueueMessage('test-1', $amqpMessage); 115 | $this->multipleConsumer->processQueueMessage('test-2', $amqpMessage); 116 | } 117 | 118 | public function testQueuesPrivider() 119 | { 120 | $amqpConnection = $this->prepareAMQPConnection(); 121 | $amqpChannel = $this->prepareAMQPChannel(); 122 | $this->multipleConsumer->setContext('foo'); 123 | 124 | $queuesProvider = $this->prepareQueuesProvider(); 125 | $queuesProvider->expects($this->once()) 126 | ->method('getQueues') 127 | ->will($this->returnValue( 128 | array( 129 | 'queue_foo' => array() 130 | ) 131 | )); 132 | 133 | $this->multipleConsumer->setQueuesProvider($queuesProvider); 134 | 135 | $reflectionClass = new \ReflectionClass(get_class($this->multipleConsumer)); 136 | $reflectionMethod = $reflectionClass->getMethod('mergeQueues'); 137 | $reflectionMethod->setAccessible(true); 138 | $reflectionMethod->invoke($this->multipleConsumer); 139 | } 140 | 141 | /** 142 | * Check queues provider works well with static queues together 143 | * 144 | * @dataProvider processMessageProvider 145 | */ 146 | public function testQueuesProviderAndStaticQueuesTogether($processFlag, $expectedMethod, $expectedRequeue = null) 147 | { 148 | $callback = $this->prepareCallback($processFlag); 149 | 150 | $this->multipleConsumer->setQueues( 151 | array( 152 | 'test-1' => array('callback' => $callback), 153 | 'test-2' => array('callback' => $callback) 154 | ) 155 | ); 156 | 157 | $queuesProvider = $this->prepareQueuesProvider(); 158 | $queuesProvider->expects($this->once()) 159 | ->method('getQueues') 160 | ->will($this->returnValue( 161 | array( 162 | 'test-3' => array('callback' => $callback), 163 | 'test-4' => array('callback' => $callback) 164 | ) 165 | )); 166 | 167 | $this->multipleConsumer->setQueuesProvider($queuesProvider); 168 | 169 | /** 170 | * We don't test consume method, which merges queues by calling $this->setupConsumer(); 171 | * So we need to invoke it manually 172 | */ 173 | $reflectionClass = new \ReflectionClass(get_class($this->multipleConsumer)); 174 | $reflectionMethod = $reflectionClass->getMethod('mergeQueues'); 175 | $reflectionMethod->setAccessible(true); 176 | $reflectionMethod->invoke($this->multipleConsumer); 177 | 178 | $this->prepareAMQPChannelExpectations($expectedMethod, $expectedRequeue); 179 | 180 | // Create a default message 181 | $amqpMessage = new AMQPMessage('foo body'); 182 | $amqpMessage->delivery_info['channel'] = $this->amqpChannel; 183 | $amqpMessage->delivery_info['delivery_tag'] = 0; 184 | 185 | $this->multipleConsumer->processQueueMessage('test-1', $amqpMessage); 186 | $this->multipleConsumer->processQueueMessage('test-2', $amqpMessage); 187 | $this->multipleConsumer->processQueueMessage('test-3', $amqpMessage); 188 | $this->multipleConsumer->processQueueMessage('test-4', $amqpMessage); 189 | } 190 | 191 | public function processMessageProvider() 192 | { 193 | return array( 194 | array(null, 'basic_ack'), // Remove message from queue only if callback return not false 195 | array(true, 'basic_ack'), // Remove message from queue only if callback return not false 196 | array(false, 'basic_reject', true), // Reject and requeue message to RabbitMQ 197 | array(ConsumerInterface::MSG_ACK, 'basic_ack'), // Remove message from queue only if callback return not false 198 | array(ConsumerInterface::MSG_REJECT_REQUEUE, 'basic_reject', true), // Reject and requeue message to RabbitMQ 199 | array(ConsumerInterface::MSG_REJECT, 'basic_reject', false), // Reject and drop 200 | ); 201 | } 202 | 203 | /** 204 | * @dataProvider queueBindingRoutingKeyProvider 205 | */ 206 | public function testShouldConsiderQueueArgumentsOnQueueDeclaration($routingKeysOption, $expectedRoutingKey) 207 | { 208 | $queueName = 'test-queue-name'; 209 | $exchangeName = 'test-exchange-name'; 210 | $expectedArgs = ['test-argument' => ['S', 'test-value']]; 211 | 212 | $this->amqpChannel->expects($this->any()) 213 | ->method('getChannelId')->willReturn(0); 214 | 215 | $this->amqpChannel->expects($this->any()) 216 | ->method('queue_declare') 217 | ->willReturn([$queueName, 5, 0]); 218 | 219 | 220 | $this->multipleConsumer->setExchangeOptions([ 221 | 'declare' => false, 222 | 'name' => $exchangeName, 223 | 'type' => 'topic']); 224 | 225 | $this->multipleConsumer->setQueues([ 226 | $queueName => [ 227 | 'passive' => true, 228 | 'durable' => true, 229 | 'exclusive' => true, 230 | 'auto_delete' => true, 231 | 'nowait' => true, 232 | 'arguments' => $expectedArgs, 233 | 'ticket' => null, 234 | 'routing_keys' => $routingKeysOption] 235 | ]); 236 | 237 | $this->multipleConsumer->setRoutingKey('test-routing-key'); 238 | 239 | // we assert that arguments are passed to the bind method 240 | $this->amqpChannel->expects($this->once()) 241 | ->method('queue_bind') 242 | ->with($queueName, $exchangeName, $expectedRoutingKey, false, $expectedArgs); 243 | 244 | $this->multipleConsumer->setupFabric(); 245 | } 246 | 247 | public function queueBindingRoutingKeyProvider() 248 | { 249 | return array( 250 | array(array(), 'test-routing-key'), 251 | array(array('test-routing-key-2'), 'test-routing-key-2'), 252 | ); 253 | } 254 | 255 | /** 256 | * Preparing AMQP Connection 257 | * 258 | * @return MockObject|AMQPStreamConnection 259 | */ 260 | private function prepareAMQPConnection() 261 | { 262 | return $this->getMockBuilder('\PhpAmqpLib\Connection\AMQPStreamConnection') 263 | ->disableOriginalConstructor() 264 | ->getMock(); 265 | } 266 | 267 | /** 268 | * Preparing AMQP Connection 269 | * 270 | * @return MockObject|AMQPChannel 271 | */ 272 | private function prepareAMQPChannel() 273 | { 274 | return $this->getMockBuilder('\PhpAmqpLib\Channel\AMQPChannel') 275 | ->disableOriginalConstructor() 276 | ->getMock(); 277 | } 278 | 279 | /** 280 | * Preparing QueuesProviderInterface instance 281 | * 282 | * @return MockObject|QueuesProviderInterface 283 | */ 284 | private function prepareQueuesProvider() 285 | { 286 | return $this->getMockBuilder('\OldSound\RabbitMqBundle\Provider\QueuesProviderInterface') 287 | ->getMock(); 288 | } 289 | 290 | /** 291 | * Preparing AMQP Channel Expectations 292 | * 293 | * @param mixed $expectedMethod 294 | * @param string $expectedRequeue 295 | * 296 | * @return void 297 | */ 298 | private function prepareAMQPChannelExpectations($expectedMethod, $expectedRequeue) 299 | { 300 | $this->amqpChannel->expects($this->any()) 301 | ->method('basic_reject') 302 | ->will($this->returnCallback(function ($delivery_tag, $requeue) use ($expectedMethod, $expectedRequeue) { 303 | Assert::assertSame($expectedMethod, 'basic_reject'); // Check if this function should be called. 304 | Assert::assertSame($requeue, $expectedRequeue); // Check if the message should be requeued. 305 | })); 306 | 307 | $this->amqpChannel->expects($this->any()) 308 | ->method('basic_ack') 309 | ->will($this->returnCallback(function ($delivery_tag) use ($expectedMethod) { 310 | Assert::assertSame($expectedMethod, 'basic_ack'); // Check if this function should be called. 311 | })); 312 | } 313 | 314 | /** 315 | * Prepare callback 316 | * 317 | * @param bool $processFlag 318 | * @return callable 319 | */ 320 | private function prepareCallback($processFlag) 321 | { 322 | return function ($msg) use ($processFlag) { 323 | return $processFlag; 324 | }; 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /Tests/RabbitMq/RpcClientTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('\OldSound\RabbitMqBundle\RabbitMq\RpcClient') 16 | ->setMethods(array('sendReply', 'maybeStopConsumer')) 17 | ->disableOriginalConstructor() 18 | ->getMock(); 19 | /** @var AMQPMessage $message */ 20 | $message = $this->getMockBuilder('\PhpAmqpLib\Message\AMQPMessage') 21 | ->setMethods(array('get')) 22 | ->setConstructorArgs(array('message')) 23 | ->getMock(); 24 | $serializer = $this->getMockBuilder('\Symfony\Component\Serializer\SerializerInterface') 25 | ->setMethods(array('serialize', 'deserialize')) 26 | ->getMock(); 27 | $serializer->expects($this->once())->method('deserialize')->with('message', 'json', null); 28 | $client->initClient(true); 29 | $client->setUnserializer(function($data) use ($serializer) { 30 | $serializer->deserialize($data, 'json',''); 31 | }); 32 | $client->processMessage($message); 33 | } 34 | 35 | public function testProcessMessageWithNotifyMethod() 36 | { 37 | /** @var RpcClient $client */ 38 | $client = $this->getMockBuilder('\OldSound\RabbitMqBundle\RabbitMq\RpcClient') 39 | ->setMethods(array('sendReply', 'maybeStopConsumer')) 40 | ->disableOriginalConstructor() 41 | ->getMock(); 42 | $expectedNotify = 'message'; 43 | /** @var AMQPMessage $message */ 44 | $message = $this->getMockBuilder('\PhpAmqpLib\Message\AMQPMessage') 45 | ->setMethods(array('get')) 46 | ->setConstructorArgs(array($expectedNotify)) 47 | ->getMock(); 48 | $notified = false; 49 | $client->notify(function ($message) use (&$notified) { 50 | $notified = $message; 51 | }); 52 | 53 | $client->initClient(false); 54 | $client->processMessage($message); 55 | 56 | $this->assertSame($expectedNotify, $notified); 57 | } 58 | 59 | public function testInvalidParameterOnNotify() 60 | { 61 | /** @var RpcClient $client */ 62 | $client = $this->getMockBuilder('\OldSound\RabbitMqBundle\RabbitMq\RpcClient') 63 | ->setMethods(array('sendReply', 'maybeStopConsumer')) 64 | ->disableOriginalConstructor() 65 | ->getMock(); 66 | 67 | $this->expectException('\InvalidArgumentException'); 68 | 69 | $client->notify('not a callable'); 70 | } 71 | 72 | public function testChannelCancelOnGetRepliesException() 73 | { 74 | $client = $this->getMockBuilder('\OldSound\RabbitMqBundle\RabbitMq\RpcClient') 75 | ->setMethods(null) 76 | ->disableOriginalConstructor() 77 | ->getMock(); 78 | 79 | $channel = $this->createMock('\PhpAmqpLib\Channel\AMQPChannel'); 80 | $channel->expects($this->any()) 81 | ->method('getChannelId') 82 | ->willReturn('test'); 83 | $channel->expects($this->once()) 84 | ->method('wait') 85 | ->willThrowException(new AMQPTimeoutException()); 86 | 87 | $this->expectException('\PhpAmqpLib\Exception\AMQPTimeoutException'); 88 | 89 | $channel->expects($this->once()) 90 | ->method('basic_cancel'); 91 | 92 | $client->setChannel($channel); 93 | $client->addRequest('a', 'b', 'c'); 94 | 95 | $client->getReplies(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Tests/RabbitMq/RpcServerTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('\OldSound\RabbitMqBundle\RabbitMq\RpcServer') 15 | ->setMethods(array('sendReply', 'maybeStopConsumer')) 16 | ->disableOriginalConstructor() 17 | ->getMock(); 18 | $message = $this->getMockBuilder('\PhpAmqpLib\Message\AMQPMessage') 19 | ->setMethods( array('get')) 20 | ->getMock(); 21 | $message->delivery_info = array( 22 | 'channel' => $this->getMockBuilder('\PhpAmqpLib\Channel\AMQPChannel') 23 | ->setMethods(array())->setConstructorArgs(array()) 24 | ->setMockClassName('') 25 | ->disableOriginalConstructor() 26 | ->getMock(), 27 | 'delivery_tag' => null 28 | ); 29 | $server->setCallback(function() { 30 | return 'message'; 31 | }); 32 | $serializer = $this->getMockBuilder('\Symfony\Component\Serializer\SerializerInterface') 33 | ->setMethods(array('serialize', 'deserialize')) 34 | ->getMock(); 35 | $serializer->expects($this->once())->method('serialize')->with('message', 'json'); 36 | $server->setSerializer(function($data) use ($serializer) { 37 | $serializer->serialize($data, 'json'); 38 | }); 39 | $server->processMessage($message); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | Tests 17 | 18 | 19 | 20 | 21 | 22 | . 23 | 24 | Resources 25 | Tests 26 | vendor 27 | 28 | 29 | 30 | 31 | --------------------------------------------------------------------------------