├── phpunit.php
├── .gitignore
├── .travis.yml
├── docker-compose.yml
├── changelog.md
├── .github
└── CODEOWNERS
├── Makefile
├── examples
├── RedisConsumerExample.php
└── RedisPublishExample.php
├── tests
├── Mocks
│ └── MockRedisPubSubLoop.php
└── RedisPubSubAdapterTest.php
├── composer.json
├── phpunit.xml
├── LICENSE
├── Dockerfile
├── src
└── RedisPubSubAdapter.php
├── .styleci.yml
└── README.md
/phpunit.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 | ./tests/
17 |
18 |
19 |
20 |
21 | ./src/
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # php-pubsub-redis
2 |
3 | A Redis adapter for the [php-pubsub](https://github.com/Superbalist/php-pubsub) package.
4 |
5 | [](https://twitter.com/superbalist)
6 | [](https://travis-ci.org/Superbalist/php-pubsub-redis)
7 | [](https://styleci.io/repos/67252513)
8 | [](LICENSE)
9 | [](https://packagist.org/packages/superbalist/php-pubsub-redis)
10 | [](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 |
--------------------------------------------------------------------------------