├── phpunit.php
├── .gitignore
├── docker-compose.yml
├── .travis.yml
├── .github
└── CODEOWNERS
├── Makefile
├── examples
├── GoogleCloudConsumerExample.php
└── GoogleCloudPublishExample.php
├── composer.json
├── phpunit.xml
├── LICENSE
├── changelog.md
├── Dockerfile
├── .styleci.yml
├── README.md
├── src
└── GoogleCloudPubSubAdapter.php
└── tests
└── GoogleCloudPubSubAdapterTest.php
/phpunit.php:
--------------------------------------------------------------------------------
1 | 'your-project-id-here',
9 | ]);
10 |
11 | $adapter = new \Superbalist\PubSub\GoogleCloud\GoogleCloudPubSubAdapter($client);
12 |
13 | $adapter->subscribe('my_channel', function ($message) {
14 | var_dump($message);
15 | });
16 |
--------------------------------------------------------------------------------
/examples/GoogleCloudPublishExample.php:
--------------------------------------------------------------------------------
1 | 'your-project-id-here',
9 | ]);
10 |
11 | $adapter = new \Superbalist\PubSub\GoogleCloud\GoogleCloudPubSubAdapter($client);
12 |
13 | $adapter->publish('my_channel', 'Hello World');
14 | $adapter->publish('my_channel', ['lorem' => 'ipsum']);
15 | $adapter->publish('my_channel', '{"blah": "bleh"}');
16 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "superbalist/php-pubsub-google-cloud",
3 | "description": "A Google Cloud 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 | "superbalist/php-pubsub": "^2.0",
14 | "google/cloud": "^0.95.0"
15 | },
16 | "autoload": {
17 | "psr-4": {
18 | "Superbalist\\PubSub\\GoogleCloud\\": "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.
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 5.2.0 - 2019-03-19
4 |
5 | * Add ability to use returnImmediately flag when pulling messages
6 |
7 | ## 5.1.0 - 2019-02-28
8 |
9 | * Bump up google/cloud requirement to ^0.95.0
10 |
11 | ## 5.0.1 - 2018-07-27
12 |
13 | * Allow setting of maximum number of messages to pull option
14 |
15 | ## 5.0.0 - 2017-07-25
16 |
17 | * Add support for using Google Cloud batch requests (aka background daemon)
18 |
19 | ## 4.0.1 - 2017-07-21
20 |
21 | * Allow for google/cloud ^0.29.0|^0.30.0|^0.31.0|^0.32.0|^0.33.0|^0.34.0|^0.35.0
22 |
23 | ## 4.0.0 - 2017-05-16
24 |
25 | * Allow for google/cloud ^0.26.0|^0.27.0|^0.28.0
26 | * Bump up to superbalist/php-pubsub ^2.0
27 | * Add new publishBatch method to GoogleCloudPubSubAdapter
28 |
29 | ## 3.0.1 - 2017-04-03
30 |
31 | * Fix to gRPC timeouts
32 | * Allow for google/cloud ^0.21.0|^0.22.0|^0.23.0|^0.24.0|^0.25.0
33 |
34 | ## 3.0.0 - 2017-01-03
35 |
36 | * Bump up google/cloud requirement to ^0.11.0|^0.12.0|^0.13.0|^0.20.0
37 |
38 | ## 2.0.2 - 2017-01-03
39 |
40 | * Allow for google/cloud ^0.10.0
41 |
42 | ## 2.0.1 - 2016-10-05
43 |
44 | * Fix to subscriber bug - client identifier needs to be unique across topics.
45 |
46 | ## 2.0.0 - 2016-10-05
47 |
48 | * Add new `$clientIdentifier` & `setClientIdentifier()` functionality to allow subscribers to use the same, or unique identifiers.
49 |
50 | ## 1.0.2 - 2016-09-26
51 |
52 | * Allow for google/cloud ^0.8.0 and ^0.9.0
53 |
54 | ## 1.0.1 - 2016-09-15
55 |
56 | * Ack messages individually after callable returns successfully
57 | * Add functionality to enable/disable auto topic & subscription creation
58 |
59 | ## 1.0.0 - 2016-09-05
60 |
61 | * Initial release
62 |
--------------------------------------------------------------------------------
/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 | && pecl install grpc \
18 | && docker-php-ext-enable grpc
19 |
20 | RUN pecl install protobuf \
21 | && docker-php-ext-enable protobuf
22 |
23 | # Composer
24 | ENV COMPOSER_HOME /composer
25 | ENV PATH /composer/vendor/bin:$PATH
26 | ENV COMPOSER_ALLOW_SUPERUSER 1
27 | RUN curl -o /tmp/composer-setup.php https://getcomposer.org/installer \
28 | && curl -o /tmp/composer-setup.sig https://composer.github.io/installer.sig \
29 | && 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); }" \
30 | && php /tmp/composer-setup.php --no-ansi --install-dir=/usr/local/bin --filename=composer --version=1.1.0 && rm -rf /tmp/composer-setup.php
31 |
32 | # Install Composer Application Dependencies
33 | COPY composer.json /opt/php-pubsub/
34 | RUN composer install --no-autoloader --no-scripts --no-interaction
35 |
36 | COPY src /opt/php-pubsub/src
37 | COPY your-gcloud-key.json /opt/php-pubsub
38 | COPY examples /opt/php-pubsub/examples
39 | COPY phpunit.php /opt/php-pubsub
40 | COPY phpunit.xml /opt/php-pubsub
41 | COPY tests /opt/php-pubsub/tests
42 |
43 | RUN composer dump-autoload --no-interaction
44 |
45 | CMD ["/bin/bash"]
46 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # php-pubsub-google-cloud
2 |
3 | A Google Cloud 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-google-cloud)
7 | [](LICENSE)
8 | [](https://packagist.org/packages/superbalist/php-pubsub-google-cloud)
9 | [](https://packagist.org/packages/superbalist/php-pubsub-google-cloud)
10 |
11 |
12 | ## Installation
13 |
14 | ```bash
15 | composer require superbalist/php-pubsub-google-cloud
16 | ```
17 |
18 | ## Usage
19 |
20 | ```php
21 | putenv('GOOGLE_APPLICATION_CREDENTIALS=' . __DIR__ . '/../your-gcloud-key.json');
22 |
23 | $client = new \Google\Cloud\PubSub\PubSubClient([
24 | 'projectId' => 'your-project-id-here',
25 | ]);
26 |
27 | $adapter = new \Superbalist\PubSub\GoogleCloud\GoogleCloudPubSubAdapter($client);
28 |
29 |
30 | // disable auto topic & subscription creation
31 | $adapter->setAutoCreateTopics(false); // this is true by default
32 | $adapter->setAutoCreateSubscriptions(false); // this is true by default
33 |
34 | // set a unique client identifier for the subscriber
35 | $adapter->setClientIdentifier('search_service');
36 |
37 | // consume messages
38 | // note: this is a blocking call
39 | $adapter->subscribe('my_channel', function ($message) {
40 | var_dump($message);
41 | });
42 |
43 | // publish messages
44 | $adapter->publish('my_channel', 'HELLO WORLD');
45 | $adapter->publish('my_channel', json_encode(['hello' => 'world']));
46 | $adapter->publish('my_channel', 1);
47 | $adapter->publish('my_channel', false);
48 | ```
49 |
50 | ## gRPC Support
51 |
52 | Google Cloud PHP v0.12.0 added support for communication over the gRPC protocol.
53 |
54 | > gRPC is great for high-performance, low-latency applications, and is highly recommended in cases where performance and latency are concerns.
55 |
56 | The library will automatically choose gRPC over REST if all dependencies are installed.
57 | * [gRPC PECL extension](https://pecl.php.net/package/gRPC)
58 | * [google/proto-client-php composer package](https://github.com/googleapis/gax-php)
59 | * [googleapis/proto-client-php composer package](https://github.com/googleapis/proto-client-php)
60 |
61 | ```bash
62 | pecl install grpc
63 |
64 | composer require google/gax
65 | composer require google/proto-client
66 | ```
67 |
68 | ## Background Batch Message Support
69 |
70 | Google Cloud v0.33.0 added support for queueing messages and publishing in the background. This is available in
71 | version 5+ of this package which requires a min version of google/cloud ^0.33.0.
72 |
73 | You can enable background batch messaging by setting `$backgroundBatching` to `true` when constructing the
74 | `GoogleCloudPubSubAdapter` or by calling `setBackgroundBatching(true)` on an existing adapter.
75 |
76 | If the [semaphore](http://php.net/manual/en/book.sem.php) and [pcntl](http://php.net/manual/en/book.pcntl.php) PHP extensions are
77 | enabled AND the `IS_BATCH_DAEMON_RUNNING` ENV var is set to `true`, the library will queue messages to be published by
78 | the [Batch Daemon](https://github.com/GoogleCloudPlatform/google-cloud-php/blob/master/src/Core/Batch/BatchDaemon.php).
79 | The Batch Daemon needs to be manually run as a long-lived background process.
80 |
81 | For all other cases, messages will be queued in memory and will be published before the script terminates using a
82 | vendor registered shutdown handler.
83 |
84 | **Please Note**
85 |
86 | This is marked by google/cloud as an experimental feature & may change before release in backwards-incompatible ways.
87 |
88 | ## Examples
89 |
90 | The library comes with [examples](examples) for the adapter and a [Dockerfile](Dockerfile) for
91 | running the example scripts.
92 |
93 | Run `make up`.
94 |
95 | You will start at a `bash` prompt in the `/opt/php-pubsub` directory.
96 |
97 | If you need another shell to publish a message to a blocking consumer, you can run `docker-compose run php-pubsub-google-cloud /bin/bash`
98 |
99 | To run the examples:
100 | ```bash
101 | $ php examples/GoogleCloudConsumerExample.php
102 | $ php examples/GoogleCloudPublishExample.php (in a separate shell)
103 | ```
104 |
--------------------------------------------------------------------------------
/src/GoogleCloudPubSubAdapter.php:
--------------------------------------------------------------------------------
1 | client = $client;
72 | $this->clientIdentifier = $clientIdentifier;
73 | $this->autoCreateTopics = $autoCreateTopics;
74 | $this->autoCreateSubscriptions = $autoCreateSubscriptions;
75 | $this->backgroundBatching = $backgroundBatching;
76 | $this->maxMessages = $maxMessages;
77 | $this->returnImmediately = $returnImmediately;
78 | $this->returnImmediatelyPause = (int) $returnImmediatelyPause;
79 | }
80 |
81 | /**
82 | * Return the Google PubSubClient.
83 | *
84 | * @return PubSubClient
85 | */
86 | public function getClient()
87 | {
88 | return $this->client;
89 | }
90 |
91 | /**
92 | * Set the unique client identifier.
93 | *
94 | * The client identifier is used when creating a subscription to a topic.
95 | *
96 | * A topic can have multiple subscribers connected.
97 | * If all subscribers use the same client identifier, the messages will load-balance across them.
98 | * If all subscribers have different client identifiers, the messages will be dispatched all of them.
99 | *
100 | * @param string $clientIdentifier
101 | */
102 | public function setClientIdentifier($clientIdentifier)
103 | {
104 | $this->clientIdentifier = $clientIdentifier;
105 | }
106 |
107 | /**
108 | * Return the unique client identifier.
109 | *
110 | * @return string
111 | */
112 | public function getClientIdentifier()
113 | {
114 | return $this->clientIdentifier;
115 | }
116 |
117 | /**
118 | * Set whether or not topics will be auto created.
119 | *
120 | * @param bool $autoCreateTopics
121 | */
122 | public function setAutoCreateTopics($autoCreateTopics)
123 | {
124 | $this->autoCreateTopics = $autoCreateTopics;
125 | }
126 |
127 | /**
128 | * Check whether or not topics will be auto created.
129 | *
130 | * @return bool
131 | */
132 | public function areTopicsAutoCreated()
133 | {
134 | return $this->autoCreateTopics;
135 | }
136 |
137 | /**
138 | * Set whether or not subscriptions will be auto created.
139 | *
140 | * @param bool $autoCreateSubscriptions
141 | */
142 | public function setAutoCreateSubscriptions($autoCreateSubscriptions)
143 | {
144 | $this->autoCreateSubscriptions = $autoCreateSubscriptions;
145 | }
146 |
147 | /**
148 | * Check whether or not subscriptions will be auto created.
149 | *
150 | * @return bool
151 | */
152 | public function areSubscriptionsAutoCreated()
153 | {
154 | return $this->autoCreateSubscriptions;
155 | }
156 |
157 | /**
158 | * Set if a pull should return immediately if there are no messages
159 | * @param bool $returnImmediately
160 | */
161 | public function setReturnImmediately($returnImmediately) {
162 | $this->returnImmediately = $returnImmediately;
163 | }
164 |
165 | /**
166 | * Return the return immediately configuration
167 | * @return bool
168 | */
169 | public function getReturnImmediately() {
170 | return $this->returnImmediately;
171 | }
172 |
173 | /**
174 | * Set the amount of time to pause between attempts to pull messages if return immediately is enabled.
175 | * Value is in microseconds
176 | *
177 | * @param int $returnImmediatelyPause
178 | */
179 | public function setReturnImmediatelyPause($returnImmediatelyPause) {
180 | $this->returnImmediatelyPause = (int) $returnImmediatelyPause;
181 | }
182 |
183 | /**
184 | * Return the return immediately pause configuration
185 | * @return int
186 | */
187 | public function getReturnImmediatelyPause() {
188 | return $this->returnImmediatelyPause;
189 | }
190 |
191 | /**
192 | * Set whether or not background batching is enabled.
193 | *
194 | * This is available from Google Cloud 0.33+ - https://github.com/GoogleCloudPlatform/google-cloud-php/releases/tag/v0.33.0
195 | *
196 | * If the http://php.net/manual/en/book.sem.php and http://php.net/manual/en/book.pcntl.php extensions are enabled
197 | * AND the IS_BATCH_DAEMON_RUNNING ENV var is set to true, the library will queue messages to be published by the
198 | * Batch Daemon (https://github.com/GoogleCloudPlatform/google-cloud-php/blob/master/src/Core/Batch/BatchDaemon.php)
199 | *
200 | * For all other cases, messages will be queued in memory and will be published before the script terminates using
201 | * a vendor registered shutdown handler.
202 | *
203 | * @param bool $backgroundBatching
204 | */
205 | public function setBackgroundBatching($backgroundBatching)
206 | {
207 | $this->backgroundBatching = $backgroundBatching;
208 | }
209 |
210 | /**
211 | * Check whether or not background batching is enabled.
212 | *
213 | * @return bool
214 | */
215 | public function isBackgroundBatchingEnabled()
216 | {
217 | return $this->backgroundBatching;
218 | }
219 |
220 | /**
221 | * Max messages to pull at a time.
222 | * https://googlecloudplatform.github.io/google-cloud-php/#/docs/google-cloud/v0.35.0/pubsub/subscription?method=pull
223 | *
224 | * @param int $maxMessages
225 | */
226 | public function setMaxMessages($maxMessages)
227 | {
228 | $this->maxMessages = $maxMessages;
229 | }
230 |
231 | /**
232 | * Subscribe a handler to a channel.
233 | *
234 | * @param string $channel
235 | * @param callable $handler
236 | */
237 | public function subscribe($channel, callable $handler)
238 | {
239 | $subscription = $this->getSubscriptionForChannel($channel);
240 |
241 | $isSubscriptionLoopActive = true;
242 | $isPauseEnabled = $this->returnImmediately && ($this->returnImmediatelyPause > 0);
243 |
244 | while ($isSubscriptionLoopActive) {
245 | $messages = $subscription->pull([
246 | 'grpcOptions' => [
247 | 'timeoutMillis' => null,
248 | ],
249 | 'maxMessages' => $this->maxMessages,
250 | 'returnImmediately' => $this->returnImmediately,
251 | ]);
252 | if ($isPauseEnabled && empty($messages)) {
253 | usleep($this->returnImmediatelyPause);
254 | continue;
255 | }
256 | foreach ($messages as $message) {
257 | /** @var Message $message */
258 | $payload = Utils::unserializeMessagePayload($message->data());
259 |
260 | if ($payload === 'unsubscribe') {
261 | $isSubscriptionLoopActive = false;
262 | } else {
263 | call_user_func($handler, $payload);
264 | }
265 |
266 | $subscription->acknowledge($message);
267 | }
268 | }
269 | }
270 |
271 | /**
272 | * Publish a message to a channel.
273 | *
274 | * @param string $channel
275 | * @param mixed $message
276 | */
277 | public function publish($channel, $message)
278 | {
279 | $topic = $this->getTopicForChannel($channel);
280 | $payload = Utils::serializeMessage($message);
281 |
282 | if ($this->backgroundBatching) {
283 | $topic->batchPublisher()->publish(['data' => $payload]);
284 | } else {
285 | $topic->publish(['data' => $payload]);
286 | }
287 | }
288 |
289 | /**
290 | * Publish multiple messages to a channel.
291 | *
292 | * @param string $channel
293 | * @param array $messages
294 | */
295 | public function publishBatch($channel, array $messages)
296 | {
297 | $topic = $this->getTopicForChannel($channel);
298 | $messages = array_map(function ($message) {
299 | return ['data' => Utils::serializeMessage($message)];
300 | }, $messages);
301 |
302 | if ($this->backgroundBatching) {
303 | $batchPublisher = $topic->batchPublisher();
304 | foreach ($messages as $message) {
305 | $batchPublisher->publish($message);
306 | }
307 | } else {
308 | $topic->publishBatch($messages);
309 | }
310 | }
311 |
312 | /**
313 | * Return a `Topic` instance from a channel name.
314 | *
315 | * If the topic doesn't exist, the topic is first created.
316 | *
317 | * @param string $channel
318 | *
319 | * @return \Google\Cloud\PubSub\Topic
320 | */
321 | protected function getTopicForChannel($channel)
322 | {
323 | $topic = $this->client->topic($channel);
324 | if ($this->autoCreateTopics && !$topic->exists()) {
325 | $topic->create();
326 | }
327 | return $topic;
328 | }
329 |
330 | /**
331 | * Return a `Subscription` instance from a channel name.
332 | *
333 | * If the subscription doesn't exist, the subscription is first created.
334 | *
335 | * @param string $channel
336 | *
337 | * @return \Google\Cloud\PubSub\Subscription
338 | */
339 | protected function getSubscriptionForChannel($channel)
340 | {
341 | $topic = $this->getTopicForChannel($channel);
342 | $clientIdentifier = $this->clientIdentifier ? $this->clientIdentifier : 'default';
343 | $clientIdentifier .= '.' . $channel;
344 | $subscription = $topic->subscription($clientIdentifier);
345 | if ($this->autoCreateSubscriptions && !$subscription->exists()) {
346 | $subscription->create();
347 | }
348 | return $subscription;
349 | }
350 | }
351 |
--------------------------------------------------------------------------------
/tests/GoogleCloudPubSubAdapterTest.php:
--------------------------------------------------------------------------------
1 | assertSame($client, $adapter->getClient());
21 | }
22 |
23 | public function testGetSetClientIdentifier()
24 | {
25 | $client = Mockery::mock(PubSubClient::class);
26 | $adapter = new GoogleCloudPubSubAdapter($client);
27 | $this->assertNull($adapter->getClientIdentifier());
28 |
29 | $adapter->setClientIdentifier('my_identifier');
30 | $this->assertEquals('my_identifier', $adapter->getClientIdentifier());
31 | }
32 |
33 | public function testGetSetAutoCreateTopics()
34 | {
35 | $client = Mockery::mock(PubSubClient::class);
36 | $adapter = new GoogleCloudPubSubAdapter($client);
37 | $this->assertTrue($adapter->areTopicsAutoCreated());
38 |
39 | $adapter->setAutoCreateTopics(false);
40 | $this->assertFalse($adapter->areTopicsAutoCreated());
41 | }
42 |
43 | public function testGetSetAutoCreateSubscriptions()
44 | {
45 | $client = Mockery::mock(PubSubClient::class);
46 | $adapter = new GoogleCloudPubSubAdapter($client);
47 | $this->assertTrue($adapter->areSubscriptionsAutoCreated());
48 |
49 | $adapter->setAutoCreateSubscriptions(false);
50 | $this->assertFalse($adapter->areSubscriptionsAutoCreated());
51 | }
52 |
53 | public function testGetSetBackgroundBatching()
54 | {
55 | $client = Mockery::mock(PubSubClient::class);
56 | $adapter = new GoogleCloudPubSubAdapter($client);
57 | $this->assertFalse($adapter->isBackgroundBatchingEnabled());
58 |
59 | $adapter->setBackgroundBatching(true);
60 | $this->assertTrue($adapter->isBackgroundBatchingEnabled());
61 |
62 | $adapter = new GoogleCloudPubSubAdapter($client, null, true, true, true);
63 | $this->assertTrue($adapter->isBackgroundBatchingEnabled());
64 | }
65 |
66 | public function testGetSetReturnImmediately()
67 | {
68 | $client = Mockery::mock(PubSubClient::class);
69 | $adapter = new GoogleCloudPubSubAdapter($client);
70 | $this->assertFalse($adapter->getReturnImmediately());
71 |
72 | $adapter->setReturnImmediately(true);
73 | $this->assertTrue($adapter->getReturnImmediately());
74 | }
75 |
76 | public function testGetSetReturnImmediatelyPause()
77 | {
78 | $client = Mockery::mock(PubSubClient::class);
79 | $adapter = new GoogleCloudPubSubAdapter($client);
80 | $this->assertEquals(500000, $adapter->getReturnImmediatelyPause());
81 |
82 | $adapter->setReturnImmediatelyPause(1000000);
83 | $this->assertEquals(1000000, $adapter->getReturnImmediatelyPause());
84 | }
85 |
86 | public function testPublishWhenTopicMustBeCreated()
87 | {
88 | $topic = Mockery::mock(Topic::class);
89 | $topic->shouldReceive('exists')
90 | ->once()
91 | ->andReturn(false);
92 | $topic->shouldReceive('create')
93 | ->once();
94 | $topic->shouldReceive('publish')
95 | ->with([
96 | 'data' => '{"hello":"world"}',
97 | ])
98 | ->once();
99 |
100 | $client = Mockery::mock(PubSubClient::class);
101 | $client->shouldReceive('topic')
102 | ->with('channel_name')
103 | ->once()
104 | ->andReturn($topic);
105 |
106 | $adapter = new GoogleCloudPubSubAdapter($client);
107 |
108 | $adapter->publish('channel_name', ['hello' => 'world']);
109 | }
110 |
111 | public function testPublishWhenTopicMustBeCreatedAndBackgroundBatchingIsEnabled()
112 | {
113 | $batchPublisher = Mockery::mock(BatchPublisher::class);
114 |
115 | $topic = Mockery::mock(Topic::class);
116 | $topic->shouldReceive('exists')
117 | ->once()
118 | ->andReturn(false);
119 | $topic->shouldReceive('create')
120 | ->once();
121 |
122 | $topic->shouldReceive('batchPublisher')
123 | ->once()
124 | ->andReturn($batchPublisher);
125 |
126 | $batchPublisher->shouldReceive('publish')
127 | ->with([
128 | 'data' => '{"hello":"world"}',
129 | ])
130 | ->once();
131 |
132 | $client = Mockery::mock(PubSubClient::class);
133 | $client->shouldReceive('topic')
134 | ->with('channel_name')
135 | ->once()
136 | ->andReturn($topic);
137 |
138 | $adapter = new GoogleCloudPubSubAdapter($client);
139 | $adapter->setBackgroundBatching(true);
140 |
141 | $adapter->publish('channel_name', ['hello' => 'world']);
142 | }
143 |
144 | public function testPublishWhenTopicExists()
145 | {
146 | $topic = Mockery::mock(Topic::class);
147 | $topic->shouldReceive('exists')
148 | ->once()
149 | ->andReturn(true);
150 | $topic->shouldNotHaveReceived('create');
151 | $topic->shouldReceive('publish')
152 | ->with([
153 | 'data' => '{"hello":"world"}',
154 | ])
155 | ->once();
156 |
157 | $client = Mockery::mock(PubSubClient::class);
158 | $client->shouldReceive('topic')
159 | ->with('channel_name')
160 | ->once()
161 | ->andReturn($topic);
162 |
163 | $adapter = new GoogleCloudPubSubAdapter($client);
164 |
165 | $adapter->publish('channel_name', ['hello' => 'world']);
166 | }
167 |
168 | public function testPublishWhenTopicExistsAndBackgroundBatchingIsEnabled()
169 | {
170 | $batchPublisher = Mockery::mock(BatchPublisher::class);
171 |
172 | $topic = Mockery::mock(Topic::class);
173 | $topic->shouldReceive('exists')
174 | ->once()
175 | ->andReturn(true);
176 | $topic->shouldNotHaveReceived('create');
177 | $topic->shouldReceive('batchPublisher')
178 | ->once()
179 | ->andReturn($batchPublisher);
180 |
181 | $batchPublisher->shouldReceive('publish')
182 | ->with([
183 | 'data' => '{"hello":"world"}',
184 | ])
185 | ->once();
186 |
187 | $client = Mockery::mock(PubSubClient::class);
188 | $client->shouldReceive('topic')
189 | ->with('channel_name')
190 | ->once()
191 | ->andReturn($topic);
192 |
193 | $adapter = new GoogleCloudPubSubAdapter($client);
194 | $adapter->setBackgroundBatching(true);
195 |
196 | $adapter->publish('channel_name', ['hello' => 'world']);
197 | }
198 |
199 | public function testPublishWhenAutoTopicCreationIsDisabled()
200 | {
201 | $topic = Mockery::mock(Topic::class);
202 | $topic->shouldNotHaveReceived('exists');
203 | $topic->shouldNotHaveReceived('create');
204 | $topic->shouldReceive('publish')
205 | ->with([
206 | 'data' => '{"hello":"world"}',
207 | ])
208 | ->once();
209 |
210 | $client = Mockery::mock(PubSubClient::class);
211 | $client->shouldReceive('topic')
212 | ->with('channel_name')
213 | ->once()
214 | ->andReturn($topic);
215 |
216 | $adapter = new GoogleCloudPubSubAdapter($client, null, false);
217 |
218 | $adapter->publish('channel_name', ['hello' => 'world']);
219 | }
220 |
221 | public function testPublishWhenAutoTopicCreationIsDisabledAndBackgroundBatchingIsEnabled()
222 | {
223 | $batchPublisher = Mockery::mock(BatchPublisher::class);
224 |
225 | $topic = Mockery::mock(Topic::class);
226 | $topic->shouldNotHaveReceived('exists');
227 | $topic->shouldNotHaveReceived('create');
228 | $topic->shouldReceive('batchPublisher')
229 | ->once()
230 | ->andReturn($batchPublisher);
231 |
232 | $batchPublisher->shouldReceive('publish')
233 | ->with([
234 | 'data' => '{"hello":"world"}',
235 | ])
236 | ->once();
237 |
238 | $client = Mockery::mock(PubSubClient::class);
239 | $client->shouldReceive('topic')
240 | ->with('channel_name')
241 | ->once()
242 | ->andReturn($topic);
243 |
244 | $adapter = new GoogleCloudPubSubAdapter($client, null, false);
245 | $adapter->setBackgroundBatching(true);
246 |
247 | $adapter->publish('channel_name', ['hello' => 'world']);
248 | }
249 |
250 | public function testPublishBatch()
251 | {
252 | $topic = Mockery::mock(Topic::class);
253 | $topic->shouldReceive('exists')
254 | ->once()
255 | ->andReturn(true);
256 | $topic->shouldReceive('publishBatch')
257 | ->with([
258 | ['data' => '{"hello":"world"}'],
259 | ['data' => '"booo!"'],
260 | ])
261 | ->once();
262 |
263 | $client = Mockery::mock(PubSubClient::class);
264 | $client->shouldReceive('topic')
265 | ->with('channel_name')
266 | ->once()
267 | ->andReturn($topic);
268 |
269 | $adapter = new GoogleCloudPubSubAdapter($client);
270 |
271 | $messages = [
272 | ['hello' => 'world'],
273 | 'booo!',
274 | ];
275 | $adapter->publishBatch('channel_name', $messages);
276 | }
277 |
278 | public function testPublishBatchWhenBackgroundBatchingIsEnabled()
279 | {
280 | $batchPublisher = Mockery::mock(BatchPublisher::class);
281 |
282 | $topic = Mockery::mock(Topic::class);
283 | $topic->shouldReceive('exists')
284 | ->once()
285 | ->andReturn(true);
286 | $topic->shouldReceive('batchPublisher')
287 | ->once()
288 | ->andReturn($batchPublisher);
289 |
290 | $batchPublisher->shouldReceive('publish')
291 | ->with([
292 | 'data' => '{"hello":"world"}',
293 | ])
294 | ->once();
295 | $batchPublisher->shouldReceive('publish')
296 | ->with([
297 | 'data' => '"booo!"',
298 | ])
299 | ->once();
300 |
301 | $client = Mockery::mock(PubSubClient::class);
302 | $client->shouldReceive('topic')
303 | ->with('channel_name')
304 | ->once()
305 | ->andReturn($topic);
306 |
307 | $adapter = new GoogleCloudPubSubAdapter($client);
308 | $adapter->setBackgroundBatching(true);
309 |
310 | $messages = [
311 | ['hello' => 'world'],
312 | 'booo!',
313 | ];
314 | $adapter->publishBatch('channel_name', $messages);
315 | }
316 |
317 | public function testSubscribeWhenSubscriptionMustBeCreated()
318 | {
319 | $message1 = new Message(['data' => '{"hello":"world"}'], ['ackId' => 1]);
320 | $message2 = new Message(['data' => '"this is a string"'], ['ackId' => 2]);
321 | $message3 = new Message(['data' => '"unsubscribe"'], ['ackId' => 3]);
322 |
323 | $messageBatch1 = [
324 | $message1,
325 | $message2,
326 | ];
327 | $messageBatch2 = [
328 | $message3,
329 | ];
330 |
331 | $subscription = Mockery::mock(Subscription::class);
332 | $subscription->shouldReceive('exists')
333 | ->once()
334 | ->andReturn(false);
335 | $subscription->shouldReceive('create')
336 | ->once();
337 | $subscription->shouldReceive('pull')
338 | ->with([
339 | 'grpcOptions' => [
340 | 'timeoutMillis' => null,
341 | ],
342 | 'maxMessages' => 1000,
343 | 'returnImmediately' => false
344 | ])
345 | ->once()
346 | ->andReturn($messageBatch1);
347 |
348 | $subscription->shouldReceive('acknowledge')
349 | ->with($message1)
350 | ->once();
351 | $subscription->shouldReceive('acknowledge')
352 | ->with($message2)
353 | ->once();
354 | $subscription->shouldReceive('pull')
355 | ->with([
356 | 'grpcOptions' => [
357 | 'timeoutMillis' => null,
358 | ],
359 | 'maxMessages' => 1000,
360 | 'returnImmediately' => false
361 | ])
362 | ->once()
363 | ->andReturn($messageBatch2);
364 | $subscription->shouldReceive('acknowledge')
365 | ->with($message3)
366 | ->once();
367 |
368 | $topic = Mockery::mock(Topic::class);
369 | $topic->shouldReceive('exists')
370 | ->once()
371 | ->andReturn(true);
372 | $topic->shouldNotHaveReceived('create');
373 | $topic->shouldReceive('subscription')
374 | ->with('default.channel_name')
375 | ->once()
376 | ->andReturn($subscription);
377 |
378 | $client = Mockery::mock(PubSubClient::class);
379 | $client->shouldReceive('topic')
380 | ->with('channel_name')
381 | ->once()
382 | ->andReturn($topic);
383 |
384 | $adapter = new GoogleCloudPubSubAdapter($client);
385 |
386 | $handler1 = Mockery::mock(\stdClass::class);
387 | $handler1->shouldReceive('handle')
388 | ->with(['hello' => 'world'])
389 | ->once();
390 | $handler1->shouldReceive('handle')
391 | ->with('this is a string')
392 | ->once();
393 |
394 | $adapter->subscribe('channel_name', [$handler1, 'handle']);
395 | }
396 |
397 | public function testSubscribeWhenSubscriptionExists()
398 | {
399 | $message1 = new Message(['data' => '{"hello":"world"}'], ['ackId' => 1]);
400 | $message2 = new Message(['data' => '"this is a string"'], ['ackId' => 2]);
401 | $message3 = new Message(['data' => '"unsubscribe"'], ['ackId' => 3]);
402 |
403 | $messageBatch1 = [
404 | $message1,
405 | $message2,
406 | ];
407 | $messageBatch2 = [
408 | $message3,
409 | ];
410 |
411 | $subscription = Mockery::mock(Subscription::class);
412 | $subscription->shouldReceive('exists')
413 | ->once()
414 | ->andReturn(true);
415 | $subscription->shouldNotHaveReceived('create');
416 | $subscription->shouldReceive('pull')
417 | ->with([
418 | 'grpcOptions' => [
419 | 'timeoutMillis' => null,
420 | ],
421 | 'maxMessages' => 1000,
422 | 'returnImmediately' => false
423 | ])
424 | ->once()
425 | ->andReturn($messageBatch1);
426 | $subscription->shouldReceive('acknowledge')
427 | ->with($message1)
428 | ->once();
429 | $subscription->shouldReceive('acknowledge')
430 | ->with($message2)
431 | ->once();
432 | $subscription->shouldReceive('pull')
433 | ->with([
434 | 'grpcOptions' => [
435 | 'timeoutMillis' => null,
436 | ],
437 | 'maxMessages' => 1000,
438 | 'returnImmediately' => false
439 | ])
440 | ->once()
441 | ->andReturn($messageBatch2);
442 | $subscription->shouldReceive('acknowledge')
443 | ->with($message3)
444 | ->once();
445 |
446 | $topic = Mockery::mock(Topic::class);
447 | $topic->shouldReceive('exists')
448 | ->once()
449 | ->andReturn(true);
450 | $topic->shouldNotHaveReceived('create');
451 | $topic->shouldReceive('subscription')
452 | ->with('default.channel_name')
453 | ->once()
454 | ->andReturn($subscription);
455 |
456 | $client = Mockery::mock(PubSubClient::class);
457 | $client->shouldReceive('topic')
458 | ->with('channel_name')
459 | ->once()
460 | ->andReturn($topic);
461 |
462 | $adapter = new GoogleCloudPubSubAdapter($client);
463 |
464 | $handler1 = Mockery::mock(\stdClass::class);
465 | $handler1->shouldReceive('handle')
466 | ->with(['hello' => 'world'])
467 | ->once();
468 | $handler1->shouldReceive('handle')
469 | ->with('this is a string')
470 | ->once();
471 |
472 | $adapter->subscribe('channel_name', [$handler1, 'handle']);
473 | }
474 |
475 | public function testSubscribeWhenAutoTopicCreationIsDisabled()
476 | {
477 | $message1 = new Message(['data' => '{"hello":"world"}'], ['ackId' => 1]);
478 | $message2 = new Message(['data' => '"this is a string"'], ['ackId' => 2]);
479 | $message3 = new Message(['data' => '"unsubscribe"'], ['ackId' => 3]);
480 |
481 | $messageBatch1 = [
482 | $message1,
483 | $message2,
484 | ];
485 | $messageBatch2 = [
486 | $message3,
487 | ];
488 |
489 | $subscription = Mockery::mock(Subscription::class);
490 | $subscription->shouldNotHaveReceived('exists');
491 | $subscription->shouldNotHaveReceived('create');
492 | $subscription->shouldReceive('pull')
493 | ->with([
494 | 'grpcOptions' => [
495 | 'timeoutMillis' => null,
496 | ],
497 | 'maxMessages' => 1000,
498 | 'returnImmediately' => false
499 | ])
500 | ->once()
501 | ->andReturn($messageBatch1);
502 | $subscription->shouldReceive('acknowledge')
503 | ->with($message1)
504 | ->once();
505 | $subscription->shouldReceive('acknowledge')
506 | ->with($message2)
507 | ->once();
508 | $subscription->shouldReceive('pull')
509 | ->with([
510 | 'grpcOptions' => [
511 | 'timeoutMillis' => null,
512 | ],
513 | 'maxMessages' => 1000,
514 | 'returnImmediately' => false
515 | ])
516 | ->once()
517 | ->andReturn($messageBatch2);
518 | $subscription->shouldReceive('acknowledge')
519 | ->with($message3)
520 | ->once();
521 |
522 | $topic = Mockery::mock(Topic::class);
523 | $topic->shouldReceive('exists')
524 | ->once()
525 | ->andReturn(true);
526 | $topic->shouldNotHaveReceived('create');
527 | $topic->shouldReceive('subscription')
528 | ->with('default.channel_name')
529 | ->once()
530 | ->andReturn($subscription);
531 |
532 | $client = Mockery::mock(PubSubClient::class);
533 | $client->shouldReceive('topic')
534 | ->with('channel_name')
535 | ->once()
536 | ->andReturn($topic);
537 |
538 | $adapter = new GoogleCloudPubSubAdapter($client, null, true, false);
539 |
540 | $handler1 = Mockery::mock(\stdClass::class);
541 | $handler1->shouldReceive('handle')
542 | ->with(['hello' => 'world'])
543 | ->once();
544 | $handler1->shouldReceive('handle')
545 | ->with('this is a string')
546 | ->once();
547 |
548 | $adapter->subscribe('channel_name', [$handler1, 'handle']);
549 | }
550 |
551 | public function testSubscribeWhenReturnImmediatelyIsEnabled()
552 | {
553 | $message1 = new Message(['data' => '{"hello":"world"}'], ['ackId' => 1]);
554 | $message2 = new Message(['data' => '"this is a string"'], ['ackId' => 2]);
555 | $message3 = new Message(['data' => '"unsubscribe"'], ['ackId' => 3]);
556 |
557 | $messageBatch1 = [
558 | $message1,
559 | $message2,
560 | ];
561 |
562 | $messageBatch2 = [
563 | $message3,
564 | ];
565 |
566 | $subscription = Mockery::mock(Subscription::class);
567 | $subscription->shouldReceive('exists')
568 | ->once()
569 | ->andReturn(true);
570 | $subscription->shouldNotHaveReceived('create');
571 |
572 | $expectedPullOptions = [
573 | 'grpcOptions' => [
574 | 'timeoutMillis' => null,
575 | ],
576 | 'maxMessages' => 1000,
577 | 'returnImmediately' => true
578 | ];
579 |
580 | $subscription->shouldReceive('pull')
581 | ->with($expectedPullOptions)
582 | ->once()
583 | ->andReturn($messageBatch1);
584 | $subscription->shouldReceive('acknowledge')
585 | ->with($message1)
586 | ->once();
587 | $subscription->shouldReceive('acknowledge')
588 | ->with($message2)
589 | ->once();
590 |
591 | $subscription->shouldReceive('pull')
592 | ->with($expectedPullOptions)
593 | ->once()
594 | ->andReturn($messageBatch2);
595 | $subscription->shouldReceive('acknowledge')
596 | ->with($message3)
597 | ->once();
598 |
599 | $topic = Mockery::mock(Topic::class);
600 | $topic->shouldReceive('exists')
601 | ->once()
602 | ->andReturn(true);
603 | $topic->shouldNotHaveReceived('create');
604 | $topic->shouldReceive('subscription')
605 | ->with('default.channel_name')
606 | ->once()
607 | ->andReturn($subscription);
608 |
609 | $client = Mockery::mock(PubSubClient::class);
610 | $client->shouldReceive('topic')
611 | ->with('channel_name')
612 | ->once()
613 | ->andReturn($topic);
614 |
615 | $handler1 = Mockery::mock(\stdClass::class);
616 | $handler1->shouldReceive('handle')
617 | ->with(['hello' => 'world'])
618 | ->once();
619 | $handler1->shouldReceive('handle')
620 | ->with('this is a string')
621 | ->once();
622 |
623 | $adapter = new GoogleCloudPubSubAdapter($client);
624 | $adapter->setReturnImmediately(true);
625 | $adapter->subscribe('channel_name', [$handler1, 'handle']);
626 | }
627 | }
628 |
--------------------------------------------------------------------------------