├── .formatter.yml ├── .gitignore ├── .php_cs ├── .php_cs.cache ├── .travis.yml ├── CONTRIBUTING.md ├── Collector └── RSQueueCollector.php ├── DependencyInjection ├── RSQueueConfiguration.php └── RSQueueExtension.php ├── LICENSE ├── README.md ├── RSQueueBundle.php ├── Resources ├── config │ ├── collector.yml │ ├── redis.yml │ ├── serializers.yml │ └── services.yml └── views │ └── Collector │ └── rsqueue.html.twig ├── Tests ├── ClusterRedisTest.php ├── RSQueueFunctionalTest.php ├── Services │ ├── ConsumerTest.php │ ├── ProducerTest.php │ └── PublisherTest.php └── SimpleRedisTest.php ├── composer.json ├── phpunit.xml.dist └── travis └── install-php-redis.sh /.formatter.yml: -------------------------------------------------------------------------------- 1 | use-sort: 2 | group-type: each 3 | sort-type: alph 4 | sort-direction: asc 5 | 6 | strict: true 7 | header: | 8 | /* 9 | * This file is part of the RSQueue library 10 | * 11 | * Copyright (c) 2016 - now() Marc Morera 12 | * 13 | * For the full copyright and license information, please view the LICENSE 14 | * file that was distributed with this source code. 15 | * 16 | * Feel free to edit as you please, and have fun. 17 | * 18 | * @author Marc Morera 19 | */ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | phpunit.xml 3 | Resources/doc/_build/* 4 | coverage 5 | composer.lock 6 | vendor 7 | composer.phar 8 | phpcs.lo 9 | phpcs.log 10 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | exclude('Resources') 5 | ->exclude('travis') 6 | ->exclude('vendor') 7 | ->in(__DIR__) 8 | ; 9 | return PhpCsFixer\Config::create() 10 | ->setRules([ 11 | '@PSR2' => true, 12 | '@Symfony' => true, 13 | ]) 14 | ->setFinder($finder) 15 | ; -------------------------------------------------------------------------------- /.php_cs.cache: -------------------------------------------------------------------------------- 1 | {"php":"7.1.12-1+ubuntu16.04.1+deb.sury.org+1","version":"2.10.0:v2.10.0#513a3765b56dd029175f9f32995566657ee89dda","rules":{"blank_line_after_namespace":true,"braces":{"allow_single_line_closure":true},"class_definition":{"singleLine":true},"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_constants":true,"lowercase_keywords":true,"method_argument_space":true,"no_break_comment":true,"no_closing_tag":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":true,"single_import_per_statement":true,"single_line_after_imports":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"visibility_required":true,"encoding":true,"full_opening_tag":true,"binary_operator_spaces":true,"blank_line_after_opening_tag":true,"blank_line_before_statement":{"statements":["return"]},"cast_spaces":true,"class_attributes_separation":{"elements":["method"]},"concat_space":{"spacing":"none"},"declare_equal_normalize":true,"function_typehint_space":true,"include":true,"increment_style":true,"lowercase_cast":true,"magic_constant_casing":true,"native_function_casing":true,"new_with_braces":true,"no_blank_lines_after_class_opening":true,"no_blank_lines_after_phpdoc":true,"no_empty_comment":true,"no_empty_phpdoc":true,"no_empty_statement":true,"no_extra_blank_lines":{"tokens":["curly_brace_block","extra","parenthesis_brace_block","square_brace_block","throw","use"]},"no_leading_import_slash":true,"no_leading_namespace_whitespace":true,"no_mixed_echo_print":{"use":"echo"},"no_multiline_whitespace_around_double_arrow":true,"no_short_bool_cast":true,"no_singleline_whitespace_before_semicolons":true,"no_spaces_around_offset":true,"no_trailing_comma_in_list_call":true,"no_trailing_comma_in_singleline_array":true,"no_unneeded_control_parentheses":true,"no_unneeded_curly_braces":true,"no_unneeded_final_method":true,"no_unused_imports":true,"no_whitespace_before_comma_in_array":true,"no_whitespace_in_blank_line":true,"normalize_index_brace":true,"object_operator_without_whitespace":true,"php_unit_fqcn_annotation":true,"phpdoc_align":{"tags":["method","param","property","return","throws","type","var"]},"phpdoc_annotation_without_dot":true,"phpdoc_indent":true,"phpdoc_inline_tag":true,"phpdoc_no_access":true,"phpdoc_no_alias_tag":true,"phpdoc_no_empty_return":true,"phpdoc_no_package":true,"phpdoc_no_useless_inheritdoc":true,"phpdoc_return_self_reference":true,"phpdoc_scalar":true,"phpdoc_separation":true,"phpdoc_single_line_var_spacing":true,"phpdoc_summary":true,"phpdoc_to_comment":true,"phpdoc_trim":true,"phpdoc_types":true,"phpdoc_var_without_name":true,"protected_to_private":true,"return_type_declaration":true,"self_accessor":true,"semicolon_after_instruction":true,"short_scalar_cast":true,"single_blank_line_before_namespace":true,"single_line_comment_style":{"comment_types":["hash"]},"single_quote":true,"space_after_semicolon":{"remove_in_empty_for_expressions":true},"standardize_not_equals":true,"ternary_operator_spaces":true,"trailing_comma_in_multiline_array":true,"trim_array_spaces":true,"unary_operator_spaces":true,"whitespace_after_comma_in_array":true,"yoda_style":true},"hashes":{"Tests\/RSQueueFunctionalTest.php":3341026368,"Tests\/Services\/ProducerTest.php":2364093899,"Tests\/Services\/PublisherTest.php":115614915,"Tests\/Services\/ConsumerTest.php":1511805715,"Tests\/ClusterRedisTest.php":2686785634,"Tests\/SimpleRedisTest.php":26860270,"RSQueueBundle.php":1122671989,"Collector\/RSQueueCollector.php":909912693,"DependencyInjection\/RSQueueConfiguration.php":349113273,"DependencyInjection\/RSQueueExtension.php":1124901613}} -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | - 5.6 8 | 9 | cache: 10 | directories: 11 | - $HOME/.composer/cache/files 12 | 13 | matrix: 14 | fast_finish: true 15 | include: 16 | - php: 5.5 17 | env: COMPOSER_FLAGS="--prefer-lowest" SYMFONY_DEPRECATIONS_HELPER=weak 18 | - php: 5.6 19 | env: SYMFONY_VERSION='2.7.*' 20 | - php: 5.6 21 | env: SYMFONY_VERSION='2.8.*' 22 | 23 | before_install: 24 | - composer self-update 25 | - if [ "$SYMFONY_VERSION" != "" ]; then composer require --dev --no-update symfony/symfony=$SYMFONY_VERSION; fi 26 | - git clone git://github.com/nicolasff/phpredis.git 27 | - cd phpredis && phpize && ./configure && make && sudo make install && cd .. 28 | - echo "extension=redis.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"` 29 | 30 | install: composer update $COMPOSER_FLAGS 31 | 32 | script: phpunit 33 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ----- 3 | 4 | All code is Symfony2 Code formatted, so every pull request must validate phpcs 5 | standards. You should read 6 | [Symfony2 coding standards](http://symfony.com/doc/current/contributing/code/standards.html) 7 | and install [this](https://github.com/opensky/Symfony2-coding-standard) 8 | CodeSniffer to check all code is validated. 9 | 10 | There is also a policy for contributing to this project. All pull request must 11 | be all explained step by step, to make us more understandable and easier to 12 | merge pull request. All new features must be tested with PHPUnit. 13 | 14 | If you'd like to contribute, please read the [Contributing Code][1] part of the 15 | documentation. If you're submitting a pull request, please follow the guidelines 16 | in the [Submitting a Patch][2] section and use the [Pull Request Template][3]. 17 | 18 | [1]: http://symfony.com/doc/current/contributing/code/index.html 19 | [2]: http://symfony.com/doc/current/contributing/code/patches.html#check-list 20 | [3]: http://symfony.com/doc/current/contributing/code/patches.html#make-a-pull-request 21 | -------------------------------------------------------------------------------- /Collector/RSQueueCollector.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace RSQueueBundle\Collector; 19 | 20 | use Exception; 21 | use RSQueue\Event\RSQueueProducerEvent; 22 | use RSQueue\Event\RSQueuePublisherEvent; 23 | use Symfony\Component\HttpFoundation\Request; 24 | use Symfony\Component\HttpFoundation\Response; 25 | use Symfony\Component\HttpKernel\DataCollector\DataCollector; 26 | 27 | /** 28 | * Collector for RSQueue data. 29 | * 30 | * All these methods are subscribed to custom RSQueueBundle events 31 | */ 32 | class RSQueueCollector extends DataCollector 33 | { 34 | /** 35 | * Construct method for initializate all data. 36 | * 37 | * Also initializes total value to 0 38 | */ 39 | public function __construct() 40 | { 41 | $this->reset(); 42 | } 43 | 44 | /** 45 | * Subscribed to RSQueueProducer event. 46 | * 47 | * Add to collect data a new producer action 48 | * 49 | * @param RSQueueProducerEvent $event Event fired 50 | * 51 | * @return RSQueueCollector self Object 52 | */ 53 | public function onProducerAction(RSQueueProducerEvent $event) 54 | { 55 | ++$this->data['total']; 56 | $this->data['prod'][] = [ 57 | 'payload' => $event->getPayloadSerialized(), 58 | 'queue' => $event->getQueueName(), 59 | ]; 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * Subscribed to RSQueuePublisher event. 66 | * 67 | * Add to collect data a new publisher action 68 | * 69 | * @param RSQueuePublisherEvent $event Event fired 70 | * 71 | * @return RSQueueCollector self Object 72 | */ 73 | public function onPublisherAction(RSQueuePublisherEvent $event) 74 | { 75 | ++$this->data['total']; 76 | $this->data['publ'][] = [ 77 | 'payload' => $event->getPayloadSerialized(), 78 | 'queue' => $event->getChannelName(), 79 | ]; 80 | 81 | return $this; 82 | } 83 | 84 | /** 85 | * Get total of queue interactions. 86 | * 87 | * @return int 88 | */ 89 | public function getTotal() 90 | { 91 | return (int) $this->data['total']; 92 | } 93 | 94 | /** 95 | * Get producer collection. 96 | * 97 | * @return array 98 | */ 99 | public function getProducer() 100 | { 101 | return $this->data['prod']; 102 | } 103 | 104 | /** 105 | * Get publisher collection. 106 | * 107 | * @return array 108 | */ 109 | public function getPublisher() 110 | { 111 | return $this->data['publ']; 112 | } 113 | 114 | /** 115 | * Collects data for the given Request and Response. 116 | * 117 | * @param Request $request 118 | * @param Response $response 119 | * @param \Exception $exception 120 | * 121 | * @api 122 | */ 123 | public function collect( 124 | Request $request, 125 | Response $response, 126 | Exception $exception = null 127 | ) { 128 | } 129 | 130 | /** 131 | * Reset collector. 132 | */ 133 | public function reset() 134 | { 135 | $this->total = 0; 136 | $this->data = [ 137 | 'prod' => [], 138 | 'publ' => [], 139 | 'total' => 0, 140 | ]; 141 | } 142 | 143 | /** 144 | * Return collector name. 145 | * 146 | * @return string Collector name 147 | */ 148 | public function getName() 149 | { 150 | return 'rsqueue_collector'; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /DependencyInjection/RSQueueConfiguration.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace RSQueueBundle\DependencyInjection; 19 | 20 | use Mmoreram\BaseBundle\DependencyInjection\BaseConfiguration; 21 | use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; 22 | 23 | /** 24 | * Class RSQueueConfiguration. 25 | */ 26 | class RSQueueConfiguration extends BaseConfiguration 27 | { 28 | /** 29 | * Configure the root node. 30 | * 31 | * @param ArrayNodeDefinition $rootNode Root node 32 | */ 33 | protected function setupTree(ArrayNodeDefinition $rootNode) 34 | { 35 | $rootNode 36 | ->addDefaultsIfNotSet() 37 | ->children() 38 | ->arrayNode('queues') 39 | ->prototype('scalar')->end() 40 | ->end() 41 | ->scalarNode('serializer') 42 | ->treatNullLike('RSQueue\\Serializer\\JsonSerializer') 43 | ->defaultValue('RSQueue\\Serializer\\JsonSerializer') 44 | ->end() 45 | ->arrayNode('collector') 46 | ->addDefaultsIfNotSet() 47 | ->children() 48 | ->booleanNode('enable') 49 | ->defaultTrue() 50 | ->end() 51 | ->end() 52 | ->end() 53 | ->arrayNode('server') 54 | ->addDefaultsIfNotSet() 55 | ->children() 56 | ->arrayNode('redis') 57 | ->addDefaultsIfNotSet() 58 | ->children() 59 | ->scalarNode('cluster') 60 | ->defaultFalse() 61 | ->end() 62 | ->scalarNode('host') 63 | ->defaultValue('127.0.0.1') 64 | ->end() 65 | ->scalarNode('port') 66 | ->defaultValue(6379) 67 | ->end() 68 | ->scalarNode('database') 69 | ->defaultNull() 70 | ->end() 71 | ->end() 72 | ->end() 73 | ->end() 74 | ->end() 75 | ->end(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /DependencyInjection/RSQueueExtension.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace RSQueueBundle\DependencyInjection; 19 | 20 | use Mmoreram\BaseBundle\DependencyInjection\BaseExtension; 21 | use Symfony\Component\Config\Definition\ConfigurationInterface; 22 | 23 | /** 24 | * This is the class that loads and manages your bundle configuration. 25 | * 26 | * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html} 27 | */ 28 | class RSQueueExtension extends BaseExtension 29 | { 30 | /** 31 | * Returns the recommended alias to use in XML. 32 | * 33 | * This alias is also the mandatory prefix to use when using YAML. 34 | * 35 | * @return string The alias 36 | */ 37 | public function getAlias() 38 | { 39 | return 'rs_queue'; 40 | } 41 | 42 | /** 43 | * Get the Config file location. 44 | * 45 | * @return string 46 | */ 47 | protected function getConfigFilesLocation(): string 48 | { 49 | return __DIR__.'/../Resources/config'; 50 | } 51 | 52 | /** 53 | * Config files to load. 54 | * 55 | * Each array position can be a simple file name if must be loaded always, 56 | * or an array, with the filename in the first position, and a boolean in 57 | * the second one. 58 | * 59 | * As a parameter, this method receives all loaded configuration, to allow 60 | * setting this boolean value from a configuration value. 61 | * 62 | * return array( 63 | * 'file1.yml', 64 | * 'file2.yml', 65 | * ['file3.yml', $config['my_boolean'], 66 | * ... 67 | * ); 68 | * 69 | * @param array $config Config definitions 70 | * 71 | * @return array Config files 72 | */ 73 | protected function getConfigFiles(array $config): array 74 | { 75 | return [ 76 | 'services', 77 | ['collector', $config['collector']['enable']], 78 | 'redis', 79 | 'serializers', 80 | ]; 81 | } 82 | 83 | /** 84 | * Return a new Configuration instance. 85 | * 86 | * If object returned by this method is an instance of 87 | * ConfigurationInterface, extension will use the Configuration to read all 88 | * bundle config definitions. 89 | * 90 | * Also will call getParametrizationValues method to load some config values 91 | * to internal parameters. 92 | * 93 | * @return ConfigurationInterface|null 94 | */ 95 | protected function getConfigurationInstance(): ? ConfigurationInterface 96 | { 97 | return new RSQueueConfiguration($this->getAlias()); 98 | } 99 | 100 | /** 101 | * Load Parametrization definition. 102 | * 103 | * return array( 104 | * 'parameter1' => $config['parameter1'], 105 | * 'parameter2' => $config['parameter2'], 106 | * ... 107 | * ); 108 | * 109 | * @param array $config Bundles config values 110 | * 111 | * @return array 112 | */ 113 | protected function getParametrizationValues(array $config): array 114 | { 115 | return [ 116 | 'rs_queue.queues' => $config['queues'], 117 | 'rs_queue.serializer.class' => $config['serializer'], 118 | 'rs_queue.server.redis' => $config['server']['redis'], 119 | ]; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Marc Morera 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RSQueueBundle for Symfony 2 | ===== 3 | ### Simple queuing system based on Redis 4 | 5 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/78931ad8-b016-4b5b-9b45-c5a5767fbd9e/mini.png)](https://insight.sensiolabs.com/projects/78931ad8-b016-4b5b-9b45-c5a5767fbd9e) 6 | [![Build Status](https://secure.travis-ci.org/mmoreram/RSQueueBundle.png?branch=master)](http://travis-ci.org/mmoreram/rsqueue-bundle) 7 | [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/mmoreram/RSQueueBundle/badges/quality-score.png?s=290f904ff14fb72d9d40288682949b3de88f99f9)](https://scrutinizer-ci.com/g/mmoreram/RSQueueBundle/) 8 | 9 | Table of contents 10 | ----- 11 | 1. [Installing/Configuring](#installingconfiguring) 12 | * [Tags](#tags) 13 | * [Installing Redis](#installing-redis) 14 | * [Installing PHPRedis](#installing-phpredis) 15 | * [Installing RSQueue](#installing-rsqueue) 16 | * [Configuration](#configuration) 17 | 2. [Producers/Consumers](#producersconsumers) 18 | 3. [Publishers/Subscribers](#publisherssubscribers) 19 | 4. [Events](#events) 20 | 5. [Contributing](#contributing) 21 | 22 | Installing/Configuring 23 | ----- 24 | 25 | ## Tags 26 | 27 | * Use version `1.0-dev` for last updated. Alias of `dev-master`. 28 | * Use last stable version tag to stay in a stable release. 29 | 30 | ## Installing [Redis](http://redis.io) 31 | 32 | ``` bash 33 | wget http://download.redis.io/redis-stable.tar.gz 34 | tar xvzf redis-stable.tar.gz 35 | cd redis-stable 36 | make 37 | ``` 38 | 39 | ## Installing [PHPRedis](https://github.com/nicolasff/phpredis) 40 | 41 | phpredis extension is necessary to be installed in your server. 42 | Otherwise composer will alert you. 43 | 44 | ``` bash 45 | git clone git://github.com/nicolasff/phpredis.git 46 | cd phpredis 47 | phpize 48 | ./configure 49 | make 50 | sudo make install 51 | cd .. 52 | echo "extension=redis.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"` 53 | ``` 54 | 55 | ## Installing [RSQueue](http://rsqueue.com) 56 | 57 | You have to add require line into you composer.json file 58 | 59 | ``` yml 60 | "require": { 61 | "php": ">=5.3.3", 62 | "symfony/symfony": "2.3.*", 63 | ... 64 | "mmoreram/rsqueue-bundle": "dev-master" 65 | }, 66 | ``` 67 | 68 | Then you have to use composer to update your project dependencies 69 | 70 | ``` bash 71 | php composer.phar update 72 | ``` 73 | 74 | And register the bundle in your appkernel.php file 75 | 76 | ``` php 77 | return array( 78 | // ... 79 | new Mmoreram\RSQueueBundle\RSQueueBundle(), 80 | // ... 81 | ); 82 | ``` 83 | 84 | ## Configuration 85 | 86 | In this first version, all conections are localhost:6379, but as soon as posible connections will be configurable. 87 | You need to configure all queues and serializer. 88 | By default serializer has the value 'Json', but also 'PHP' value can be used. Also custom serializer can be implemented by extending default serializer interface. Then you need to add namespace of class into the rs_queue.serializer parameter. 89 | 90 | ``` yml 91 | rs_queue: 92 | 93 | # Queues definition 94 | queues: 95 | videos: "queues:videos" 96 | audios: "queues:audios" 97 | 98 | # Serializer definition 99 | serializer: ~ 100 | 101 | # Server configuration. By default, these values 102 | server: 103 | redis: 104 | host: 127.0.0.1 105 | port: 6379 106 | database: ~ 107 | ``` 108 | 109 | Producers/Consumers 110 | ----- 111 | Producer/consumer model allows you to produce elements into one/many queues by using default rsqueue producer service. 112 | One element is pushed into one queue so one and only one consumer will pop and treat this element. 113 | 114 | ``` php 115 | $this->container->get("rs_queue.producer")->produce("videos", "this is my video"); 116 | $this->container->get("rs_queue.producer")->produce("audios", "this is my audio"); 117 | ``` 118 | 119 | Then you should extend ConsumerCommand so that in this way you can define which queues listen, and in each case, which action execute. 120 | 121 | ``` php 122 | use Symfony\Component\Console\Input\InputInterface; 123 | use Symfony\Component\Console\Output\OutputInterface; 124 | use Mmoreram\RSQueueBundle\Command\ConsumerCommand; 125 | 126 | /** 127 | * Testing consumer command 128 | */ 129 | class TestConsumerCommand extends ConsumerCommand 130 | { 131 | 132 | /** 133 | * Configuration method 134 | */ 135 | protected function configure() 136 | { 137 | $this 138 | ->setName('test:consumer') 139 | ->setDescription('Testing consumer command'); 140 | ; 141 | 142 | parent::configure(); 143 | } 144 | 145 | /** 146 | * Relates queue name with appropiated method 147 | */ 148 | public function define() 149 | { 150 | $this->addQueue('videos', 'consumeVideo'); 151 | } 152 | 153 | /** 154 | * If many queues are defined, as Redis respects order of queues, you can shuffle them 155 | * just overwritting method shuffleQueues() and returning true 156 | * 157 | * @return boolean Shuffle before passing to Gearman 158 | */ 159 | public function shuffleQueues() 160 | { 161 | return true; 162 | } 163 | 164 | /** 165 | * Consume method with retrieved queue value 166 | * 167 | * @param InputInterface $input An InputInterface instance 168 | * @param OutputInterface $output An OutputInterface instance 169 | * @param Mixed $payload Data retrieved and unserialized from queue 170 | */ 171 | protected function consumeVideo(InputInterface $input, OutputInterface $output, $payload) 172 | { 173 | $output->writeln($payload); 174 | } 175 | } 176 | ``` 177 | 178 | Publishers/Subscribers 179 | ----- 180 | This model allows data broadcasting. This means that one or more Subscribers will treat all elements of the queue, but only if they are listening just in the moment publisher publish them. 181 | 182 | ``` php 183 | $this->container->get("rs_queue.publisher")->publish("audios", "this is my audio"); 184 | ``` 185 | 186 | And, as consumers, subscribers must define which channels they want to listen 187 | 188 | ``` php 189 | use Symfony\Component\Console\Input\InputInterface; 190 | use Symfony\Component\Console\Output\OutputInterface; 191 | use Mmoreram\RSQueueBundle\Command\SubscriberCommand; 192 | 193 | /** 194 | * Testing subscriber command 195 | */ 196 | class TestSubscriberCommand extends SubscriberCommand 197 | { 198 | 199 | /** 200 | * Configuration method 201 | */ 202 | protected function configure() 203 | { 204 | $this 205 | ->setName('test:subscriber:audios') 206 | ->setDescription('Testing subscriber audios command'); 207 | ; 208 | 209 | parent::configure(); 210 | } 211 | 212 | /** 213 | * Relates queue name with appropiated method 214 | */ 215 | public function define() 216 | { 217 | $this->addChannel('audios', 'consumeAudio'); 218 | } 219 | 220 | /** 221 | * If many queues are defined, as Redis respects order of queues, you can shuffle them 222 | * just overwritting method shuffleQueues() and returning true 223 | * 224 | * @return boolean Shuffle before passing to Gearman 225 | */ 226 | public function shuffleQueues() 227 | { 228 | return true; 229 | } 230 | 231 | /** 232 | * subscriber method with retrieved queue value 233 | * 234 | * @param InputInterface $input An InputInterface instance 235 | * @param OutputInterface $output An OutputInterface instance 236 | * @param Mixed $payload Data retrieved and unserialized from queue 237 | */ 238 | protected function consumeAudio(InputInterface $input, OutputInterface $output, $payload) 239 | { 240 | $output->writeln($payload); 241 | } 242 | } 243 | ``` 244 | 245 | By extending PSubscriberCommand you can define patterns instead of queue names. 246 | 247 | ``` php 248 | use Symfony\Component\Console\Input\InputInterface; 249 | use Symfony\Component\Console\Output\OutputInterface; 250 | use Mmoreram\RSQueueBundle\Command\PSubscriberCommand; 251 | 252 | /** 253 | * Testing PSubscriber command 254 | */ 255 | class TestPSubscriberCommand extends PSubscriberCommand 256 | { 257 | 258 | /** 259 | * Configuration method 260 | */ 261 | protected function configure() 262 | { 263 | $this 264 | ->setName('test:psubscriber') 265 | ->setDescription('Testing psubscriber command'); 266 | ; 267 | 268 | parent::configure(); 269 | } 270 | 271 | /** 272 | * Relates queue name with appropiated method 273 | */ 274 | public function define() 275 | { 276 | $this->addPattern('*', 'consumeAll'); 277 | } 278 | 279 | /** 280 | * If many queues are defined, as Redis respects order of queues, you can shuffle them 281 | * just overwritting method shuffleQueues() and returning true 282 | * 283 | * @return boolean Shuffle before passing to Gearman 284 | */ 285 | public function shuffleQueues() 286 | { 287 | return true; 288 | } 289 | 290 | /** 291 | * Consume method with retrieved queue value 292 | * 293 | * @param InputInterface $input An InputInterface instance 294 | * @param OutputInterface $output An OutputInterface instance 295 | * @param Mixed $payload Data retrieved and unserialized from queue 296 | */ 297 | protected function consumeAll(InputInterface $input, OutputInterface $output, $payload) 298 | { 299 | $output->writeln($payload); 300 | } 301 | } 302 | ``` 303 | 304 | Events 305 | ----- 306 | Custom events are used in this bundle. 307 | 308 | ``` php 309 | /** 310 | * The rs_queue.consumer is thrown each time a job is consumed by consumer 311 | * 312 | * The event listener recieves an 313 | * Mmoreram\RSQueueBundle\Event\RSQueueConsumerEvent instance 314 | * 315 | * @var string 316 | */ 317 | const RSQUEUE_CONSUMER = 'rs_queue.consumer'; 318 | 319 | /** 320 | * The rs_queue.subscriber is thrown each time a job is consumed by subscriber 321 | * 322 | * The event listener recieves an 323 | * Mmoreram\RSQueueBundle\Event\RSQueueSubscriberEvent instance 324 | * 325 | * @var string 326 | */ 327 | const RSQUEUE_SUBSCRIBER = 'rs_queue.subscriber'; 328 | 329 | /** 330 | * The rs_queue.producer is thrown each time a job is consumed by producer 331 | * 332 | * The event listener recieves an 333 | * Mmoreram\RSQueueBundle\Event\RSQueueProducerEvent instance 334 | * 335 | * @var string 336 | */ 337 | const RSQUEUE_PRODUCER = 'rs_queue.producer'; 338 | 339 | /** 340 | * The rs_queue.publisher is thrown each time a job is consumed by publisher 341 | * 342 | * The event listener recieves an 343 | * Mmoreram\RSQueueBundle\Event\RSQueuePublisherEvent instance 344 | * 345 | * @var string 346 | */ 347 | const RSQUEUE_PUBLISHER = 'rs_queue.publisher'; 348 | ``` 349 | 350 | Contributing 351 | ----- 352 | 353 | All code is Symfony2 Code formatted, so every pull request must validate phpcs 354 | standards. You should read 355 | [Symfony2 coding standards](http://symfony.com/doc/current/contributing/code/standards.html) 356 | and install [this](https://github.com/opensky/Symfony2-coding-standard) 357 | CodeSniffer to check all code is validated. 358 | 359 | There is also a policy for contributing to this project. All pull request must 360 | be all explained step by step, to make us more understandable and easier to 361 | merge pull request. All new features must be tested with PHPUnit. 362 | 363 | If you'd like to contribute, please read the [Contributing Code][1] part of the 364 | documentation. If you're submitting a pull request, please follow the guidelines 365 | in the [Submitting a Patch][2] section and use the [Pull Request Template][3]. 366 | 367 | [1]: http://symfony.com/doc/current/contributing/code/index.html 368 | [2]: http://symfony.com/doc/current/contributing/code/patches.html#check-list 369 | [3]: http://symfony.com/doc/current/contributing/code/patches.html#make-a-pull-request 370 | -------------------------------------------------------------------------------- /RSQueueBundle.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace RSQueueBundle; 19 | 20 | use Mmoreram\BaseBundle\BaseBundle; 21 | use RSQueueBundle\DependencyInjection\RSQueueExtension; 22 | use Symfony\Bundle\FrameworkBundle\FrameworkBundle; 23 | use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; 24 | use Symfony\Component\HttpKernel\KernelInterface; 25 | 26 | /** 27 | * Main RSQueueBundle class. 28 | */ 29 | class RSQueueBundle extends BaseBundle 30 | { 31 | /** 32 | * Returns the bundle's container extension. 33 | * 34 | * @return ExtensionInterface|null The container extension 35 | * 36 | * @throws \LogicException 37 | */ 38 | public function getContainerExtension() 39 | { 40 | return new RSQueueExtension(); 41 | } 42 | 43 | /** 44 | * Return all bundle dependencies. 45 | * 46 | * Values can be a simple bundle namespace or its instance 47 | * 48 | * @return array 49 | */ 50 | public static function getBundleDependencies(KernelInterface $kernel): array 51 | { 52 | return [ 53 | FrameworkBundle::class, 54 | ]; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Resources/config/collector.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | rs_queue.data_collector: 4 | class: RSQueueBundle\Collector\RSQueueCollector 5 | tags: 6 | - { name: data_collector, template: "RSQueueBundle:Collector:rsqueue", id: "rsqueue_collector" } 7 | - { name: kernel.event_listener, event: rsqueue.producer, method: onProducerAction } 8 | - { name: kernel.event_listener, event: rsqueue.publisher, method: onPublisherAction } -------------------------------------------------------------------------------- /Resources/config/redis.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | rs_queue.redis_factory: 4 | class: RSQueue\RedisFactory 5 | arguments: 6 | - "%rs_queue.server.redis%" 7 | 8 | rs_queue.redis: 9 | class: StdObject 10 | factory: 11 | - "@rs_queue.redis_factory" 12 | - create -------------------------------------------------------------------------------- /Resources/config/serializers.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | rs_queue.serializer.factory: 4 | class: RSQueue\Serializer\SerializerFactory 5 | arguments: 6 | - "%rs_queue.serializer.class%" 7 | 8 | rs_queue.serializer: 9 | class: RSQueue\Serializer\Serializer 10 | factory: 11 | - "@rs_queue.serializer.factory" 12 | - "create" -------------------------------------------------------------------------------- /Resources/config/services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | rs_queue.resolver.queue_alias: 4 | class: RSQueue\Resolver\QueueAliasResolver 5 | arguments: 6 | - "%rs_queue.queues%" 7 | 8 | rs_queue.service: 9 | abstract: true 10 | arguments: 11 | - "@event_dispatcher" 12 | - "@rs_queue.redis" 13 | - "@rs_queue.resolver.queue_alias" 14 | - "@rs_queue.serializer" 15 | 16 | rs_queue.consumer: 17 | class: RSQueue\Services\Consumer 18 | parent: rs_queue.service 19 | public: true 20 | 21 | rs_queue.producer: 22 | class: RSQueue\Services\Producer 23 | parent: rs_queue.service 24 | public: true 25 | 26 | rs_queue.publisher: 27 | class: RSQueue\Services\Publisher 28 | parent: rs_queue.service 29 | public: true -------------------------------------------------------------------------------- /Resources/views/Collector/rsqueue.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'WebProfilerBundle:Profiler:layout.html.twig' %} 2 | 3 | {% block toolbar %} 4 | {# the web debug toolbar content #} 5 | {% set icon %} 6 | RSQueues 7 | {{ collector.total }} 8 | {% endset %} 9 | {% set text %} 10 |
11 | Producer 12 | {{ collector.producer|length }} 13 |
14 |
15 | Publisher 16 | {{ collector.publisher|length }} 17 |
18 | {% endset %} 19 | {% include 'WebProfilerBundle:Profiler:toolbar_item.html.twig' with { 'link': profiler_url } %} 20 | {% endblock %} 21 | 22 | {% block menu %} 23 | 24 | RSQueues 25 | RSQueue 26 | 27 | {{ collector.total }} 28 | 29 | 30 | {% endblock %} 31 | 32 | {% block panel %} 33 | {# the panel content #} 34 | 35 |

RSQueue

36 | {% if collector.total == 0 %} 37 |

No queue interaction

38 | {% else %} 39 | {% if collector.producer|length > 0 %} 40 | Producer 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {% for line in collector.producer %} 50 | 51 | 52 | 53 | 54 | {% endfor %} 55 | 56 |
Queue namePayload
{{ line.queue }}{{ line.payload }}
57 | {% endif %} 58 | 59 | {% if collector.publisher|length > 0 %} 60 | Publisher 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | {% for line in collector.publisher %} 70 | 71 | 72 | 73 | 74 | {% endfor %} 75 | 76 |
Queue namePayload
{{ line.queue }}{{ line.payload }}
77 | {% endif %} 78 | 79 | {% endif %} 80 | {% endblock %} 81 | -------------------------------------------------------------------------------- /Tests/ClusterRedisTest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace RSQueueBundle\Tests; 19 | 20 | /** 21 | * Class ClusterRedisTest. 22 | */ 23 | class ClusterRedisTest extends RSQueueFunctionalTest 24 | { 25 | /** 26 | * Get redis configuration. 27 | * 28 | * @return array 29 | */ 30 | public static function getRedisConfiguration(): array 31 | { 32 | return [ 33 | 'cluster' => true, 34 | 'port' => 30001, 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/RSQueueFunctionalTest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace RSQueueBundle\Tests; 19 | 20 | use Mmoreram\BaseBundle\Tests\BaseFunctionalTest; 21 | use Mmoreram\BaseBundle\Tests\BaseKernel; 22 | use RSQueueBundle\RSQueueBundle; 23 | use RSQueueBundle\Tests\Services\ConsumerTest; 24 | use RSQueueBundle\Tests\Services\ProducerTest; 25 | use RSQueueBundle\Tests\Services\PublisherTest; 26 | use Symfony\Component\HttpKernel\KernelInterface; 27 | 28 | /** 29 | * Class RSQueueFunctionalTest. 30 | */ 31 | abstract class RSQueueFunctionalTest extends BaseFunctionalTest 32 | { 33 | use ConsumerTest; 34 | use ProducerTest; 35 | use PublisherTest; 36 | 37 | /** 38 | * Get kernel. 39 | * 40 | * @return KernelInterface 41 | */ 42 | protected static function getKernel(): KernelInterface 43 | { 44 | return new BaseKernel( 45 | [RSQueueBundle::class], 46 | [ 47 | 'rs_queue' => [ 48 | 'server' => [ 49 | 'redis' => static::getRedisConfiguration(), 50 | ], 51 | ], 52 | ], 53 | [] 54 | ); 55 | } 56 | 57 | /** 58 | * Get redis configuration. 59 | * 60 | * @return array 61 | */ 62 | abstract public static function getRedisConfiguration(): array; 63 | 64 | /** 65 | * Produce. 66 | * 67 | * @param string $queue 68 | * @param mixed $payload 69 | */ 70 | protected function produce( 71 | string $queue, 72 | $payload 73 | ) { 74 | $this 75 | ->get('rs_queue.producer') 76 | ->produce($queue, $payload); 77 | } 78 | 79 | /** 80 | * Consume. 81 | * 82 | * @param string|string[] $queue 83 | * @param int $timeout 84 | * 85 | * @return array 86 | */ 87 | protected function consume( 88 | $queue, 89 | int $timeout = 0 90 | ): array { 91 | return $this 92 | ->get('rs_queue.consumer') 93 | ->consume($queue, $timeout); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Tests/Services/ConsumerTest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace RSQueueBundle\Tests\Services; 19 | 20 | /** 21 | * Class ConsumerTest. 22 | */ 23 | trait ConsumerTest 24 | { 25 | /** 26 | * Test that we can produce something and we can consume it. 27 | * 28 | * @dataProvider getPayloads 29 | */ 30 | public function testBasicScenario($payload) 31 | { 32 | $redisQueue = 'test_'.rand(0, 100000000000); 33 | $this->produce($redisQueue, $payload); 34 | $this->assertEquals( 35 | [$redisQueue, $payload], 36 | $this->consume($redisQueue) 37 | ); 38 | } 39 | 40 | /** 41 | * get payloads. 42 | */ 43 | public function getPayloads() 44 | { 45 | return [ 46 | [1], 47 | [['something', 1234, null]], 48 | [null], 49 | ]; 50 | } 51 | 52 | /** 53 | * Test empty queue with timeout. 54 | */ 55 | public function testEmptyQueueWithTimeout() 56 | { 57 | $redisQueue = 'test_'.rand(0, 100000000000); 58 | $this->assertEquals( 59 | [], 60 | $this->consume($redisQueue, 1) 61 | ); 62 | } 63 | 64 | /** 65 | * Test n iterations. 66 | * 67 | * @param string|string[] 68 | * 69 | * @dataProvider getQueues 70 | */ 71 | public function testNIterations($redisQueue) 72 | { 73 | $uniqueRedisQueue = is_array($redisQueue) 74 | ? $redisQueue[array_rand($redisQueue)] 75 | : $redisQueue; 76 | 77 | $this->produce($uniqueRedisQueue, 0); 78 | $this->produce($uniqueRedisQueue, 1); 79 | $this->produce($uniqueRedisQueue, 2); 80 | $this->produce($uniqueRedisQueue, 3); 81 | $this->assertEquals(0, $this->consume($redisQueue)[1]); 82 | $this->assertEquals(1, $this->consume($redisQueue)[1]); 83 | $this->assertEquals(2, $this->consume($redisQueue)[1]); 84 | $this->assertEquals(3, $this->consume($redisQueue)[1]); 85 | $this->assertEquals([], $this->consume($redisQueue, 1)); 86 | } 87 | 88 | /** 89 | * Get queues. 90 | */ 91 | public function getQueues() 92 | { 93 | return [ 94 | ['test_'.rand(0, 100000000000)], 95 | ]; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Tests/Services/ProducerTest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace RSQueueBundle\Tests\Services; 19 | 20 | use RSQueue\Services\Producer; 21 | 22 | /** 23 | * Class ProducerTest. 24 | */ 25 | trait ProducerTest 26 | { 27 | /** 28 | * Test that publisher service exists. 29 | */ 30 | public function testProducerExists() 31 | { 32 | $this->assertInstanceOf( 33 | Producer::class, 34 | $this->get('rs_queue.producer') 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/Services/PublisherTest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace RSQueueBundle\Tests\Services; 19 | 20 | use RSQueue\Services\Publisher; 21 | 22 | /** 23 | * Class PublisherTest. 24 | */ 25 | trait PublisherTest 26 | { 27 | /** 28 | * Test that publisher service exists. 29 | */ 30 | public function testPublisherExists() 31 | { 32 | $this->assertInstanceOf( 33 | Publisher::class, 34 | $this->get('rs_queue.publisher') 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/SimpleRedisTest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | 16 | declare(strict_types=1); 17 | 18 | namespace RSQueueBundle\Tests; 19 | 20 | /** 21 | * Class SimpleRedisTest. 22 | */ 23 | class SimpleRedisTest extends RSQueueFunctionalTest 24 | { 25 | /** 26 | * Get redis configuration. 27 | * 28 | * @return array 29 | */ 30 | public static function getRedisConfiguration(): array 31 | { 32 | return []; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rsqueue/rsqueue-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Redis Symfony2 Queue Bundle, a simple and soft redis based message queue for symfony2", 5 | "keywords": ["rsqueue", "redis", "queue", "symfony"], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Marc Morera", 10 | "email": "yuhu@mmoreram.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=7.1", 15 | "symfony/config": "^3.0|^4.0", 16 | "symfony/http-kernel": "^3.0|^4.0", 17 | "symfony/http-foundation": "^3.0|^4.0", 18 | "symfony/dependency-injection": "~3.0|^4.0", 19 | "symfony/framework-bundle": "~3.0|^4.0", 20 | "mmoreram/base-bundle": "^1.0.7", 21 | "mmoreram/symfony-bundle-dependencies": "^2.0.0", 22 | "rsqueue/rsqueue": "^0.1.0" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "^5.6.4", 26 | "mmoreram/php-formatter": "1.3.1", 27 | "friendsofphp/php-cs-fixer": "^2.5.0" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "RSQueueBundle\\": "" 32 | } 33 | }, 34 | "scripts": { 35 | "fix-code": [ 36 | "vendor/bin/php-cs-fixer fix --config=.php_cs", 37 | "vendor/bin/php-formatter f:h:f . --exclude=vendor", 38 | "vendor/bin/php-formatter f:s:f . --exclude=vendor", 39 | "vendor/bin/php-formatter f:u:s . --exclude=vendor" 40 | ], 41 | "test": [ 42 | "vendor/phpunit/phpunit/phpunit" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./Tests 17 | 18 | 19 | 20 | 21 | 22 | ./ 23 | 24 | ./Tests/ 25 | ./Resources/ 26 | ./DependencyInjection/ 27 | ./vendor/ 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /travis/install-php-redis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # @copyright (c) 2013 phpBB Group 4 | # @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 5 | # 6 | 7 | # redis 8 | git clone git://github.com/nicolasff/phpredis.git 9 | cd phpredis 10 | phpize 11 | ./configure 12 | make 13 | sudo make install 14 | echo "extension=redis.so" > /etc/php5/conf.d/redis.ini --------------------------------------------------------------------------------