├── .github └── CODEOWNERS ├── .gitignore ├── .styleci.yml ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── changelog.md ├── composer.json ├── docker-compose.yml ├── examples ├── RedisConsumerExample.php └── RedisPublishExample.php ├── phpunit.php ├── phpunit.xml ├── src └── RedisPubSubAdapter.php └── tests ├── Mocks └── MockRedisPubSubLoop.php └── RedisPubSubAdapterTest.php /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | # More details are here: https://help.github.com/articles/about-codeowners/ 4 | # The '*' pattern is global owners. 5 | # Order is important. The last matching pattern has the most precedence. 6 | 7 | * @Superbalist/core 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | composer.lock 3 | vendor 4 | bin 5 | coverage 6 | coverage.xml -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: psr2 2 | 3 | enabled: 4 | - alpha_ordered_imports 5 | - binary_operator_spaces 6 | - blank_line_after_opening_tag 7 | - cast_spaces 8 | - concat_with_spaces 9 | - const_visibility_required 10 | - declare_equal_normalize 11 | - function_typehint_space 12 | - hash_to_slash_comment 13 | - heredoc_to_nowdoc 14 | - include 15 | - lowercase_cast 16 | - method_separation 17 | - native_function_casing 18 | - new_with_braces 19 | - no_blank_lines_after_class_opening 20 | - no_blank_lines_after_phpdoc 21 | - no_blank_lines_after_return 22 | - no_blank_lines_after_throw 23 | - no_blank_lines_between_imports 24 | - no_blank_lines_between_traits 25 | - no_empty_statement 26 | - no_extra_consecutive_blank_lines 27 | - no_leading_import_slash 28 | - no_leading_namespace_whitespace 29 | - no_multiline_whitespace_around_double_arrow 30 | - no_short_bool_cast 31 | - no_short_echo_tag 32 | - no_singleline_whitespace_before_semicolons 33 | - no_spaces_inside_offset 34 | - no_spaces_outside_offset 35 | - no_trailing_comma_in_list_call 36 | - no_trailing_comma_in_singleline_array 37 | - no_unneeded_control_parentheses 38 | - no_unreachable_default_argument_value 39 | - no_unused_imports 40 | - no_useless_return 41 | - no_whitespace_before_comma_in_array 42 | - no_whitespace_in_blank_line 43 | - normalize_index_brace 44 | - object_operator_without_whitespace 45 | - phpdoc_add_missing_param_annotation 46 | - phpdoc_indent 47 | - phpdoc_inline_tag 48 | - phpdoc_link_to_see 49 | - phpdoc_no_access 50 | - phpdoc_no_empty_return 51 | - phpdoc_no_package 52 | - phpdoc_order 53 | - phpdoc_property 54 | - phpdoc_scalar 55 | - phpdoc_separation 56 | - phpdoc_single_line_var_spacing 57 | - phpdoc_to_comment 58 | - phpdoc_trim 59 | - phpdoc_type_to_var 60 | - phpdoc_types 61 | - phpdoc_var_without_name 62 | - print_to_echo 63 | - self_accessor 64 | - short_array_syntax 65 | - short_scalar_cast 66 | - single_blank_line_before_namespace 67 | - single_quote 68 | - space_after_semicolon 69 | - standardize_not_equals 70 | - ternary_operator_spaces 71 | - trailing_comma_in_multiline_array 72 | - trim_array_spaces 73 | - unalign_double_arrow 74 | - unalign_equals 75 | - unary_operator_spaces 76 | - whitespace_after_comma_in_array 77 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | - 7.1 7 | - hhvm 8 | - nightly 9 | 10 | before_script: 11 | - composer install 12 | 13 | script: ./vendor/bin/phpunit --configuration phpunit.xml 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.0-fpm 2 | MAINTAINER Superbalist 3 | 4 | RUN mkdir /opt/php-pubsub 5 | WORKDIR /opt/php-pubsub 6 | 7 | # Packages 8 | RUN apt-get update \ 9 | && DEBIAN_FRONTEND=noninteractive apt-get install -y \ 10 | git \ 11 | zlib1g-dev \ 12 | unzip \ 13 | && rm -r /var/lib/apt/lists/* 14 | 15 | # PHP Extensions 16 | RUN docker-php-ext-install -j$(nproc) zip 17 | 18 | # Composer 19 | ENV COMPOSER_HOME /composer 20 | ENV PATH /composer/vendor/bin:$PATH 21 | ENV COMPOSER_ALLOW_SUPERUSER 1 22 | RUN curl -o /tmp/composer-setup.php https://getcomposer.org/installer \ 23 | && curl -o /tmp/composer-setup.sig https://composer.github.io/installer.sig \ 24 | && php -r "if (hash('SHA384', file_get_contents('/tmp/composer-setup.php')) !== trim(file_get_contents('/tmp/composer-setup.sig'))) { unlink('/tmp/composer-setup.php'); echo 'Invalid installer' . PHP_EOL; exit(1); }" \ 25 | && php /tmp/composer-setup.php --no-ansi --install-dir=/usr/local/bin --filename=composer --version=1.1.0 && rm -rf /tmp/composer-setup.php 26 | 27 | # Install Composer Application Dependencies 28 | COPY composer.json /opt/php-pubsub/ 29 | RUN composer install --no-autoloader --no-scripts --no-interaction 30 | 31 | COPY src /opt/php-pubsub/src 32 | COPY examples /opt/php-pubsub/examples 33 | 34 | RUN composer dump-autoload --no-interaction 35 | 36 | CMD ["/bin/bash"] 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Superbalist.com a division of Takealot Online (Pty) Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: tests 2 | 3 | up: 4 | @docker-compose rm -f 5 | @docker-compose pull 6 | @sed -e "s/HOSTIP/$$(docker-machine ip)/g" docker-compose.yml | docker-compose --file - up --build -d 7 | @docker-compose run php-pubsub-redis /bin/bash 8 | 9 | down: 10 | @docker-compose stop -t 1 11 | 12 | tests: 13 | @./vendor/bin/phpunit --configuration phpunit.xml 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php-pubsub-redis 2 | 3 | A Redis adapter for the [php-pubsub](https://github.com/Superbalist/php-pubsub) package. 4 | 5 | [![Author](http://img.shields.io/badge/author-@superbalist-blue.svg?style=flat-square)](https://twitter.com/superbalist) 6 | [![Build Status](https://img.shields.io/travis/Superbalist/php-pubsub-redis/master.svg?style=flat-square)](https://travis-ci.org/Superbalist/php-pubsub-redis) 7 | [![StyleCI](https://styleci.io/repos/67252513/shield?branch=master)](https://styleci.io/repos/67252513) 8 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) 9 | [![Packagist Version](https://img.shields.io/packagist/v/superbalist/php-pubsub-redis.svg?style=flat-square)](https://packagist.org/packages/superbalist/php-pubsub-redis) 10 | [![Total Downloads](https://img.shields.io/packagist/dt/superbalist/php-pubsub-redis.svg?style=flat-square)](https://packagist.org/packages/superbalist/php-pubsub-redis) 11 | 12 | 13 | ## Installation 14 | 15 | ```bash 16 | composer require superbalist/php-pubsub-redis 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```php 22 | $client = new Predis\Client([ 23 | 'scheme' => 'tcp', 24 | 'host' => '127.0.0.1', 25 | 'port' => 6379, 26 | 'database' => 0, 27 | 'read_write_timeout' => 0 28 | ]); 29 | 30 | $adapter = new \Superbalist\PubSub\Redis\RedisPubSubAdapter($client); 31 | 32 | // consume messages 33 | // note: this is a blocking call 34 | $adapter->subscribe('my_channel', function ($message) { 35 | var_dump($message); 36 | }); 37 | 38 | // publish messages 39 | $adapter->publish('my_channel', 'HELLO WORLD'); 40 | $adapter->publish('my_channel', ['hello' => 'world']); 41 | $adapter->publish('my_channel', 1); 42 | $adapter->publish('my_channel', false); 43 | 44 | // publish multiple messages 45 | $messages = [ 46 | 'message 1', 47 | 'message 2', 48 | ]; 49 | $adapter->publishBatch('my_channel', $messages); 50 | ``` 51 | 52 | ## Examples 53 | 54 | The library comes with [examples](examples) for the adapter and a [Dockerfile](Dockerfile) for 55 | running the example scripts. 56 | 57 | Run `make up`. 58 | 59 | You will start at a `bash` prompt in the `/opt/php-pubsub` directory. 60 | 61 | If you need another shell to publish a message to a blocking consumer, you can run `docker-compose run php-pubsub-redis /bin/bash` 62 | 63 | To run the examples: 64 | ```bash 65 | $ php examples/RedisConsumerExample.php 66 | $ php examples/RedisPublishExample.php (in a separate shell) 67 | ``` 68 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.0.0 - 2017-05-16 4 | 5 | * Bump up to superbalist/php-pubsub ^2.0 6 | * Add new publishBatch method to RedisPubSubAdapter 7 | 8 | ## 1.0.1 - 2016-09-09 9 | 10 | * Allow for older versions of predis/predis - ^0.8|^1.0|^1.1 11 | 12 | ## 1.0.0 - 2016-09-02 13 | 14 | * Initial release -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "superbalist/php-pubsub-redis", 3 | "description": "A Redis adapter for the php-pubsub package", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Superbalist.com a division of Takealot Online (Pty) Ltd", 8 | "email": "info@superbalist.com" 9 | } 10 | ], 11 | "require": { 12 | "php": ">=5.6.0", 13 | "predis/predis": "^0.8|^1.0|^1.1", 14 | "superbalist/php-pubsub": "^2.0" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "Superbalist\\PubSub\\Redis\\": "src/", 19 | "Tests\\": "tests/" 20 | } 21 | }, 22 | "extra": { 23 | "branch-alias": { 24 | "dev-master": "1.0-dev" 25 | } 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "^5.5", 29 | "mockery/mockery": "^0.9.5" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | php-pubsub-redis: 4 | build: . 5 | depends_on: 6 | - "redis" 7 | volumes: 8 | - ./src:/opt/php-pubsub/src 9 | - ./examples:/opt/php-pubsub/examples 10 | redis: 11 | image: redis:3.2 12 | ports: 13 | - "6379:6379" -------------------------------------------------------------------------------- /examples/RedisConsumerExample.php: -------------------------------------------------------------------------------- 1 | 'tcp', 7 | 'host' => 'redis', 8 | 'port' => 6379, 9 | 'database' => 0, 10 | 'read_write_timeout' => 0, 11 | ]); 12 | 13 | $adapter = new \Superbalist\PubSub\Redis\RedisPubSubAdapter($client); 14 | 15 | $adapter->subscribe('my_channel', function ($message) { 16 | var_dump($message); 17 | }); 18 | -------------------------------------------------------------------------------- /examples/RedisPublishExample.php: -------------------------------------------------------------------------------- 1 | 'tcp', 7 | 'host' => 'redis', 8 | 'port' => 6379, 9 | 'database' => 0, 10 | 'read_write_timeout' => 0, 11 | ]); 12 | 13 | $adapter = new \Superbalist\PubSub\Redis\RedisPubSubAdapter($client); 14 | 15 | $adapter->publish('my_channel', 'HELLO WORLD'); 16 | $adapter->publish('my_channel', ['hello' => 'world']); 17 | $adapter->publish('my_channel', 1); 18 | $adapter->publish('my_channel', false); 19 | -------------------------------------------------------------------------------- /phpunit.php: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | ./tests/ 17 | 18 | 19 | 20 | 21 | ./src/ 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/RedisPubSubAdapter.php: -------------------------------------------------------------------------------- 1 | client = $client; 22 | } 23 | 24 | /** 25 | * Return the Redis client. 26 | * 27 | * @return Client 28 | */ 29 | public function getClient() 30 | { 31 | return $this->client; 32 | } 33 | 34 | /** 35 | * Subscribe a handler to a channel. 36 | * 37 | * @param string $channel 38 | * @param callable $handler 39 | */ 40 | public function subscribe($channel, callable $handler) 41 | { 42 | $loop = $this->client->pubSubLoop(); 43 | 44 | $loop->subscribe($channel); 45 | 46 | foreach ($loop as $message) { 47 | /** @var \stdClass $message */ 48 | if ($message->kind === 'message') { 49 | call_user_func($handler, Utils::unserializeMessagePayload($message->payload)); 50 | } 51 | } 52 | 53 | unset($loop); 54 | } 55 | 56 | /** 57 | * Publish a message to a channel. 58 | * 59 | * @param string $channel 60 | * @param mixed $message 61 | */ 62 | public function publish($channel, $message) 63 | { 64 | $this->client->publish($channel, Utils::serializeMessage($message)); 65 | } 66 | 67 | /** 68 | * Publish multiple messages to a channel. 69 | * 70 | * @param string $channel 71 | * @param array $messages 72 | */ 73 | public function publishBatch($channel, array $messages) 74 | { 75 | foreach ($messages as $message) { 76 | $this->publish($channel, $message); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/Mocks/MockRedisPubSubLoop.php: -------------------------------------------------------------------------------- 1 | kind = 'subscribe'; 11 | $message1->payload = null; 12 | 13 | $message2 = new \stdClass(); 14 | $message2->kind = 'message'; 15 | $message2->payload = '{ "hello": "world" }'; 16 | 17 | parent::__construct([$message1, $message2]); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/RedisPubSubAdapterTest.php: -------------------------------------------------------------------------------- 1 | assertSame($client, $adapter->getClient()); 17 | } 18 | 19 | public function testSubscribe() 20 | { 21 | $loop = Mockery::mock('\Tests\Mocks\MockRedisPubSubLoop[subscribe]'); 22 | $loop->shouldReceive('subscribe') 23 | ->with('channel_name') 24 | ->once(); 25 | 26 | $client = Mockery::mock(Client::class); 27 | $client->shouldReceive('pubSubLoop') 28 | ->once() 29 | ->andReturn($loop); 30 | 31 | $adapter = new RedisPubSubAdapter($client); 32 | 33 | $handler1 = Mockery::mock(\stdClass::class); 34 | $handler1->shouldReceive('handle') 35 | ->with(['hello' => 'world']) 36 | ->once(); 37 | $adapter->subscribe('channel_name', [$handler1, 'handle']); 38 | } 39 | 40 | public function testPublish() 41 | { 42 | $client = Mockery::mock(Client::class); 43 | $client->shouldReceive('publish') 44 | ->withArgs([ 45 | 'channel_name', 46 | '{"hello":"world"}', 47 | ]) 48 | ->once(); 49 | 50 | $adapter = new RedisPubSubAdapter($client); 51 | $adapter->publish('channel_name', ['hello' => 'world']); 52 | } 53 | 54 | public function testPublishBatch() 55 | { 56 | $client = Mockery::mock(Client::class); 57 | $client->shouldReceive('publish') 58 | ->withArgs([ 59 | 'channel_name', 60 | '"message1"', 61 | ]) 62 | ->once(); 63 | $client->shouldReceive('publish') 64 | ->withArgs([ 65 | 'channel_name', 66 | '"message2"', 67 | ]) 68 | ->once(); 69 | 70 | $adapter = new RedisPubSubAdapter($client); 71 | $messages = [ 72 | 'message1', 73 | 'message2', 74 | ]; 75 | $adapter->publishBatch('channel_name', $messages); 76 | } 77 | } 78 | --------------------------------------------------------------------------------