├── .gitignore
├── phpcs.xml
├── src
├── PurgerInterface.php
├── DeleterInterface.php
├── ConsumerInterface.php
├── Message
│ ├── MessageFactoryInterface.php
│ ├── MessageInterface.php
│ ├── MessageFactory.php
│ └── Message.php
├── Adapter
│ ├── NamedInterface.php
│ ├── Exception
│ │ ├── FailedEnqueueException.php
│ │ ├── FailedRejectionException.php
│ │ ├── FailedAcknowledgementException.php
│ │ ├── FailedExtensionException.php
│ │ ├── MethodNotSupportedException.php
│ │ └── AdapterException.php
│ ├── AdapterInterface.php
│ ├── ArrayAdapter.php
│ ├── FirehoseAdapter.php
│ └── SqsAdapter.php
├── ProducerInterface.php
├── AbstractWorker.php
├── Handler
│ ├── EagerAcknowledgementHandler.php
│ ├── NullAcknowledgementHandler.php
│ ├── AbstractAcknowledgementHandler.php
│ ├── ResultAcknowledgementHandler.php
│ └── BatchAcknowledgementHandler.php
└── Client.php
├── tests
├── src
│ └── TestCase.php
├── unit
│ ├── Message
│ │ ├── MessageFactoryTest.php
│ │ └── MessageTest.php
│ ├── Adapter
│ │ ├── Exception
│ │ │ ├── FailedEnqueueExceptionTest.php
│ │ │ ├── FailedExtensionExceptionTest.php
│ │ │ ├── FailedRejectionExceptionTest.php
│ │ │ ├── FailedAcknowledgementExceptionTest.php
│ │ │ ├── MethodNotSupportedExceptionTest.php
│ │ │ └── AdapterExceptionTest.php
│ │ ├── FirehoseAdapterTest.php
│ │ ├── ArrayAdapterTest.php
│ │ └── SqsAdapterTest.php
│ ├── Handler
│ │ ├── ResultAcknowledgementHandlerTest.php
│ │ ├── NullAcknowledgementHandlerTest.php
│ │ ├── BatchAcknowledgementHandlerTest.php
│ │ └── EagerAcknowledgementHandlerTest.php
│ └── ClientTest.php
└── integration
│ ├── FirehoseIntegrationTest.php
│ ├── ArrayIntegrationTest.php
│ └── SqsIntegrationTest.php
├── .travis.yml
├── CHANGELOG.md
├── phpunit.xml.dist
├── LICENSE
├── composer.json
├── CONTRIBUTING.md
├── Makefile
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 |
3 | .idea
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | The graze PHP coding standard as defined in graze/standards.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/PurgerInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue;
17 |
18 | interface PurgerInterface
19 | {
20 | /**
21 | * Purge the Queue
22 | *
23 | * @return void
24 | */
25 | public function purge();
26 | }
27 |
--------------------------------------------------------------------------------
/src/DeleterInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue;
17 |
18 | interface DeleterInterface
19 | {
20 | /**
21 | * Delete the queue
22 | *
23 | * @return void
24 | */
25 | public function delete();
26 | }
27 |
--------------------------------------------------------------------------------
/src/ConsumerInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue;
17 |
18 | interface ConsumerInterface
19 | {
20 | /**
21 | * Listen for messages from the Queue
22 | *
23 | * @param callable $worker
24 | * @param int $limit
25 | */
26 | public function receive(callable $worker, $limit = 1);
27 | }
28 |
--------------------------------------------------------------------------------
/src/Message/MessageFactoryInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Message;
17 |
18 | interface MessageFactoryInterface
19 | {
20 | /**
21 | * @param string $body
22 | * @param array $options
23 | *
24 | * @return MessageInterface
25 | */
26 | public function createMessage($body, array $options = []);
27 | }
28 |
--------------------------------------------------------------------------------
/src/Adapter/NamedInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter;
17 |
18 | /**
19 | * Attached to adapters that may be name-able, such as an SQS queue name.
20 | * This is useful in some core pieces of code, especially when throwing useful exceptions.
21 | */
22 | interface NamedInterface
23 | {
24 | /**
25 | * @return string
26 | */
27 | public function getQueueName();
28 | }
29 |
--------------------------------------------------------------------------------
/tests/src/TestCase.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Test;
17 |
18 | use Hamcrest\Util;
19 | use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
20 |
21 | class TestCase extends \PHPUnit\Framework\TestCase
22 | {
23 | use MockeryPHPUnitIntegration;
24 |
25 | public static function setUpBeforeClass()
26 | {
27 | // Require the Hamcrest global functions.
28 | Util::registerGlobalFunctions();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Message/MessageInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Message;
17 |
18 | use Graze\DataStructure\Container\ContainerInterface;
19 |
20 | interface MessageInterface
21 | {
22 | /**
23 | * @return string
24 | */
25 | public function getBody();
26 |
27 | /**
28 | * @return ContainerInterface
29 | */
30 | public function getMetadata();
31 |
32 | /**
33 | * @return bool
34 | */
35 | public function isValid();
36 | }
37 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | dist: trusty
4 |
5 | cache:
6 | directories:
7 | - $HOME/.composer/cache/files
8 |
9 | php:
10 | - 5.6
11 | - 7.0
12 | - 7.1
13 | - 7.2
14 | - nightly
15 |
16 | env:
17 | - 'COMPOSER_FLAGS="--prefer-lowest --prefer-stable"'
18 | - 'COMPOSER_FLAGS=""'
19 |
20 | matrix:
21 | allow_failures:
22 | - php: nightly
23 |
24 | before_script:
25 | - composer config platform.php $(php -r "echo PHP_VERSION;")
26 | - travis_retry composer update --no-interaction --prefer-dist $COMPOSER_FLAGS
27 |
28 | script:
29 | - vendor/bin/phpcs -p --warning-severity=0 src/ tests/
30 | - vendor/bin/phpunit --coverage-clover=./tests/report/coverage.clover
31 |
32 | after_script:
33 | - test -f ./tests/report/coverage.clover && (wget https://scrutinizer-ci.com/ocular.phar; php ocular.phar code-coverage:upload --format=php-clover ./tests/report/coverage.clover)
34 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | This project adheres to [Semantic Versioning](http://semver.org/).
6 |
7 | ## [v0.2.0](https://github.com/graze/queue/compare/v0.1.1...v0.2.0)
8 |
9 | ### Added
10 |
11 | * `purge` method to `ProducerInterface`
12 |
13 | ### Updated
14 |
15 | * Updated `SqsAdapter` to support version 3.0 of the `aws/aws-sdk-php` dependency
16 |
17 | ### Fixed
18 |
19 | * Fixed an `OutOfBoundsException` in `ArrayAdapter` thrown when calling dequeue on an empty array
20 |
21 | ## [v0.1.1](https://github.com/graze/queue/compare/v0.1.0...v0.1.1)
22 |
23 | ### Fixed
24 |
25 | * Change `licence` to `license` in composer.json
26 |
27 | ## [v0.1.0](https://github.com/graze/queue/tree/v0.1.0)
28 |
29 | ### Added
30 |
31 | * `Graze\Queue` project :balloon:
32 | * Adapter `Graze\Queue\Adapter\ArrayAdapter`
33 | * Adapter `Graze\Queue\Adapter\SqsAdapter`
34 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 | tests/integration/
9 |
10 |
11 |
12 | tests/unit/
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | src/
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/ProducerInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue;
17 |
18 | use Graze\Queue\Message\MessageInterface;
19 |
20 | interface ProducerInterface
21 | {
22 | /**
23 | * Create a new message
24 | *
25 | * @param string $body
26 | * @param array $options
27 | *
28 | * @return MessageInterface
29 | */
30 | public function create($body, array $options = []);
31 |
32 | /**
33 | * Send the provided messages to the Queue
34 | *
35 | * @param MessageInterface[] $messages
36 | */
37 | public function send(array $messages);
38 | }
39 |
--------------------------------------------------------------------------------
/src/AbstractWorker.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue;
17 |
18 | use Closure;
19 | use Graze\Queue\Message\MessageInterface;
20 |
21 | abstract class AbstractWorker
22 | {
23 | /**
24 | * @param MessageInterface $message
25 | * @param Closure $done
26 | *
27 | * @return mixed
28 | */
29 | public function __invoke(MessageInterface $message, Closure $done)
30 | {
31 | return $this->execute($message, $done);
32 | }
33 |
34 | /**
35 | * @param MessageInterface $message
36 | * @param Closure $done
37 | */
38 | abstract protected function execute(MessageInterface $message, Closure $done);
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Nature Delivered 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
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "graze/queue",
3 | "description": ":postbox: Flexible abstraction for working with queues in PHP.",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Graze Developers",
8 | "email": "developers@graze.com",
9 | "homepage": "https://github.com/graze/queue/graphs/contributors"
10 | }
11 | ],
12 | "autoload": {
13 | "psr-4": {
14 | "Graze\\Queue\\": "src/",
15 | "Graze\\Queue\\Test\\": "tests/src/"
16 | }
17 | },
18 | "autoload-dev": {
19 | "classmap": [
20 | "tests/unit/",
21 | "tests/integration"
22 | ]
23 | },
24 | "require": {
25 | "php": "^5.5|^7",
26 | "graze/data-structure": "^2"
27 | },
28 | "require-dev": {
29 | "aws/aws-sdk-php": "^3",
30 | "hamcrest/hamcrest-php": "^2",
31 | "mockery/mockery": "^1",
32 | "phpunit/phpunit": "^5.7.21|^6|^7",
33 | "squizlabs/php_codesniffer": "^3",
34 | "graze/standards": "^2",
35 | "graze/hamcrest-test-listener": "^2|^3"
36 | },
37 | "suggest": {
38 | "aws/aws-sdk-php": "Required when using the SQS Adapter"
39 | },
40 | "config": {
41 | "platform": {
42 | "php": "7.2"
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Adapter/Exception/FailedEnqueueException.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter\Exception;
17 |
18 | use Exception;
19 | use Graze\Queue\Adapter\AdapterInterface;
20 | use Graze\Queue\Message\MessageInterface;
21 |
22 | /**
23 | * Exception to throw when an {@see \Graze\Queue\Adapter} fails to enqueue a message.
24 | */
25 | class FailedEnqueueException extends AdapterException
26 | {
27 | /**
28 | * @param AdapterInterface $adapter
29 | * @param MessageInterface[] $messages
30 | * @param array $debug
31 | * @param Exception $previous
32 | */
33 | public function __construct(
34 | AdapterInterface $adapter,
35 | array $messages,
36 | array $debug = [],
37 | Exception $previous = null
38 | ) {
39 | parent::__construct('Failed to enqueue messages', $adapter, $messages, $debug, $previous);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Adapter/Exception/FailedRejectionException.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter\Exception;
17 |
18 | use Exception;
19 | use Graze\Queue\Adapter\AdapterInterface;
20 | use Graze\Queue\Message\MessageInterface;
21 |
22 | /**
23 | * Exception to throw when a {@see \Graze\Queue\Handler} is unable to reject a message.
24 | */
25 | class FailedRejectionException extends AdapterException
26 | {
27 | /**
28 | * @param AdapterInterface $adapter
29 | * @param MessageInterface[] $messages
30 | * @param array $debug
31 | * @param Exception $previous
32 | */
33 | public function __construct(
34 | AdapterInterface $adapter,
35 | array $messages,
36 | array $debug = [],
37 | Exception $previous = null
38 | ) {
39 | parent::__construct('Failed to reject the messages', $adapter, $messages, $debug, $previous);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Adapter/Exception/FailedAcknowledgementException.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter\Exception;
17 |
18 | use Exception;
19 | use Graze\Queue\Adapter\AdapterInterface;
20 | use Graze\Queue\Message\MessageInterface;
21 |
22 | /**
23 | * Exception to throw when a {@see \Graze\Queue\Handler} is unable to acknowledge a message.
24 | */
25 | class FailedAcknowledgementException extends AdapterException
26 | {
27 | /**
28 | * @param AdapterInterface $adapter
29 | * @param MessageInterface[] $messages
30 | * @param array $debug
31 | * @param Exception $previous
32 | */
33 | public function __construct(
34 | AdapterInterface $adapter,
35 | array $messages,
36 | array $debug = [],
37 | Exception $previous = null
38 | ) {
39 | parent::__construct('Failed to acknowledge messages', $adapter, $messages, $debug, $previous);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Adapter/Exception/FailedExtensionException.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter\Exception;
17 |
18 | use Exception;
19 | use Graze\Queue\Adapter\AdapterInterface;
20 | use Graze\Queue\Message\MessageInterface;
21 |
22 | /**
23 | * Exception to throw when a {@see \Graze\Queue\Handler} is unable to delay a message.
24 | */
25 | class FailedExtensionException extends AdapterException
26 | {
27 | /**
28 | * @param AdapterInterface $adapter
29 | * @param MessageInterface[] $messages
30 | * @param array $debug
31 | * @param Exception $previous
32 | */
33 | public function __construct(
34 | AdapterInterface $adapter,
35 | array $messages,
36 | array $debug = [],
37 | Exception $previous = null
38 | ) {
39 | parent::__construct('Failed to extend processing time for messages', $adapter, $messages, $debug, $previous);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Message/MessageFactory.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Message;
17 |
18 | use Graze\DataStructure\Container\ImmutableContainer;
19 |
20 | final class MessageFactory implements MessageFactoryInterface
21 | {
22 | /**
23 | * @param string $body
24 | * @param array $options
25 | *
26 | * @return Message
27 | */
28 | public function createMessage($body, array $options = [])
29 | {
30 | return new Message($body, $this->getMetadata($options), $this->getValidator($options));
31 | }
32 |
33 | /**
34 | * @param array $options
35 | *
36 | * @return ImmutableContainer
37 | */
38 | protected function getMetadata(array $options)
39 | {
40 | $metadata = isset($options['metadata']) ? $options['metadata'] : [];
41 |
42 | return new ImmutableContainer($metadata);
43 | }
44 |
45 | /**
46 | * @param array $options
47 | *
48 | * @return \Closure
49 | */
50 | protected function getValidator(array $options)
51 | {
52 | return isset($options['validator']) ? $options['validator'] : function () {
53 | return true;
54 | };
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Message/Message.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Message;
17 |
18 | use Graze\DataStructure\Container\ContainerInterface;
19 |
20 | final class Message implements MessageInterface
21 | {
22 | /**
23 | * @var string
24 | */
25 | protected $body;
26 |
27 | /**
28 | * @var ContainerInterface
29 | */
30 | protected $metadata;
31 |
32 | /**
33 | * @var callable
34 | */
35 | protected $validator;
36 |
37 | /**
38 | * @param string $body
39 | * @param ContainerInterface $metadata
40 | * @param callable $validator
41 | */
42 | public function __construct($body, ContainerInterface $metadata, callable $validator)
43 | {
44 | $this->body = (string) $body;
45 | $this->metadata = $metadata;
46 | $this->validator = $validator;
47 | }
48 |
49 | /**
50 | * @return string
51 | */
52 | public function getBody()
53 | {
54 | return $this->body;
55 | }
56 |
57 | /**
58 | * @return ContainerInterface
59 | */
60 | public function getMetadata()
61 | {
62 | return $this->metadata;
63 | }
64 |
65 | /**
66 | * @return bool
67 | */
68 | public function isValid()
69 | {
70 | return (boolean) call_user_func($this->validator, $this);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Adapter/Exception/MethodNotSupportedException.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter\Exception;
17 |
18 | use Exception;
19 | use Graze\Queue\Adapter\AdapterInterface;
20 | use Graze\Queue\Message\MessageInterface;
21 |
22 | /**
23 | * Exception to throw when an implmentation of {@see \Graze\Queue\AdapterInterface}
24 | * does not support a method on {@see \Graze\Queue\Adapter\AdapterInterface}.
25 | */
26 | class MethodNotSupportedException extends AdapterException
27 | {
28 | /**
29 | * @var string
30 | */
31 | protected $method;
32 |
33 | /**
34 | * @param string $method
35 | * @param AdapterInterface $adapter
36 | * @param MessageInterface[] $messages
37 | * @param array $debug
38 | * @param Exception $previous
39 | */
40 | public function __construct(
41 | $method,
42 | AdapterInterface $adapter,
43 | array $messages,
44 | array $debug = [],
45 | Exception $previous = null
46 | ) {
47 | $this->method = $method;
48 |
49 | parent::__construct(
50 | sprintf('Method `%s` is not supported by this adapter', $method),
51 | $adapter,
52 | $messages,
53 | $debug,
54 | $previous
55 | );
56 | }
57 |
58 | /**
59 | * @return string
60 | */
61 | public function getMethod()
62 | {
63 | return $this->method;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Handler/EagerAcknowledgementHandler.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Handler;
17 |
18 | use Graze\Queue\Adapter\AdapterInterface;
19 | use Graze\Queue\Message\MessageInterface;
20 |
21 | class EagerAcknowledgementHandler extends AbstractAcknowledgementHandler
22 | {
23 | /**
24 | * @param MessageInterface $message
25 | * @param AdapterInterface $adapter
26 | * @param mixed $result
27 | */
28 | protected function acknowledge(
29 | MessageInterface $message,
30 | AdapterInterface $adapter,
31 | $result = null
32 | ) {
33 | $adapter->acknowledge([$message]);
34 | }
35 |
36 | /**
37 | * @param MessageInterface $message
38 | * @param AdapterInterface $adapter
39 | * @param int $duration
40 | */
41 | protected function extend(
42 | MessageInterface $message,
43 | AdapterInterface $adapter,
44 | $duration
45 | ) {
46 | $adapter->extend([$message], $duration);
47 | }
48 |
49 | /**
50 | * @param MessageInterface $message
51 | * @param AdapterInterface $adapter
52 | * @param mixed $result
53 | */
54 | protected function reject(
55 | MessageInterface $message,
56 | AdapterInterface $adapter,
57 | $result = null
58 | ) {
59 | $adapter->reject([$message]);
60 | }
61 |
62 | /**
63 | * @param AdapterInterface $adapter
64 | */
65 | protected function flush(AdapterInterface $adapter)
66 | {
67 | // Nothing to flush
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Handler/NullAcknowledgementHandler.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Handler;
17 |
18 | use Graze\Queue\Adapter\AdapterInterface;
19 | use Graze\Queue\Message\MessageInterface;
20 |
21 | class NullAcknowledgementHandler extends AbstractAcknowledgementHandler
22 | {
23 | /**
24 | * @param MessageInterface $message
25 | * @param AdapterInterface $adapter
26 | * @param mixed $result
27 | */
28 | protected function acknowledge(
29 | MessageInterface $message,
30 | AdapterInterface $adapter,
31 | $result = null
32 | ) {
33 | // Don't acknowledge.
34 | }
35 |
36 | /**
37 | * @param MessageInterface $message
38 | * @param AdapterInterface $adapter
39 | * @param int $duration Number of seconds to ensure that this message is not seen by any other clients
40 | */
41 | protected function extend(
42 | MessageInterface $message,
43 | AdapterInterface $adapter,
44 | $duration
45 | ) {
46 | // Don't delay
47 | }
48 |
49 | /**
50 | * @param MessageInterface $message
51 | * @param AdapterInterface $adapter
52 | * @param mixed $result
53 | */
54 | protected function reject(
55 | MessageInterface $message,
56 | AdapterInterface $adapter,
57 | $result = null
58 | ) {
59 | // Don't reject
60 | }
61 |
62 | /**
63 | * @param AdapterInterface $adapter
64 | */
65 | protected function flush(AdapterInterface $adapter)
66 | {
67 | // Nothing to flush.
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | The following guidelines for contribution should be followed if you want to submit a pull request.
4 |
5 | 1\. [Fork the repository](https://github.com/graze/queue/fork)
6 |
7 | 2\. Clone your new repository:
8 |
9 | $ git clone https://github.com//queue.git
10 |
11 | 3\. Set up the development environment with [Docker](https://www.docker.com/toolbox):
12 |
13 | $ make install
14 |
15 | 4\. Add tests for your change. Make your change. Make the tests pass:
16 |
17 | $ make test
18 |
19 | 5\. Push to your fork and [submit a pull request](https://github.com/graze/queue/compare)
20 |
21 | At this point you're waiting on us. We may suggest some changes or improvements or alternatives.
22 |
23 | ### Commit Messages
24 |
25 | Please also write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html):
26 |
27 | * Use the present tense ("Add feature" not "Added feature")
28 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
29 | * Limit the first line to 72 characters or less
30 | * Reference issues and pull requests liberally
31 | * Consider starting the commit message with an applicable emoji:
32 | * :art: `:art:` when improving the format/structure of the code
33 | * :racehorse: `:racehorse:` when improving performance
34 | * :non-potable_water: `:non-potable_water:` when plugging memory leaks
35 | * :memo: `:memo:` when writing docs
36 | * :bug: `:bug:` when fixing a bug
37 | * :fire: `:fire:` when removing code or files
38 | * :green_heart: `:green_heart:` when fixing the CI build
39 | * :white_check_mark: `:white_check_mark:` when adding tests
40 | * :lock: `:lock:` when dealing with security
41 | * :arrow_up: `:arrow_up:` when upgrading dependencies
42 | * :arrow_down: `:arrow_down:` when downgrading dependencies
43 | * :shirt: `:shirt:` when removing linter warnings
44 |
45 | Thanks to [atom/atom](https://github.com/atom/atom) for the commit message guidelines.
46 |
--------------------------------------------------------------------------------
/tests/unit/Message/MessageFactoryTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Message;
17 |
18 | use Graze\Queue\Test\TestCase;
19 |
20 | class MessageFactoryTest extends TestCase
21 | {
22 | /** @var MessageFactory */
23 | private $factory;
24 |
25 | public function setUp()
26 | {
27 | $this->factory = new MessageFactory();
28 | }
29 |
30 | public function testInterface()
31 | {
32 | assertThat($this->factory, is(anInstanceOf('Graze\Queue\Message\MessageFactoryInterface')));
33 | }
34 |
35 | public function testCreateMessage()
36 | {
37 | $message = $this->factory->createMessage('foo');
38 |
39 | assertThat($message, is(anInstanceOf('Graze\Queue\Message\MessageInterface')));
40 | assertThat($message->getBody(), is(identicalTo('foo')));
41 | assertThat($message->isValid(), is(identicalTo(true)));
42 | }
43 |
44 | public function testCreateMessageWithMetadata()
45 | {
46 | $message = $this->factory->createMessage('foo', ['metadata' => ['bar' => 'baz']]);
47 |
48 | assertThat($message, is(anInstanceOf('Graze\Queue\Message\MessageInterface')));
49 | assertThat($message->getBody(), is(identicalTo('foo')));
50 | assertThat($message->getMetadata()->get('bar'), is(identicalTo('baz')));
51 | }
52 |
53 | public function testCreateMessageWithValidator()
54 | {
55 | $message = $this->factory->createMessage('bar', [
56 | 'validator' => function ($msg) {
57 | return false;
58 | },
59 | ]);
60 |
61 | assertThat($message, is(anInstanceOf('Graze\Queue\Message\MessageInterface')));
62 | assertThat($message->getBody(), is(identicalTo('bar')));
63 | assertThat($message->isValid(), is(identicalTo(false)));
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/unit/Adapter/Exception/FailedEnqueueExceptionTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter\Exception;
17 |
18 | use Exception;
19 | use Graze\Queue\Adapter\AdapterInterface;
20 | use Graze\Queue\Message\MessageInterface;
21 | use Mockery as m;
22 | use Mockery\MockInterface;
23 | use Graze\Queue\Test\TestCase;
24 |
25 | class FailedEnqueueExceptionTest extends TestCase
26 | {
27 | /** @var AdapterInterface|MockInterface */
28 | private $adapter;
29 | /** @var array */
30 | private $debug;
31 | /** @var MessageInterface[]|MockInterface[] */
32 | private $messages;
33 | /** @var Exception */
34 | private $previous;
35 | /** @var FailedEnqueueException */
36 | private $exception;
37 |
38 | public function setUp()
39 | {
40 | $this->adapter = m::mock(AdapterInterface::class);
41 | $this->debug = ['foo' => 'bar'];
42 |
43 | $a = m::mock(MessageInterface::class);
44 | $b = m::mock(MessageInterface::class);
45 | $c = m::mock(MessageInterface::class);
46 | $this->messages = [$a, $b, $c];
47 |
48 | $this->previous = new Exception();
49 | $this->exception = new FailedEnqueueException($this->adapter, $this->messages, $this->debug, $this->previous);
50 | }
51 |
52 | public function testInterface()
53 | {
54 | assertThat($this->exception, is(anInstanceOf(AdapterException::class)));
55 | }
56 |
57 | public function testGetAdapter()
58 | {
59 | assertThat($this->exception->getAdapter(), is(identicalTo($this->adapter)));
60 | }
61 |
62 | public function testGetDebug()
63 | {
64 | assertThat($this->exception->getDebug(), is(identicalTo($this->debug)));
65 | }
66 |
67 | public function testGetMessages()
68 | {
69 | assertThat($this->exception->getMessages(), is(identicalTo($this->messages)));
70 | }
71 |
72 | public function testGetPrevious()
73 | {
74 | assertThat($this->exception->getPrevious(), is(identicalTo($this->previous)));
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Adapter/Exception/AdapterException.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter\Exception;
17 |
18 | use Exception;
19 | use Graze\Queue\Adapter\AdapterInterface;
20 | use Graze\Queue\Adapter\NamedInterface;
21 | use Graze\Queue\Message\MessageInterface;
22 | use RuntimeException;
23 |
24 | class AdapterException extends RuntimeException
25 | {
26 | /** @var AdapterInterface */
27 | protected $adapter;
28 | /** @var array */
29 | protected $debug;
30 | /** @var MessageInterface[] */
31 | protected $messages;
32 | /** @var string|null */
33 | protected $queueName;
34 |
35 | /**
36 | * @param string $message
37 | * @param AdapterInterface $adapter
38 | * @param MessageInterface[] $messages
39 | * @param array $debug
40 | * @param Exception $previous
41 | */
42 | public function __construct(
43 | $message,
44 | AdapterInterface $adapter,
45 | array $messages,
46 | array $debug = [],
47 | Exception $previous = null
48 | ) {
49 | $this->debug = $debug;
50 | $this->adapter = $adapter;
51 | $this->messages = $messages;
52 |
53 | if ($adapter instanceof NamedInterface) {
54 | $this->queueName = $adapter->getQueueName();
55 | }
56 |
57 | parent::__construct($this->queueName . ': ' . $message, 0, $previous);
58 | }
59 |
60 | /**
61 | * @return AdapterInterface
62 | */
63 | public function getAdapter()
64 | {
65 | return $this->adapter;
66 | }
67 |
68 | /**
69 | * @return array
70 | */
71 | public function getDebug()
72 | {
73 | return $this->debug;
74 | }
75 |
76 | /**
77 | * @return \Graze\Queue\Message\MessageInterface[]
78 | */
79 | public function getMessages()
80 | {
81 | return $this->messages;
82 | }
83 |
84 | /**
85 | * @return null|string
86 | */
87 | public function getQueueName()
88 | {
89 | return $this->queueName;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/tests/unit/Message/MessageTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Message;
17 |
18 | use Graze\DataStructure\Container\ContainerInterface;
19 | use Mockery as m;
20 | use Mockery\MockInterface;
21 | use Graze\Queue\Test\TestCase;
22 |
23 | class MessageTest extends TestCase
24 | {
25 | /** @var ContainerInterface|MockInterface */
26 | private $metadata;
27 |
28 | public function setUp()
29 | {
30 | $this->metadata = m::mock(ContainerInterface::class);
31 | }
32 |
33 | public function testInterface()
34 | {
35 | $this->assertInstanceOf(
36 | MessageInterface::class,
37 | new Message('foo', $this->metadata, function () {
38 | })
39 | );
40 | }
41 |
42 | public function testGetBody()
43 | {
44 | $message = new Message('foo', $this->metadata, function () {
45 | });
46 |
47 | assertThat($message->getBody(), is(identicalTo('foo')));
48 | }
49 |
50 | public function testGetMetadata()
51 | {
52 | $message = new Message('foo', $this->metadata, function () {
53 | });
54 |
55 | assertThat($message->getMetadata(), is(identicalTo($this->metadata)));
56 | }
57 |
58 | public function testIsValidIsFalse()
59 | {
60 | $message = new Message('foo', $this->metadata, function () {
61 | return false;
62 | });
63 |
64 | assertThat($message->isValid(), is(identicalTo(false)));
65 | }
66 |
67 | public function testIsValidIsTrue()
68 | {
69 | $message = new Message('foo', $this->metadata, function () {
70 | return true;
71 | });
72 |
73 | assertThat($message->isValid(), is(identicalTo(true)));
74 | }
75 |
76 | public function testIsValidIsCalledWithMessage()
77 | {
78 | $seen = null;
79 | $message = new Message('foo', $this->metadata, function ($msg) use (&$seen) {
80 | $seen = $msg;
81 | });
82 |
83 | $message->isValid();
84 |
85 | assertThat($seen, is(identicalTo($message)));
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/tests/unit/Adapter/Exception/FailedExtensionExceptionTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter\Exception;
17 |
18 | use Exception;
19 | use Graze\Queue\Adapter\AdapterInterface;
20 | use Graze\Queue\Message\MessageInterface;
21 | use Mockery as m;
22 | use Mockery\MockInterface;
23 | use Graze\Queue\Test\TestCase;
24 |
25 | class FailedExtensionExceptionTest extends TestCase
26 | {
27 | /** @var AdapterInterface|MockInterface */
28 | private $adapter;
29 | /** @var array */
30 | private $debug;
31 | /** @var MessageInterface[]|MockInterface[] */
32 | private $messages;
33 | /** @var Exception */
34 | private $previous;
35 | /** @var FailedExtensionException */
36 | private $exception;
37 |
38 | public function setUp()
39 | {
40 | $this->adapter = m::mock(AdapterInterface::class);
41 | $this->debug = ['foo' => 'bar'];
42 |
43 | $a = m::mock(MessageInterface::class);
44 | $b = m::mock(MessageInterface::class);
45 | $c = m::mock(MessageInterface::class);
46 | $this->messages = [$a, $b, $c];
47 |
48 | $this->previous = new Exception();
49 |
50 | $this->exception = new FailedExtensionException(
51 | $this->adapter,
52 | $this->messages,
53 | $this->debug,
54 | $this->previous
55 | );
56 | }
57 |
58 | public function testInterface()
59 | {
60 | assertThat($this->exception, is(anInstanceOf(AdapterException::class)));
61 | }
62 |
63 | public function testGetAdapter()
64 | {
65 | assertThat($this->exception->getAdapter(), is(identicalTo($this->adapter)));
66 | }
67 |
68 | public function testGetDebug()
69 | {
70 | assertThat($this->exception->getDebug(), is(identicalTo($this->debug)));
71 | }
72 |
73 | public function testGetMessages()
74 | {
75 | assertThat($this->exception->getMessages(), is(identicalTo($this->messages)));
76 | }
77 |
78 | public function testGetPrevious()
79 | {
80 | assertThat($this->exception->getPrevious(), is(identicalTo($this->previous)));
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/tests/unit/Adapter/Exception/FailedRejectionExceptionTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter\Exception;
17 |
18 | use Exception;
19 | use Graze\Queue\Adapter\AdapterInterface;
20 | use Graze\Queue\Message\MessageInterface;
21 | use Mockery as m;
22 | use Mockery\MockInterface;
23 | use Graze\Queue\Test\TestCase;
24 |
25 | class FailedRejectionExceptionTest extends TestCase
26 | {
27 | /** @var AdapterInterface|MockInterface */
28 | private $adapter;
29 | /** @var array */
30 | private $debug;
31 | /** @var MessageInterface[]|MockInterface[] */
32 | private $messages;
33 | /** @var Exception */
34 | private $previous;
35 | /** @var FailedRejectionException */
36 | private $exception;
37 |
38 | public function setUp()
39 | {
40 | $this->adapter = m::mock(AdapterInterface::class);
41 | $this->debug = ['foo' => 'bar'];
42 |
43 | $a = m::mock(MessageInterface::class);
44 | $b = m::mock(MessageInterface::class);
45 | $c = m::mock(MessageInterface::class);
46 | $this->messages = [$a, $b, $c];
47 |
48 | $this->previous = new Exception();
49 |
50 | $this->exception = new FailedRejectionException(
51 | $this->adapter,
52 | $this->messages,
53 | $this->debug,
54 | $this->previous
55 | );
56 | }
57 |
58 | public function testInterface()
59 | {
60 | assertThat($this->exception, is(anInstanceOf(AdapterException::class)));
61 | }
62 |
63 | public function testGetAdapter()
64 | {
65 | assertThat($this->exception->getAdapter(), is(identicalTo($this->adapter)));
66 | }
67 |
68 | public function testGetDebug()
69 | {
70 | assertThat($this->exception->getDebug(), is(identicalTo($this->debug)));
71 | }
72 |
73 | public function testGetMessages()
74 | {
75 | assertThat($this->exception->getMessages(), is(identicalTo($this->messages)));
76 | }
77 |
78 | public function testGetPrevious()
79 | {
80 | assertThat($this->exception->getPrevious(), is(identicalTo($this->previous)));
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/tests/unit/Adapter/Exception/FailedAcknowledgementExceptionTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter\Exception;
17 |
18 | use Exception;
19 | use Graze\Queue\Adapter\AdapterInterface;
20 | use Graze\Queue\Message\MessageInterface;
21 | use Mockery as m;
22 | use Mockery\MockInterface;
23 | use Graze\Queue\Test\TestCase;
24 |
25 | class FailedAcknowledgementExceptionTest extends TestCase
26 | {
27 | /** @var AdapterInterface|MockInterface */
28 | private $adapter;
29 | /** @var array */
30 | private $debug;
31 | /** @var MessageInterface[]|MockInterface[] */
32 | private $messages;
33 | /** @var Exception */
34 | private $previous;
35 | /** @var FailedAcknowledgementException */
36 | private $exception;
37 |
38 | public function setUp()
39 | {
40 | $this->adapter = m::mock(AdapterInterface::class);
41 | $this->debug = ['foo' => 'bar'];
42 |
43 | $a = m::mock(MessageInterface::class);
44 | $b = m::mock(MessageInterface::class);
45 | $c = m::mock(MessageInterface::class);
46 | $this->messages = [$a, $b, $c];
47 |
48 | $this->previous = new Exception();
49 |
50 | $this->exception = new FailedAcknowledgementException(
51 | $this->adapter,
52 | $this->messages,
53 | $this->debug,
54 | $this->previous
55 | );
56 | }
57 |
58 | public function testInterface()
59 | {
60 | assertThat($this->exception, is(anInstanceOf(AdapterException::class)));
61 | }
62 |
63 | public function testGetAdapter()
64 | {
65 | assertThat($this->exception->getAdapter(), is(identicalTo($this->adapter)));
66 | }
67 |
68 | public function testGetDebug()
69 | {
70 | assertThat($this->exception->getDebug(), is(identicalTo($this->debug)));
71 | }
72 |
73 | public function testGetMessages()
74 | {
75 | assertThat($this->exception->getMessages(), is(identicalTo($this->messages)));
76 | }
77 |
78 | public function testGetPrevious()
79 | {
80 | assertThat($this->exception->getPrevious(), is(identicalTo($this->previous)));
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Adapter/AdapterInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter;
17 |
18 | use Graze\Queue\Adapter\Exception\FailedAcknowledgementException;
19 | use Graze\Queue\Adapter\Exception\FailedEnqueueException;
20 | use Graze\Queue\Message\MessageFactoryInterface;
21 | use Graze\Queue\Message\MessageInterface;
22 | use Iterator;
23 |
24 | interface AdapterInterface
25 | {
26 | /**
27 | * Acknowledge the messages (delete them from the queue)
28 | *
29 | * @param MessageInterface[] $messages
30 | *
31 | * @return void
32 | *
33 | * @throws FailedAcknowledgementException
34 | */
35 | public function acknowledge(array $messages);
36 |
37 | /**
38 | * Attempt to reject all the following messages (make the message immediately visible to other consumers)
39 | *
40 | * @param MessageInterface[] $messages
41 | *
42 | * @return void
43 | *
44 | * @throws FailedAcknowledgementException
45 | */
46 | public function reject(array $messages);
47 |
48 | /**
49 | * @param MessageInterface[] $messages
50 | * @param int $duration Number of seconds to ensure that this message stays being processed and not
51 | * put back on the queue
52 | *
53 | * @return void
54 | */
55 | public function extend(array $messages, $duration);
56 |
57 | /**
58 | * Remove up to {$limit} messages from the queue
59 | *
60 | * @param MessageFactoryInterface $factory
61 | * @param int $limit
62 | *
63 | * @return Iterator
64 | */
65 | public function dequeue(MessageFactoryInterface $factory, $limit);
66 |
67 | /**
68 | * Add all the messages to the queue
69 | *
70 | * @param MessageInterface[] $messages
71 | *
72 | * @return void
73 | *
74 | * @throws FailedEnqueueException
75 | */
76 | public function enqueue(array $messages);
77 |
78 | /**
79 | * Empty the queue
80 | *
81 | * @return void
82 | */
83 | public function purge();
84 |
85 | /**
86 | * Delete the queue
87 | *
88 | * @return void
89 | */
90 | public function delete();
91 | }
92 |
--------------------------------------------------------------------------------
/tests/unit/Adapter/Exception/MethodNotSupportedExceptionTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter\Exception;
17 |
18 | use Exception;
19 | use Graze\Queue\Adapter\AdapterInterface;
20 | use Graze\Queue\Message\MessageInterface;
21 | use Mockery as m;
22 | use Mockery\MockInterface;
23 | use Graze\Queue\Test\TestCase;
24 |
25 | class MethodNotSupportedExceptionTest extends TestCase
26 | {
27 | /** @var AdapterInterface|MockInterface */
28 | private $adapter;
29 | /** @var array */
30 | private $debug;
31 | /** @var MessageInterface[]|MockInterface[] */
32 | private $messages;
33 | /** @var Exception */
34 | private $previous;
35 | /** @var MethodNotSupportedException */
36 | private $exception;
37 |
38 | public function setUp()
39 | {
40 | $this->adapter = m::mock(AdapterInterface::class);
41 | $this->debug = ['foo' => 'bar'];
42 |
43 | $a = m::mock(MessageInterface::class);
44 | $b = m::mock(MessageInterface::class);
45 | $c = m::mock(MessageInterface::class);
46 | $this->messages = [$a, $b, $c];
47 |
48 | $this->previous = new Exception();
49 |
50 | $this->exception = new MethodNotSupportedException(
51 | 'method',
52 | $this->adapter,
53 | $this->messages,
54 | $this->debug,
55 | $this->previous
56 | );
57 | }
58 |
59 | public function testInterface()
60 | {
61 | assertThat($this->exception, is(anInstanceOf(AdapterException::class)));
62 | }
63 |
64 | public function testGetMethod()
65 | {
66 | assertThat($this->exception->getMethod(), is(identicalTo('method')));
67 | }
68 |
69 | public function testGetAdapter()
70 | {
71 | assertThat($this->exception->getAdapter(), is(identicalTo($this->adapter)));
72 | }
73 |
74 | public function testGetDebug()
75 | {
76 | assertThat($this->exception->getDebug(), is(identicalTo($this->debug)));
77 | }
78 |
79 | public function testGetMessages()
80 | {
81 | assertThat($this->exception->getMessages(), is(identicalTo($this->messages)));
82 | }
83 |
84 | public function testGetPrevious()
85 | {
86 | assertThat($this->exception->getPrevious(), is(identicalTo($this->previous)));
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/tests/integration/FirehoseIntegrationTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue;
17 |
18 | use Aws\ResultInterface;
19 | use Aws\Firehose\FirehoseClient;
20 | use Graze\Queue\Adapter\Exception\FailedEnqueueException;
21 | use Graze\Queue\Adapter\FirehoseAdapter;
22 | use Mockery as m;
23 | use Mockery\MockInterface;
24 | use Graze\Queue\Test\TestCase;
25 |
26 | class FirehoseIntegrationTest extends TestCase
27 | {
28 | /** @var string */
29 | private $deliveryStreamName;
30 | /** @var FirehoseClient|MockInterface */
31 | private $firehoseClient;
32 | /** @var Client */
33 | private $client;
34 |
35 | public function setUp()
36 | {
37 | $this->deliveryStreamName = 'delivery_stream_foo';
38 | $this->firehoseClient = m::mock(FirehoseClient::class);
39 | $this->client = new Client(new FirehoseAdapter($this->firehoseClient, 'delivery_stream_foo'));
40 | }
41 |
42 | public function testSend()
43 | {
44 | $model = m::mock(ResultInterface::class);
45 | $model->shouldReceive('get')->once()->with('RequestResponses')->andReturn([]);
46 |
47 | $this->firehoseClient->shouldReceive('putRecordBatch')->once()->with([
48 | 'DeliveryStreamName' => $this->deliveryStreamName,
49 | 'Records' => [
50 | ['Data' => 'foo']
51 | ]
52 | ])->andReturn($model);
53 |
54 | $this->client->send([$this->client->create('foo')]);
55 | }
56 |
57 | /**
58 | * @expectedException \Graze\Queue\Adapter\Exception\FailedEnqueueException
59 | */
60 | public function testSendError()
61 | {
62 | $model = m::mock(ResultInterface::class);
63 | $model->shouldReceive('get')->once()->with('RequestResponses')->andReturn([
64 | [
65 | 'ErrorCode' => 'fooError',
66 | 'ErrorMessage' => 'Some error message',
67 | 'RecordId' => 'foo',
68 | ]
69 | ]);
70 |
71 | $this->firehoseClient->shouldReceive('putRecordBatch')->once()->with([
72 | 'DeliveryStreamName' => $this->deliveryStreamName,
73 | 'Records' => [
74 | ['Data' => 'foo'],
75 | ],
76 | ])->andReturn($model);
77 |
78 | $this->client->send([$this->client->create('foo')]);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/tests/unit/Adapter/Exception/AdapterExceptionTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter\Exception;
17 |
18 | use Exception;
19 | use Graze\Queue\Adapter\AdapterInterface;
20 | use Graze\Queue\Adapter\NamedInterface;
21 | use Graze\Queue\Message\MessageInterface;
22 | use Graze\Queue\Test\TestCase;
23 | use Mockery as m;
24 | use Mockery\MockInterface;
25 |
26 | class AdapterExceptionTest extends TestCase
27 | {
28 | /** @var string */
29 | private $queueName;
30 | /** @var array */
31 | private $debug;
32 | /** @var AdapterInterface|NamedInterface|MockInterface */
33 | private $adapter;
34 | /** @var MessageInterface[]|MockInterface[] */
35 | private $messages;
36 | /** @var Exception */
37 | private $previous;
38 | /** @var AdapterException */
39 | private $exception;
40 |
41 | public function setUp()
42 | {
43 | $this->queueName = 'foobar';
44 | $this->debug = ['foo' => 'bar'];
45 |
46 | $this->adapter = m::mock(AdapterInterface::class, NamedInterface::class);
47 | $this->adapter->shouldReceive('getQueueName')->andReturn($this->queueName);
48 |
49 | $a = m::mock('Graze\Queue\Message\MessageInterface');
50 | $b = m::mock('Graze\Queue\Message\MessageInterface');
51 | $c = m::mock('Graze\Queue\Message\MessageInterface');
52 | $this->messages = [$a, $b, $c];
53 |
54 | $this->previous = new Exception();
55 |
56 | $this->exception = new AdapterException('foo', $this->adapter, $this->messages, $this->debug, $this->previous);
57 | }
58 |
59 | public function testInterface()
60 | {
61 | assertThat($this->exception, is(anInstanceOf('RuntimeException')));
62 | }
63 |
64 | public function testGetAdapter()
65 | {
66 | assertThat($this->exception->getAdapter(), is(identicalTo($this->adapter)));
67 | }
68 |
69 | public function testGetDebug()
70 | {
71 | assertThat($this->exception->getDebug(), is(identicalTo($this->debug)));
72 | }
73 |
74 | public function testGetMessages()
75 | {
76 | assertThat($this->exception->getMessages(), is(identicalTo($this->messages)));
77 | }
78 |
79 | public function testGetPrevious()
80 | {
81 | assertThat($this->exception->getPrevious(), is(identicalTo($this->previous)));
82 | }
83 |
84 | public function testGetQueueName()
85 | {
86 | assertThat($this->exception->getQueueName(), is(identicalTo($this->queueName)));
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Handler/AbstractAcknowledgementHandler.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Handler;
17 |
18 | use Exception;
19 | use Graze\Queue\Adapter\AdapterInterface;
20 | use Graze\Queue\Message\MessageInterface;
21 | use Iterator;
22 |
23 | abstract class AbstractAcknowledgementHandler
24 | {
25 | /**
26 | * @param MessageInterface $message
27 | * @param AdapterInterface $adapter
28 | * @param mixed $result
29 | */
30 | abstract protected function acknowledge(
31 | MessageInterface $message,
32 | AdapterInterface $adapter,
33 | $result = null
34 | );
35 |
36 | /**
37 | * @param MessageInterface $message
38 | * @param AdapterInterface $adapter
39 | * @param int $duration Number of seconds to ensure that this message is not seen by any other clients
40 | */
41 | abstract protected function extend(
42 | MessageInterface $message,
43 | AdapterInterface $adapter,
44 | $duration
45 | );
46 |
47 | /**
48 | * @param MessageInterface $message
49 | * @param AdapterInterface $adapter
50 | * @param mixed $result
51 | */
52 | abstract protected function reject(
53 | MessageInterface $message,
54 | AdapterInterface $adapter,
55 | $result = null
56 | );
57 |
58 | /**
59 | * @param AdapterInterface $adapter
60 | */
61 | abstract protected function flush(AdapterInterface $adapter);
62 |
63 | /**
64 | * @param Iterator $messages
65 | * @param AdapterInterface $adapter
66 | * @param callable $worker
67 | *
68 | * @throws Exception
69 | */
70 | public function __invoke(Iterator $messages, AdapterInterface $adapter, callable $worker)
71 | {
72 | // Used to break from polling consumer
73 | $break = false;
74 | $done = function () use (&$break) {
75 | $break = true;
76 | };
77 |
78 | try {
79 | foreach ($messages as $message) {
80 | if ($message->isValid()) {
81 | $result = call_user_func($worker, $message, $done);
82 | $this->acknowledge($message, $adapter, $result);
83 | }
84 |
85 | if ($break) {
86 | break;
87 | }
88 | }
89 | } catch (Exception $e) {
90 | $this->flush($adapter);
91 | throw $e;
92 | }
93 |
94 | $this->flush($adapter);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SHELL = /bin/sh
2 |
3 | DOCKER ?= $(shell which docker)
4 | PHP_VER := 7.2
5 | IMAGE := graze/php-alpine:${PHP_VER}-test
6 | VOLUME := /srv
7 | DOCKER_RUN_BASE := ${DOCKER} run --rm -t -v $$(pwd):${VOLUME} -w ${VOLUME}
8 | DOCKER_RUN := ${DOCKER_RUN_BASE} ${IMAGE}
9 |
10 | PREFER_LOWEST ?=
11 |
12 | .PHONY: install composer help
13 | .PHONY: test lint lint-fix test-unit test-integration test-matrix test-matrix-lowest
14 | .PHONY: test-coverage test-coverage-html test-coverage-clover
15 |
16 | .SILENT: help
17 |
18 | # Building
19 |
20 | build: ## Install the dependencies
21 | build: ensure-composer-file
22 | make 'composer-install --optimize-autoloader --prefer-dist ${PREFER_LOWEST}'
23 |
24 | build-update: ## Update the dependencies
25 | build-update: ensure-composer-file
26 | make 'composer-update --optimize-autoloader --prefer-dist ${PREFER_LOWEST}'
27 |
28 | ensure-composer-file: # Update the composer file
29 | make 'composer-config platform.php ${PHP_VER}'
30 |
31 | composer-%: ## Run a composer command, `make "composer- [...]"`.
32 | ${DOCKER} run -t --rm \
33 | -v $$(pwd):/app:delegated \
34 | -v ~/.composer:/tmp:delegated \
35 | -v ~/.ssh:/root/.ssh:ro \
36 | composer --ansi --no-interaction $* $(filter-out $@,$(MAKECMDGOALS))
37 |
38 | # Testing
39 |
40 | test: ## Run the unit and integration testsuites.
41 | test: lint test-unit test-integration
42 |
43 | lint: ## Run phpcs against the code.
44 | ${DOCKER_RUN} vendor/bin/phpcs -p --warning-severity=0 -s src/ tests/
45 |
46 | lint-fix: ## Run phpcsf and fix possible lint errors.
47 | ${DOCKER_RUN} vendor/bin/phpcbf -p -s src/ tests/
48 |
49 | test-unit: ## Run the unit testsuite.
50 | ${DOCKER_RUN} vendor/bin/phpunit --colors=always --testsuite unit
51 |
52 | test-integration: ## Run the integration testsuite.
53 | ${DOCKER_RUN} vendor/bin/phpunit --colors=always --testsuite integration
54 |
55 | test-matrix-lowest: ## Test all version, with the lowest version
56 | ${MAKE} test-matrix PREFER_LOWEST='--prefer-lowest --prefer-stable'
57 | ${MAKE} build-update
58 |
59 | test-matrix: ## Run the unit tests against multiple targets.
60 | ${MAKE} PHP_VER="5.6" build-update test
61 | ${MAKE} PHP_VER="7.0" build-update test
62 | ${MAKE} PHP_VER="7.1" build-update test
63 | ${MAKE} PHP_VER="7.2" build-update test
64 |
65 | test-coverage: ## Run all tests and output coverage to the console.
66 | ${DOCKER_RUN} phpdbg7 -qrr vendor/bin/phpunit --coverage-text
67 |
68 | test-coverage-html: ## Run all tests and output coverage to html.
69 | ${DOCKER_RUN} phpdbg7 -qrr vendor/bin/phpunit --coverage-html=./tests/report/html
70 |
71 | test-coverage-clover: ## Run all tests and output clover coverage to file.
72 | ${DOCKER_RUN} phpdbg7 -qrr vendor/bin/phpunit --coverage-clover=./tests/report/coverage.clover
73 |
74 | # Help
75 |
76 | help: ## Show this help message.
77 | echo "usage: make [target] ..."
78 | echo ""
79 | echo "targets:"
80 | fgrep --no-filename "##" $(MAKEFILE_LIST) | fgrep --invert-match $$'\t' | sed -e 's/: ## / - /'
81 |
--------------------------------------------------------------------------------
/tests/unit/Handler/ResultAcknowledgementHandlerTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Handler;
17 |
18 | use ArrayIterator;
19 | use Closure;
20 | use Graze\Queue\Adapter\AdapterInterface;
21 | use Graze\Queue\Message\MessageInterface;
22 | use Mockery as m;
23 | use Mockery\MockInterface;
24 | use Graze\Queue\Test\TestCase;
25 |
26 | class ResultAcknowledgementHandlerTest extends TestCase
27 | {
28 | /** @var AdapterInterface|MockInterface */
29 | private $adapter;
30 | /** @var ResultAcknowledgementHandler */
31 | private $handler;
32 | /** @var MessageInterface|MockInterface */
33 | private $message;
34 | /** @var ArrayIterator */
35 | private $messages;
36 |
37 | public function setUp()
38 | {
39 | $this->adapter = m::mock('Graze\Queue\Adapter\AdapterInterface');
40 |
41 | $this->message = m::mock('Graze\Queue\Message\MessageInterface');
42 | $this->messages = new ArrayIterator([$this->message]);
43 |
44 | $this->handler = new EagerAcknowledgementHandler();
45 | }
46 |
47 | public function testHandleTrueResult()
48 | {
49 | $handler = new ResultAcknowledgementHandler(function ($result) {
50 | return $result === true;
51 | }, $this->handler);
52 |
53 | $this->message->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
54 | $this->adapter->shouldReceive('acknowledge')->once()->with(m::mustBe([$this->message]));
55 |
56 | $msgs = [];
57 | $handler($this->messages, $this->adapter, function ($msg, Closure $done) use (&$msgs) {
58 | $msgs[] = $msg;
59 | return true;
60 | });
61 |
62 | assertThat($msgs, is(identicalTo(iterator_to_array($this->messages))));
63 | }
64 |
65 | public function testHandleNonTrueResponse()
66 | {
67 | $handler = new ResultAcknowledgementHandler(function ($result) {
68 | return $result === true;
69 | }, $this->handler);
70 |
71 | $this->message->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
72 | $this->adapter->shouldReceive('reject')->once()->with(m::mustBe([$this->message]));
73 |
74 | $handler($this->messages, $this->adapter, function ($msg, Closure $done) use (&$msgs) {
75 | return false;
76 | });
77 | }
78 |
79 | public function testCustomResultAcknowledgementHandler()
80 | {
81 | $handler = new ResultAcknowledgementHandler(function ($result) {
82 | return $result === false;
83 | }, $this->handler);
84 |
85 | $this->message->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
86 | $this->adapter->shouldReceive('acknowledge')->once()->with(m::mustBe([$this->message]));
87 |
88 | $handler($this->messages, $this->adapter, function ($msg) {
89 | return false;
90 | });
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/tests/integration/ArrayIntegrationTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue;
17 |
18 | use Graze\Queue\Adapter\ArrayAdapter;
19 | use Graze\Queue\Message\MessageFactory;
20 | use Graze\Queue\Message\MessageInterface;
21 | use Graze\Queue\Test\TestCase;
22 |
23 | class ArrayIntegrationTest extends TestCase
24 | {
25 | /** @var MessageInterface[] */
26 | private $messages;
27 | /** @var Client */
28 | private $client;
29 |
30 | public function setUp()
31 | {
32 | $factory = new MessageFactory();
33 |
34 | $this->messages = [
35 | $factory->createMessage('foo'),
36 | $factory->createMessage('bar'),
37 | $factory->createMessage('baz'),
38 | ];
39 |
40 | $this->client = new Client(new ArrayAdapter($this->messages));
41 | }
42 |
43 | public function testReceive()
44 | {
45 | $msgs = [];
46 | $this->client->receive(function ($msg) use (&$msgs) {
47 | $msgs[] = $msg;
48 | }, 100);
49 |
50 | assertThat($msgs, is(identicalTo($this->messages)));
51 | }
52 |
53 | public function testReceiveWithPolling()
54 | {
55 | $msgs = [];
56 | $this->client->receive(function ($msg) use (&$msgs) {
57 | $msgs[] = $msg;
58 | }, null);
59 |
60 | assertThat($msgs, is(identicalTo($this->messages)));
61 | }
62 |
63 | public function testReceiveWithNoMessages()
64 | {
65 | $client = new Client(new ArrayAdapter());
66 |
67 | $msgs = [];
68 | $client->receive(function ($msg) use (&$msgs) {
69 | $msgs[] = $msg;
70 | }, null);
71 |
72 | assertThat($msgs, is(emptyArray()));
73 | }
74 |
75 | public function testReceiveWithLimitAndNoMessages()
76 | {
77 | $client = new Client(new ArrayAdapter());
78 |
79 | $msgs = [];
80 | $client->receive(function ($msg) use (&$msgs) {
81 | $msgs[] = $msg;
82 | }, 10);
83 |
84 | assertThat($msgs, is(emptyArray()));
85 | }
86 |
87 | public function testSend()
88 | {
89 | $this->client->send([$this->client->create('foo')]);
90 | }
91 |
92 | public function testPurge()
93 | {
94 | $this->client->purge();
95 |
96 | $msgs = [];
97 | $this->client->receive(function ($msg) use (&$msgs) {
98 | $msgs[] = $msg;
99 | }, null);
100 |
101 | assertThat($msgs, is(emptyArray()));
102 | }
103 |
104 | public function testDelete()
105 | {
106 | $this->client->delete();
107 |
108 | $msgs = [];
109 | $this->client->receive(function ($msg) use (&$msgs) {
110 | $msgs[] = $msg;
111 | }, null);
112 |
113 | assertThat($msgs, is(emptyArray()));
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/Handler/ResultAcknowledgementHandler.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Handler;
17 |
18 | use Graze\Queue\Adapter\AdapterInterface;
19 | use Graze\Queue\Message\MessageInterface;
20 |
21 | class ResultAcknowledgementHandler extends AbstractAcknowledgementHandler
22 | {
23 | /** @var callable */
24 | private $validator;
25 |
26 | /** @var AbstractAcknowledgementHandler */
27 | private $handler;
28 |
29 | /**
30 | * ResultAcknowledgementHandler constructor.
31 | *
32 | * @param callable $validator
33 | * @param AbstractAcknowledgementHandler $handler
34 | */
35 | public function __construct(callable $validator, AbstractAcknowledgementHandler $handler)
36 | {
37 | /**
38 | * This callable should accept the mixed result returned by a worker
39 | * and return a boolean value.
40 | *
41 | * @var callable
42 | */
43 | $this->validator = $validator;
44 |
45 | /**
46 | * The handler to call `acknowledge` or `reject` on if {@see $validator} returns a
47 | * truthy value for the given result.
48 | *
49 | * @var AbstractAcknowledgementHandler
50 | */
51 | $this->handler = $handler;
52 | }
53 |
54 | /**
55 | * @param MessageInterface $message
56 | * @param AdapterInterface $adapter
57 | * @param mixed $result
58 | */
59 | protected function acknowledge(
60 | MessageInterface $message,
61 | AdapterInterface $adapter,
62 | $result = null
63 | ) {
64 | if (call_user_func($this->validator, $result) === true) {
65 | $this->handler->acknowledge($message, $adapter, $result);
66 | } else {
67 | $this->handler->reject($message, $adapter, $result);
68 | }
69 | }
70 |
71 | /**
72 | * @param MessageInterface $message
73 | * @param AdapterInterface $adapter
74 | * @param int $duration Number of seconds to ensure that this message is not seen by any other clients
75 | */
76 | protected function extend(
77 | MessageInterface $message,
78 | AdapterInterface $adapter,
79 | $duration
80 | ) {
81 | $this->handler->extend($message, $adapter, $duration);
82 | }
83 |
84 | /**
85 | * @param MessageInterface $message
86 | * @param AdapterInterface $adapter
87 | * @param mixed $result
88 | */
89 | protected function reject(
90 | MessageInterface $message,
91 | AdapterInterface $adapter,
92 | $result = null
93 | ) {
94 | $this->handler->reject($message, $adapter, $result);
95 | }
96 |
97 | /**
98 | * @param AdapterInterface $adapter
99 | */
100 | protected function flush(AdapterInterface $adapter)
101 | {
102 | $this->handler->flush($adapter);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/Handler/BatchAcknowledgementHandler.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Handler;
17 |
18 | use Graze\Queue\Adapter\AdapterInterface;
19 | use Graze\Queue\Message\MessageInterface;
20 |
21 | class BatchAcknowledgementHandler extends AbstractAcknowledgementHandler
22 | {
23 | /** @var int */
24 | protected $batchSize;
25 |
26 | /** @var MessageInterface[] */
27 | protected $acknowledged = [];
28 |
29 | /** @var MessageInterface[] */
30 | protected $rejected = [];
31 |
32 | /** @var MessageInterface[][] */
33 | protected $delayed = [];
34 |
35 | /**
36 | * @param int $batchSize
37 | */
38 | public function __construct($batchSize = 0)
39 | {
40 | $this->batchSize = (integer) $batchSize;
41 | }
42 |
43 | /**
44 | * @param MessageInterface $message
45 | * @param AdapterInterface $adapter
46 | * @param mixed $result
47 | */
48 | protected function acknowledge(
49 | MessageInterface $message,
50 | AdapterInterface $adapter,
51 | $result = null
52 | ) {
53 | $this->acknowledged[] = $message;
54 |
55 | if (count($this->acknowledged) === $this->batchSize) {
56 | $this->flush($adapter);
57 | }
58 | }
59 |
60 | /**
61 | * @param MessageInterface $message
62 | * @param AdapterInterface $adapter
63 | * @param int $duration
64 | */
65 | protected function extend(
66 | MessageInterface $message,
67 | AdapterInterface $adapter,
68 | $duration
69 | ) {
70 | $this->delayed[$duration][] = $message;
71 |
72 | if (count($this->delayed[$duration]) === $this->batchSize) {
73 | $this->flush($adapter);
74 | }
75 | }
76 |
77 | /**
78 | * @param MessageInterface $message
79 | * @param AdapterInterface $adapter
80 | * @param mixed $result
81 | */
82 | protected function reject(
83 | MessageInterface $message,
84 | AdapterInterface $adapter,
85 | $result = null
86 | ) {
87 | $this->rejected[] = $message;
88 |
89 | if (count($this->rejected) === $this->batchSize) {
90 | $this->flush($adapter);
91 | }
92 | }
93 |
94 | /**
95 | * @param AdapterInterface $adapter
96 | */
97 | protected function flush(AdapterInterface $adapter)
98 | {
99 | if (!empty($this->acknowledged)) {
100 | $adapter->acknowledge($this->acknowledged);
101 |
102 | $this->acknowledged = [];
103 | }
104 | if (!empty($this->rejected)) {
105 | $adapter->acknowledge($this->rejected);
106 |
107 | $this->rejected = [];
108 | }
109 | if (!empty($this->delayed)) {
110 | foreach ($this->delayed as $duration => $messages) {
111 | $adapter->extend($messages, $duration);
112 | }
113 |
114 | $this->delayed = [];
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/Client.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue;
17 |
18 | use Graze\Queue\Adapter\AdapterInterface;
19 | use Graze\Queue\Handler\BatchAcknowledgementHandler;
20 | use Graze\Queue\Message\MessageFactory;
21 | use Graze\Queue\Message\MessageFactoryInterface;
22 | use Graze\Queue\Message\MessageInterface;
23 |
24 | final class Client implements ConsumerInterface, DeleterInterface, ProducerInterface, PurgerInterface
25 | {
26 | /** @var AdapterInterface */
27 | protected $adapter;
28 |
29 | /** @var MessageFactoryInterface */
30 | protected $factory;
31 |
32 | /** @var callable */
33 | protected $handler;
34 |
35 | /**
36 | * @param AdapterInterface $adapter
37 | * @param array $config - handler Handler to apply a worker to a list of messages
38 | * and determine when to send acknowledgement.
39 | * - message_factory Factory used to create
40 | * messages.
41 | */
42 | public function __construct(AdapterInterface $adapter, array $config = [])
43 | {
44 | $this->adapter = $adapter;
45 |
46 | $this->handler = isset($config['handler'])
47 | ? $config['handler']
48 | : $this->createDefaultHandler();
49 |
50 | $this->factory = isset($config['message_factory'])
51 | ? $config['message_factory']
52 | : $this->createDefaultMessageFactory();
53 | }
54 |
55 | /**
56 | * @param string $body
57 | * @param array $options
58 | *
59 | * @return MessageInterface
60 | */
61 | public function create($body, array $options = [])
62 | {
63 | return $this->factory->createMessage($body, $options);
64 | }
65 |
66 | /**
67 | * @param callable $worker
68 | * @param int $limit
69 | */
70 | public function receive(callable $worker, $limit = 1)
71 | {
72 | $messages = $this->adapter->dequeue($this->factory, $limit);
73 |
74 | call_user_func($this->handler, $messages, $this->adapter, $worker);
75 | }
76 |
77 | /**
78 | * @param MessageInterface[] $messages
79 | *
80 | * @return mixed
81 | */
82 | public function send(array $messages)
83 | {
84 | return $this->adapter->enqueue($messages);
85 | }
86 |
87 | /**
88 | * {@inheritdoc}
89 | */
90 | public function purge()
91 | {
92 | $this->adapter->purge();
93 | }
94 |
95 | /**
96 | * {@inheritdoc}
97 | */
98 | public function delete()
99 | {
100 | $this->adapter->delete();
101 | }
102 |
103 | /**
104 | * @return callable
105 | */
106 | protected function createDefaultHandler()
107 | {
108 | return new BatchAcknowledgementHandler();
109 | }
110 |
111 | /**
112 | * @return MessageFactoryInterface
113 | */
114 | protected function createDefaultMessageFactory()
115 | {
116 | return new MessageFactory();
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/tests/unit/Handler/NullAcknowledgementHandlerTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Handler;
17 |
18 | use ArrayIterator;
19 | use Graze\Queue\Adapter\AdapterInterface;
20 | use Graze\Queue\Message\MessageInterface;
21 | use Mockery as m;
22 | use Mockery\MockInterface;
23 | use Graze\Queue\Test\TestCase;
24 | use RuntimeException;
25 |
26 | class NullAcknowledgementHandlerTest extends TestCase
27 | {
28 | /** @var AdapterInterface|MockInterface */
29 | private $adapter;
30 | /** @var MessageInterface|MockInterface */
31 | private $messageA;
32 | /** @var MessageInterface|MockInterface */
33 | private $messageB;
34 | /** @var MessageInterface|MockInterface */
35 | private $messageC;
36 | /** @var ArrayIterator */
37 | private $messages;
38 | /** @var NullAcknowledgementHandler */
39 | private $handler;
40 |
41 | public function setUp()
42 | {
43 | $this->adapter = m::mock('Graze\Queue\Adapter\AdapterInterface');
44 |
45 | $this->messageA = $a = m::mock('Graze\Queue\Message\MessageInterface');
46 | $this->messageB = $b = m::mock('Graze\Queue\Message\MessageInterface');
47 | $this->messageC = $c = m::mock('Graze\Queue\Message\MessageInterface');
48 | $this->messages = new ArrayIterator([$a, $b, $c]);
49 |
50 | $this->handler = new NullAcknowledgementHandler();
51 | }
52 |
53 | public function testHandle()
54 | {
55 | $handler = $this->handler;
56 |
57 | $this->messageA->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
58 | $this->messageB->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
59 | $this->messageC->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
60 |
61 | $msgs = [];
62 | $handler($this->messages, $this->adapter, function ($msg) use (&$msgs) {
63 | $msgs[] = $msg;
64 | });
65 |
66 | assertThat($msgs, is(identicalTo(iterator_to_array($this->messages))));
67 | }
68 |
69 | public function testHandleInvalidMessage()
70 | {
71 | $handler = $this->handler;
72 |
73 | $this->messageA->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
74 | $this->messageB->shouldReceive('isValid')->once()->withNoArgs()->andReturn(false);
75 | $this->messageC->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
76 |
77 | $msgs = [];
78 | $handler($this->messages, $this->adapter, function ($msg) use (&$msgs) {
79 | $msgs[] = $msg;
80 | });
81 |
82 | assertThat($msgs, is(identicalTo([$this->messageA, $this->messageC])));
83 | }
84 |
85 | /**
86 | * @expectedException \RuntimeException
87 | * @expectedExceptionMessage foo
88 | */
89 | public function testHandleWorkerWithThrownException()
90 | {
91 | $handler = $this->handler;
92 |
93 | $this->messageA->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
94 | $this->messageB->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
95 |
96 | $handler($this->messages, $this->adapter, function ($msg) {
97 | if ($msg === $this->messageB) {
98 | throw new RuntimeException('foo');
99 | }
100 | });
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Adapter/ArrayAdapter.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter;
17 |
18 | use ArrayIterator;
19 | use Graze\Queue\Adapter\Exception\FailedAcknowledgementException;
20 | use Graze\Queue\Message\MessageFactoryInterface;
21 | use Graze\Queue\Message\MessageInterface;
22 | use LimitIterator;
23 |
24 | final class ArrayAdapter implements AdapterInterface
25 | {
26 | /** @var MessageInterface[] */
27 | protected $queue = [];
28 |
29 | /**
30 | * @param MessageInterface[] $messages
31 | */
32 | public function __construct(array $messages = [])
33 | {
34 | $this->enqueue($messages);
35 | }
36 |
37 | /**
38 | * @param array $messages
39 | */
40 | public function acknowledge(array $messages)
41 | {
42 | $this->queue = array_values(array_filter($this->queue, function ($message) use ($messages) {
43 | return false === array_search($message, $messages, true);
44 | }));
45 | }
46 |
47 | /**
48 | * @param MessageInterface[] $messages
49 | * @param int $duration Number of seconds to ensure that this message stays being processed and not
50 | * put back on the queue
51 | *
52 | * @return void
53 | */
54 | public function extend(array $messages, $duration)
55 | {
56 | // do nothing, timeouts not implemented, so messages are immediately available
57 | }
58 |
59 | /**
60 | * Attempt to reject all the following messages (make the message immediately visible to other consumers)
61 | *
62 | * @param MessageInterface[] $messages
63 | *
64 | * @return void
65 | *
66 | * @throws FailedAcknowledgementException
67 | */
68 | public function reject(array $messages)
69 | {
70 | // do nothing, timeouts not implemented, so messages are immediately available
71 | }
72 |
73 | /**
74 | * @param MessageFactoryInterface $factory
75 | * @param int $limit
76 | *
77 | * @return LimitIterator
78 | */
79 | public function dequeue(MessageFactoryInterface $factory, $limit)
80 | {
81 | /*
82 | * If {@see $limit} is null then {@see LimitIterator} should be passed -1 as the count
83 | * to avoid throwing OutOfBoundsException.
84 | *
85 | * @link https://github.com/php/php-src/blob/php-5.6.12/ext/spl/internal/limititerator.inc#L60-L62
86 | */
87 | $count = (null === $limit) ? -1 : $limit;
88 |
89 | return new LimitIterator(new ArrayIterator($this->queue), 0, $count);
90 | }
91 |
92 | /**
93 | * @param array $messages
94 | */
95 | public function enqueue(array $messages)
96 | {
97 | foreach ($messages as $message) {
98 | $this->addMessage($message);
99 | }
100 | }
101 |
102 | /**
103 | * {@inheritdoc}
104 | */
105 | public function purge()
106 | {
107 | $this->queue = [];
108 | }
109 |
110 | /**
111 | * {@inheritdoc}
112 | */
113 | public function delete()
114 | {
115 | $this->purge();
116 | }
117 |
118 | /**
119 | * @param MessageInterface $message
120 | */
121 | protected function addMessage(MessageInterface $message)
122 | {
123 | $this->queue[] = $message;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/tests/unit/ClientTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue;
17 |
18 | use ArrayIterator;
19 | use Graze\Queue\Adapter\AdapterInterface;
20 | use Graze\Queue\Handler\AbstractAcknowledgementHandler;
21 | use Graze\Queue\Message\MessageFactoryInterface;
22 | use Graze\Queue\Message\MessageInterface;
23 | use Mockery as m;
24 | use Mockery\MockInterface;
25 | use Graze\Queue\Test\TestCase;
26 |
27 | class ClientTest extends TestCase
28 | {
29 | /** @var AdapterInterface|MockInterface */
30 | private $adapter;
31 | /** @var MessageFactoryInterface|MockInterface */
32 | private $factory;
33 | /** @var AbstractAcknowledgementHandler|MockInterface */
34 | private $handler;
35 | /** @var MessageInterface|MockInterface */
36 | private $messageA;
37 | /** @var MessageInterface|MockInterface */
38 | private $messageB;
39 | /** @var MessageInterface|MockInterface */
40 | private $messageC;
41 | /** @var MessageInterface[]|MockInterface[] */
42 | private $messages;
43 | /** @var Client */
44 | private $client;
45 |
46 | public function setUp()
47 | {
48 | $this->adapter = m::mock(AdapterInterface::class);
49 | $this->factory = m::mock(MessageFactoryInterface::class);
50 | $this->handler = m::mock(AbstractAcknowledgementHandler::class);
51 |
52 | $this->messageA = $a = m::mock(MessageInterface::class);
53 | $this->messageB = $b = m::mock(MessageInterface::class);
54 | $this->messageC = $c = m::mock(MessageInterface::class);
55 | $this->messages = [$a, $b, $c];
56 |
57 | $this->client = new Client($this->adapter, [
58 | 'handler' => $this->handler,
59 | 'message_factory' => $this->factory,
60 | ]);
61 | }
62 |
63 | public function testInterface()
64 | {
65 | assertThat($this->client, is(anInstanceOf(ConsumerInterface::class)));
66 | assertThat($this->client, is(anInstanceOf(DeleterInterface::class)));
67 | assertThat($this->client, is(anInstanceOf(ProducerInterface::class)));
68 | assertThat($this->client, is(anInstanceOf(PurgerInterface::class)));
69 | }
70 |
71 | public function testCreate()
72 | {
73 | $this->factory->shouldReceive('createMessage')->once()->with('foo', ['bar'])->andReturn($this->messageA);
74 |
75 | assertThat($this->client->create('foo', ['bar']), is(identicalTo($this->messageA)));
76 | }
77 |
78 | public function testSend()
79 | {
80 | $this->adapter->shouldReceive('enqueue')->once()->with($this->messages);
81 |
82 | $this->client->send($this->messages);
83 | }
84 |
85 | public function testReceive()
86 | {
87 | $worker = function () {
88 | };
89 |
90 | $messages = new ArrayIterator($this->messages);
91 |
92 | $this->adapter->shouldReceive('dequeue')->once()->with($this->factory, 1)->andReturn($messages);
93 | $this->handler->shouldReceive('__invoke')->once()->with($messages, $this->adapter, $worker);
94 |
95 | $this->client->receive($worker);
96 | }
97 |
98 | public function testPurge()
99 | {
100 | $this->adapter->shouldReceive('purge')->once();
101 | $this->client->purge();
102 | }
103 |
104 | public function testDelete()
105 | {
106 | $this->adapter->shouldReceive('delete')->once();
107 | $this->client->delete();
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/tests/unit/Handler/BatchAcknowledgementHandlerTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Handler;
17 |
18 | use ArrayIterator;
19 | use Closure;
20 | use Graze\Queue\Adapter\AdapterInterface;
21 | use Graze\Queue\Message\MessageInterface;
22 | use Mockery as m;
23 | use Mockery\MockInterface;
24 | use Graze\Queue\Test\TestCase;
25 | use RuntimeException;
26 |
27 | class BatchAcknowledgementHandlerTest extends TestCase
28 | {
29 | /** @var AdapterInterface|MockInterface */
30 | private $adapter;
31 | /** @var MessageInterface|MockInterface */
32 | private $messageA;
33 | /** @var MessageInterface|MockInterface */
34 | private $messageB;
35 | /** @var MessageInterface|MockInterface */
36 | private $messageC;
37 | /** @var ArrayIterator */
38 | private $messages;
39 | /** @var BatchAcknowledgementHandler */
40 | private $handler;
41 |
42 | public function setUp()
43 | {
44 | $this->adapter = m::mock(AdapterInterface::class);
45 |
46 | $this->messageA = $a = m::mock(MessageInterface::class);
47 | $this->messageB = $b = m::mock(MessageInterface::class);
48 | $this->messageC = $c = m::mock(MessageInterface::class);
49 | $this->messages = new ArrayIterator([$a, $b, $c]);
50 |
51 | $this->handler = new BatchAcknowledgementHandler(3);
52 | }
53 |
54 | public function testHandle()
55 | {
56 | $handler = $this->handler;
57 |
58 | $this->messageA->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
59 | $this->messageB->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
60 | $this->messageC->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
61 | $this->adapter->shouldReceive('acknowledge')->once()->with(iterator_to_array($this->messages));
62 |
63 | $msgs = [];
64 | $handler($this->messages, $this->adapter, function ($msg, Closure $done) use (&$msgs) {
65 | $msgs[] = $msg;
66 | });
67 |
68 | assertThat($msgs, is(identicalTo(iterator_to_array($this->messages))));
69 | }
70 |
71 | public function testHandleInvalidMessage()
72 | {
73 | $handler = $this->handler;
74 |
75 | $this->messageA->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
76 | $this->messageB->shouldReceive('isValid')->once()->withNoArgs()->andReturn(false);
77 | $this->messageC->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
78 | $this->adapter->shouldReceive('acknowledge')->once()->with([$this->messageA, $this->messageC]);
79 |
80 | $msgs = [];
81 | $handler($this->messages, $this->adapter, function ($msg) use (&$msgs) {
82 | $msgs[] = $msg;
83 | });
84 |
85 | assertThat($msgs, is(identicalTo([$this->messageA, $this->messageC])));
86 | }
87 |
88 | /**
89 | * @expectedException \RuntimeException
90 | * @expectedExceptionMessage foo
91 | */
92 | public function testHandleWorkerWithThrownException()
93 | {
94 | $handler = $this->handler;
95 |
96 | $this->messageA->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
97 | $this->messageB->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
98 |
99 | $this->adapter->shouldReceive('acknowledge')->once()->with([$this->messageA]);
100 |
101 | $handler($this->messages, $this->adapter, function ($msg) {
102 | if ($msg === $this->messageB) {
103 | throw new RuntimeException('foo');
104 | }
105 | });
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/tests/unit/Adapter/FirehoseAdapterTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter;
17 |
18 | use Aws\ResultInterface;
19 | use Aws\Firehose\FirehoseClient;
20 | use Graze\Queue\Adapter\Exception\MethodNotSupportedException;
21 | use Graze\Queue\Message\MessageFactoryInterface;
22 | use Graze\Queue\Message\MessageInterface;
23 | use Mockery as m;
24 | use Mockery\MockInterface;
25 | use Graze\Queue\Test\TestCase;
26 |
27 | class FirehoseAdapterTest extends TestCase
28 | {
29 | /** @var MessageInterface|MockInterface */
30 | private $messageA;
31 | /** @var MessageInterface|MockInterface */
32 | private $messageB;
33 | /** @var MessageInterface|MockInterface */
34 | private $messageC;
35 | /** @var MessageInterface[]|MockInterface[] */
36 | private $messages;
37 | /** @var ResultInterface|MockInterface */
38 | private $model;
39 | /** @var MessageFactoryInterface|MockInterface */
40 | private $factory;
41 | /** @var FirehoseClient */
42 | private $client;
43 |
44 | public function setUp()
45 | {
46 | $this->client = m::mock(FirehoseClient::class);
47 | $this->model = m::mock(ResultInterface::class);
48 | $this->factory = m::mock(MessageFactoryInterface::class);
49 |
50 | $this->messageA = $a = m::mock(MessageInterface::class);
51 | $this->messageB = $b = m::mock(MessageInterface::class);
52 | $this->messageC = $c = m::mock(MessageInterface::class);
53 | $this->messages = [$a, $b, $c];
54 | }
55 |
56 | public function testInterface()
57 | {
58 | assertThat(new FirehoseAdapter($this->client, 'foo'), is(anInstanceOf('Graze\Queue\Adapter\AdapterInterface')));
59 | }
60 |
61 | public function testEnqueue()
62 | {
63 | $adapter = new FirehoseAdapter($this->client, 'foo');
64 |
65 | $this->messageA->shouldReceive('getBody')->once()->withNoArgs()->andReturn('foo');
66 | $this->messageB->shouldReceive('getBody')->once()->withNoArgs()->andReturn('bar');
67 | $this->messageC->shouldReceive('getBody')->once()->withNoArgs()->andReturn('baz');
68 |
69 | $this->model->shouldReceive('get')->once()->with('RequestResponses')->andReturn([]);
70 |
71 | $this->client->shouldReceive('putRecordBatch')->once()->with([
72 | 'DeliveryStreamName' => 'foo',
73 | 'Records' => [
74 | ['Data' => 'foo'],
75 | ['Data' => 'bar'],
76 | ['Data' => 'baz'],
77 | ],
78 | ])->andReturn($this->model);
79 |
80 | $adapter->enqueue($this->messages);
81 | }
82 |
83 | /**
84 | * @expectedException \Graze\Queue\Adapter\Exception\MethodNotSupportedException
85 | */
86 | public function testAcknowledge()
87 | {
88 | $adapter = new FirehoseAdapter($this->client, 'foo');
89 | $adapter->acknowledge($this->messages);
90 | }
91 |
92 | /**
93 | * @expectedException \Graze\Queue\Adapter\Exception\MethodNotSupportedException
94 | */
95 | public function testDequeue()
96 | {
97 | $adapter = new FirehoseAdapter($this->client, 'foo');
98 | $adapter->dequeue($this->factory, 10);
99 | }
100 |
101 | /**
102 | * @expectedException \Graze\Queue\Adapter\Exception\MethodNotSupportedException
103 | */
104 | public function testPurge()
105 | {
106 | $adapter = new FirehoseAdapter($this->client, 'foo');
107 | $adapter->purge();
108 | }
109 |
110 | /**
111 | * @expectedException \Graze\Queue\Adapter\Exception\MethodNotSupportedException
112 | */
113 | public function testDelete()
114 | {
115 | $adapter = new FirehoseAdapter($this->client, 'foo');
116 | $adapter->delete();
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/tests/unit/Handler/EagerAcknowledgementHandlerTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Handler;
17 |
18 | use ArrayIterator;
19 | use Graze\Queue\Adapter\AdapterInterface;
20 | use Graze\Queue\Message\MessageInterface;
21 | use Mockery as m;
22 | use Mockery\MockInterface;
23 | use Graze\Queue\Test\TestCase;
24 | use RuntimeException;
25 |
26 | class EagerAcknowledgementHandlerTest extends TestCase
27 | {
28 | /** @var AdapterInterface|MockInterface */
29 | private $adapter;
30 | /** @var MessageInterface|MockInterface */
31 | private $messageA;
32 | /** @var MessageInterface|MockInterface */
33 | private $messageB;
34 | /** @var MessageInterface|MockInterface */
35 | private $messageC;
36 | /** @var ArrayIterator */
37 | private $messages;
38 | /** @var EagerAcknowledgementHandler */
39 | private $handler;
40 |
41 | public function setUp()
42 | {
43 | $this->adapter = m::mock(AdapterInterface::class);
44 |
45 | $this->messageA = $a = m::mock(MessageInterface::class);
46 | $this->messageB = $b = m::mock(MessageInterface::class);
47 | $this->messageC = $c = m::mock(MessageInterface::class);
48 | $this->messages = new ArrayIterator([$a, $b, $c]);
49 |
50 | $this->handler = new EagerAcknowledgementHandler();
51 | }
52 |
53 | public function testHandle()
54 | {
55 | $handler = $this->handler;
56 |
57 | $this->messageA->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
58 | $this->messageB->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
59 | $this->messageC->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
60 |
61 | // @see https://github.com/padraic/mockery/issues/331
62 | $this->adapter->shouldReceive('acknowledge')->once()->with(m::mustBe([$this->messageA]));
63 | $this->adapter->shouldReceive('acknowledge')->once()->with(m::mustBe([$this->messageB]));
64 | $this->adapter->shouldReceive('acknowledge')->once()->with(m::mustBe([$this->messageC]));
65 |
66 | $msgs = [];
67 | $handler($this->messages, $this->adapter, function ($msg) use (&$msgs) {
68 | $msgs[] = $msg;
69 | });
70 |
71 | assertThat($msgs, is(identicalTo(iterator_to_array($this->messages))));
72 | }
73 |
74 | public function testHandleInvalidMessage()
75 | {
76 | $handler = $this->handler;
77 |
78 | $this->messageA->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
79 | $this->messageB->shouldReceive('isValid')->once()->withNoArgs()->andReturn(false);
80 | $this->messageC->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
81 |
82 | // @see https://github.com/padraic/mockery/issues/331
83 | $this->adapter->shouldReceive('acknowledge')->once()->with(m::mustBe([$this->messageA]));
84 | $this->adapter->shouldReceive('acknowledge')->once()->with(m::mustBe([$this->messageC]));
85 |
86 | $msgs = [];
87 | $handler($this->messages, $this->adapter, function ($msg) use (&$msgs) {
88 | $msgs[] = $msg;
89 | });
90 |
91 | assertThat($msgs, is(identicalTo([$this->messageA, $this->messageC])));
92 | }
93 |
94 | /**
95 | * @expectedException \RuntimeException
96 | * @expectedExceptionMessage foo
97 | */
98 | public function testHandleWorkerWithThrownException()
99 | {
100 | $handler = $this->handler;
101 |
102 | $this->messageA->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
103 | $this->messageB->shouldReceive('isValid')->once()->withNoArgs()->andReturn(true);
104 |
105 | // @see https://github.com/padraic/mockery/issues/331
106 | $this->adapter->shouldReceive('acknowledge')->once()->with(m::mustBe([$this->messageA]));
107 |
108 | $handler($this->messages, $this->adapter, function ($msg) {
109 | if ($msg === $this->messageB) {
110 | throw new RuntimeException('foo');
111 | }
112 | });
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Queue
2 |
3 |
4 |
5 | [![PHP ~5.5][ico-engine]][lang]
6 | [![Latest Version][ico-package]][package]
7 | [![MIT Licensed][ico-license]][license]
8 | [![Build Status][ico-build]][travis]
9 | [![Coverage Status][ico-coverage]][coverage]
10 | [![Quality Score][ico-quality]][quality]
11 | [![Total Downloads][ico-downloads]][downloads]
12 |
13 | This library provides a flexible abstraction layer for working with queues.
14 |
15 | It can be installed in whichever way you prefer, but we recommend [Composer][package].
16 |
17 | `~$ composer require graze/queue`
18 |
19 |
20 | [travis]: https://travis-ci.org/graze/queue
21 | [lang]: https://secure.php.net
22 | [package]: https://packagist.org/packages/graze/queue
23 | [license]: https://github.com/graze/queue/blob/master/LICENSE
24 | [coverage]: https://scrutinizer-ci.com/g/graze/queue/code-structure
25 | [quality]: https://scrutinizer-ci.com/g/graze/queue
26 | [downloads]: https://packagist.org/packages/graze/queue
27 |
28 |
29 | [ico-license]: https://img.shields.io/packagist/l/graze/queue.svg?style=flat-square
30 | [ico-package]: https://img.shields.io/packagist/v/graze/queue.svg?style=flat-square
31 | [ico-build]: https://img.shields.io/travis/graze/queue/master.svg?style=flat-square
32 | [ico-engine]: https://img.shields.io/badge/php-%3E%3D5.5-8892BF.svg?style=flat-square
33 | [ico-coverage]: https://img.shields.io/scrutinizer/coverage/g/graze/queue.svg?style=flat-square
34 | [ico-quality]: https://img.shields.io/scrutinizer/g/graze/queue.svg?style=flat-square
35 | [ico-downloads]: https://img.shields.io/packagist/dt/graze/queue.svg?style=flat-square
36 |
37 | ## Documentation
38 |
39 | Queue operations center around lists of Message objects. Whether you're sending
40 | one or multiple Messages, it's always an array. Workers work only on one Message
41 | object at a time, whether a list of one or multiple is received from the queue.
42 |
43 | ```php
44 | use Aws\Sqs\SqsClient;
45 | use Graze\Queue\Adapter\SqsAdapter;
46 | use Graze\Queue\Client;
47 | use Graze\Queue\Message\MessageInterface;
48 |
49 | $client = new Client(new SqsAdapter(new SqsClient([
50 | 'region' => 'us-east-1',
51 | 'version' => '2012-11-05',
52 | 'credentials' => [
53 | 'key' => 'ive_got_the_key',
54 | 'secret' => 'ive_got_the_secret'
55 | ],
56 | ]), 'queue_name'));
57 |
58 | // Producer
59 | $client->send([
60 | $client->create('foo'),
61 | ]);
62 |
63 | // Consumer
64 | $client->receive(function (MessageInterface $msg) {
65 | var_dump($msg->getBody());
66 | var_dump($msg->getMetadata()->getAll());
67 | });
68 | ```
69 |
70 | ### Adapters
71 |
72 | The Adapter object is used to fulfill the low level requests to the queue provider.
73 |
74 | Currently supported queue providers are:
75 |
76 | - [Array](src/Adapter/ArrayAdapter.php)
77 | - [AWS SQS](src/Adapter/SqsAdapter.php) (with the [AWS SDK](http://aws.amazon.com/sdk-for-php/))
78 |
79 | ### Handlers
80 |
81 | The Handler object is used to execute worker callables with a list of received messages and handle Acknowledgement.
82 |
83 | The current handlers are:
84 |
85 | - [Batch Acknowledgement](src/Handler/BatchAcknowledgementHandler.php) to acknowledge batches
86 | - [Eager Acknowledgement](src/Handler/EagerAcknowledgementHandler.php) to acknowledge immediately after work
87 | - [Null Acknowledgement](src/Handler/NullAcknowledgementHandler.php) for development
88 |
89 | ```php
90 | use Graze\Queue\Client;
91 | use Graze\Queue\Adapter\ArrayAdapter;
92 | use Graze\Queue\Handler\BatchAcknowledgementHandler;
93 | use Graze\Queue\Message\MessageInterface;
94 |
95 | // Create client with the Batch Acknowledgement Handler.
96 | $client = new Client(new ArrayAdapter(), [
97 | 'handler' => new BatchAcknowledgementHandler(),
98 | ]);
99 |
100 | // Receive a maximum of 10 messages.
101 | $client->receive(function (MessageInterface $message) {
102 | // Do some work.
103 | }, 10);
104 | ```
105 |
106 | ### Polling
107 |
108 | Polling a queue is supported by passing `null` as the limit argument to the
109 | `receive` method. The second argument given to your worker is a `Closure` you
110 | should use to stop polling when you're finished working. Make sure you use a
111 | handler that acknowledges work effectively too!
112 |
113 | Note that the individual Adapter objects may decide to stop polling at any time.
114 | A likely scenario where it may stop would be if the queue is of finite length
115 | and all possible messages have been received.
116 |
117 | ```php
118 | use Graze\Queue\Client;
119 | use Graze\Queue\Adapter\ArrayAdapter;
120 | use Graze\Queue\Handler\BatchAcknowledgementHandler;
121 | use Graze\Queue\Message\MessageInterface;
122 |
123 | // Create client with the Batch Acknowledgement Handler.
124 | $client = new Client(new ArrayAdapter(), [
125 | 'handler' => new BatchAcknowledgeHandler(100), // Acknowledge after 100 messages.
126 | ]);
127 |
128 | // Poll until `$done()` is called.
129 | $client->receive(function (MessageInterface $message, Closure $done) {
130 | // Do some work.
131 |
132 | // You should always define a break condition (i.e. timeout, expired session, etc).
133 | if ($breakCondition) $done();
134 | }, null);
135 | ```
136 |
137 | ## License
138 |
139 | The content of this library is released under the **MIT License** by **Nature Delivered Ltd.**
140 |
141 | You can find a copy of this license in [`LICENSE`][license] or at http://opensource.org/licenses/mit.
142 |
--------------------------------------------------------------------------------
/tests/unit/Adapter/ArrayAdapterTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter;
17 |
18 | use Graze\Queue\Message\MessageFactoryInterface;
19 | use Graze\Queue\Message\MessageInterface;
20 | use Mockery as m;
21 | use Mockery\MockInterface;
22 | use Graze\Queue\Test\TestCase;
23 |
24 | class ArrayAdapterTest extends TestCase
25 | {
26 | /** @var MessageFactoryInterface|MockInterface */
27 | private $factory;
28 | /** @var MessageInterface|MockInterface */
29 | private $messageA;
30 | /** @var MessageInterface|MockInterface */
31 | private $messageB;
32 | /** @var MessageInterface|MockInterface */
33 | private $messageC;
34 | /** @var MessageInterface[]|MockInterface[] */
35 | private $messages;
36 | /** @var ArrayAdapter */
37 | private $adapter;
38 |
39 | public function setUp()
40 | {
41 | $this->factory = m::mock(MessageFactoryInterface::class);
42 |
43 | $this->messageA = $a = m::mock(MessageInterface::class);
44 | $this->messageB = $b = m::mock(MessageInterface::class);
45 | $this->messageC = $c = m::mock(MessageInterface::class);
46 | $this->messages = [$a, $b, $c];
47 |
48 | $this->adapter = new ArrayAdapter($this->messages);
49 | }
50 |
51 | public function testInterface()
52 | {
53 | assertThat($this->adapter, is(anInstanceOf(AdapterInterface::class)));
54 | }
55 |
56 | public function testAcknowledge()
57 | {
58 | $this->adapter->acknowledge($this->messages);
59 |
60 | $iterator = $this->adapter->dequeue($this->factory, 10);
61 |
62 | assertThat(iterator_to_array($iterator), is(identicalTo([])));
63 | }
64 |
65 | public function testAcknowledgeOne()
66 | {
67 | $this->adapter->acknowledge([$this->messageB]);
68 |
69 | $iterator = $this->adapter->dequeue($this->factory, 10);
70 |
71 | assertThat(iterator_to_array($iterator), is(identicalTo([$this->messageA, $this->messageC])));
72 | }
73 |
74 | public function testReject()
75 | {
76 | $this->adapter->reject($this->messages);
77 |
78 | $iterator = $this->adapter->dequeue($this->factory, 10);
79 |
80 | assertThat(iterator_to_array($iterator), is(identicalTo($this->messages)));
81 | }
82 |
83 | public function testRejectOne()
84 | {
85 | $this->adapter->reject([$this->messageA]);
86 |
87 | $iterator = $this->adapter->dequeue($this->factory, 10);
88 |
89 | assertThat(iterator_to_array($iterator), is(identicalTo($this->messages)));
90 | }
91 |
92 | public function testDequeue()
93 | {
94 | $iterator = $this->adapter->dequeue($this->factory, 10);
95 |
96 | assertThat(iterator_to_array($iterator), is(identicalTo($this->messages)));
97 | }
98 |
99 | public function testDequeueWithLimit()
100 | {
101 | $iterator = $this->adapter->dequeue($this->factory, 1);
102 |
103 | assertThat(iterator_to_array($iterator), is(identicalTo([$this->messageA])));
104 | }
105 |
106 | public function testDequeueWithPollingLimit()
107 | {
108 | $iterator = $this->adapter->dequeue($this->factory, null);
109 |
110 | assertThat(iterator_to_array($iterator), is(identicalTo($this->messages)));
111 | }
112 |
113 | public function testDequeueWithNoMessages()
114 | {
115 | $adapter = new ArrayAdapter();
116 |
117 | $iterator = $adapter->dequeue($this->factory, null);
118 |
119 | assertThat(iterator_to_array($iterator), is(emptyArray()));
120 | }
121 |
122 | public function testDequeueWithLimitAndNoMessages()
123 | {
124 | $adapter = new ArrayAdapter();
125 |
126 | $iterator = $adapter->dequeue($this->factory, 10);
127 |
128 | assertThat(iterator_to_array($iterator), is(emptyArray()));
129 | }
130 |
131 | public function testEnqueue()
132 | {
133 | $messageA = m::mock(MessageInterface::class);
134 | $messageB = m::mock(MessageInterface::class);
135 | $messageC = m::mock(MessageInterface::class);
136 | $messages = [$messageA, $messageB, $messageC];
137 | $merged = array_merge($this->messages, $messages);
138 |
139 | $this->adapter->enqueue($messages);
140 |
141 | $iterator = $this->adapter->dequeue($this->factory, 10);
142 |
143 | assertThat(iterator_to_array($iterator), is(identicalTo($merged)));
144 | }
145 |
146 | public function testPurge()
147 | {
148 | $iterator = $this->adapter->dequeue($this->factory, 10);
149 |
150 | assertThat(iterator_to_array($iterator), is(nonEmptyArray()));
151 |
152 | $this->adapter->purge();
153 |
154 | $iterator = $this->adapter->dequeue($this->factory, 10);
155 |
156 | assertThat(iterator_to_array($iterator), is(emptyArray()));
157 | }
158 |
159 | public function testDelete()
160 | {
161 | $iterator = $this->adapter->dequeue($this->factory, 10);
162 |
163 | assertThat(iterator_to_array($iterator), is(nonEmptyArray()));
164 |
165 | $this->adapter->delete();
166 |
167 | $iterator = $this->adapter->dequeue($this->factory, 10);
168 |
169 | assertThat(iterator_to_array($iterator), is(emptyArray()));
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/src/Adapter/FirehoseAdapter.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter;
17 |
18 | use Aws\Firehose\FirehoseClient;
19 | use Graze\Queue\Adapter\Exception\FailedEnqueueException;
20 | use Graze\Queue\Adapter\Exception\MethodNotSupportedException;
21 | use Graze\Queue\Message\MessageFactoryInterface;
22 | use Graze\Queue\Message\MessageInterface;
23 |
24 | /**
25 | * Amazon AWS Kinesis Firehose Adapter.
26 | *
27 | * This method only supports the enqueue method to send messages to a Kinesiss
28 | * Firehose stream
29 | *
30 | * @link http://docs.aws.amazon.com/aws-sdk-php/latest/class-Aws.Firehose.FirehoseClient.html#putRecordBatch
31 | */
32 | final class FirehoseAdapter implements AdapterInterface
33 | {
34 | const BATCHSIZE_SEND = 100;
35 |
36 | /** @var FirehoseClient */
37 | protected $client;
38 |
39 | /** @var array */
40 | protected $options;
41 |
42 | /** @var string */
43 | protected $deliveryStreamName;
44 |
45 | /**
46 | * @param FirehoseClient $client
47 | * @param string $deliveryStreamName
48 | * @param array $options - BatchSize The number of messages to send in each batch.
49 | */
50 | public function __construct(FirehoseClient $client, $deliveryStreamName, array $options = [])
51 | {
52 | $this->client = $client;
53 | $this->deliveryStreamName = $deliveryStreamName;
54 | $this->options = $options;
55 | }
56 |
57 | /**
58 | * @param MessageInterface[] $messages
59 | *
60 | * @throws MethodNotSupportedException
61 | */
62 | public function acknowledge(array $messages)
63 | {
64 | throw new MethodNotSupportedException(
65 | __FUNCTION__,
66 | $this,
67 | $messages
68 | );
69 | }
70 |
71 | /**
72 | * @param MessageInterface[] $messages
73 | * @param int $duration Number of seconds to ensure that this message stays being processed and not
74 | * put back on the queue
75 | */
76 | public function extend(array $messages, $duration)
77 | {
78 | throw new MethodNotSupportedException(
79 | __FUNCTION__,
80 | $this,
81 | $messages
82 | );
83 | }
84 |
85 | /**
86 | * @param MessageInterface[] $messages
87 | */
88 | public function reject(array $messages)
89 | {
90 | throw new MethodNotSupportedException(
91 | __FUNCTION__,
92 | $this,
93 | $messages
94 | );
95 | }
96 |
97 | /**
98 | * @param MessageFactoryInterface $factory
99 | * @param int $limit
100 | *
101 | * @throws MethodNotSupportedException
102 | */
103 | public function dequeue(MessageFactoryInterface $factory, $limit)
104 | {
105 | throw new MethodNotSupportedException(
106 | __FUNCTION__,
107 | $this,
108 | []
109 | );
110 | }
111 |
112 | /**
113 | * @param MessageInterface[] $messages
114 | *
115 | * @throws FailedEnqueueException
116 | */
117 | public function enqueue(array $messages)
118 | {
119 | $failed = [];
120 | $batches = array_chunk(
121 | $messages,
122 | $this->getOption('BatchSize', self::BATCHSIZE_SEND)
123 | );
124 |
125 | foreach ($batches as $batch) {
126 | $requestRecords = array_map(
127 | function (MessageInterface $message) {
128 | return [
129 | 'Data' => $message->getBody(),
130 | ];
131 | },
132 | $batch
133 | );
134 |
135 | $request = [
136 | 'DeliveryStreamName' => $this->deliveryStreamName,
137 | 'Records' => $requestRecords,
138 | ];
139 |
140 | $results = $this->client->putRecordBatch($request);
141 |
142 | foreach ($results->get('RequestResponses') as $idx => $response) {
143 | if (isset($response['ErrorCode'])) {
144 | $failed[] = $batch[$idx];
145 | }
146 | }
147 | }
148 |
149 | if (!empty($failed)) {
150 | throw new FailedEnqueueException($this, $failed);
151 | }
152 | }
153 |
154 | /**
155 | * @param string $name
156 | * @param mixed $default
157 | *
158 | * @return mixed
159 | */
160 | protected function getOption($name, $default = null)
161 | {
162 | return isset($this->options[$name]) ? $this->options[$name] : $default;
163 | }
164 |
165 | /**
166 | * @throws MethodNotSupportedException
167 | */
168 | public function purge()
169 | {
170 | throw new MethodNotSupportedException(
171 | __FUNCTION__,
172 | $this,
173 | []
174 | );
175 | }
176 |
177 | /**
178 | * @throws MethodNotSupportedException
179 | */
180 | public function delete()
181 | {
182 | throw new MethodNotSupportedException(
183 | __FUNCTION__,
184 | $this,
185 | []
186 | );
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/tests/integration/SqsIntegrationTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue;
17 |
18 | use Aws\ResultInterface;
19 | use Aws\Sqs\SqsClient;
20 | use Graze\Queue\Adapter\SqsAdapter;
21 | use Mockery as m;
22 | use Mockery\MockInterface;
23 | use Graze\Queue\Test\TestCase;
24 |
25 | class SqsIntegrationTest extends TestCase
26 | {
27 | /** @var string */
28 | private $queueName;
29 | /** @var SqsClient|MockInterface */
30 | private $sqsClient;
31 | /** @var Client */
32 | private $client;
33 |
34 | public function setUp()
35 | {
36 | $this->queueName = 'queue_foo';
37 | $this->sqsClient = m::mock(SqsClient::class);
38 | $this->client = new Client(new SqsAdapter($this->sqsClient, 'queue_foo'));
39 | }
40 |
41 | /**
42 | * Create a queue
43 | *
44 | * @return string
45 | */
46 | protected function stubCreateQueue()
47 | {
48 | $url = 'queue://foo';
49 | $model = m::mock(ResultInterface::class);
50 | $model->shouldReceive('get')->once()->with('QueueUrl')->andReturn($url);
51 |
52 | $this->sqsClient->shouldReceive('createQueue')->once()->with([
53 | 'QueueName' => $this->queueName,
54 | 'Attributes' => [],
55 | ])->andReturn($model);
56 |
57 | return $url;
58 | }
59 |
60 | /**
61 | * @param string $url
62 | *
63 | * @return int
64 | */
65 | protected function stubQueueVisibilityTimeout($url)
66 | {
67 | $timeout = 120;
68 | $model = m::mock(ResultInterface::class);
69 | $model->shouldReceive('get')->once()->with('Attributes')->andReturn(['VisibilityTimeout' => $timeout]);
70 |
71 | $this->sqsClient->shouldReceive('getQueueAttributes')->once()->with([
72 | 'QueueUrl' => $url,
73 | 'AttributeNames' => ['VisibilityTimeout'],
74 | ])->andReturn($model);
75 |
76 | return $timeout;
77 | }
78 |
79 | public function testReceive()
80 | {
81 | $url = $this->stubCreateQueue();
82 | $timeout = $this->stubQueueVisibilityTimeout($url);
83 |
84 | $receiveModel = m::mock(ResultInterface::class);
85 | $receiveModel->shouldReceive('get')->once()->with('Messages')->andReturn([
86 | ['Body' => 'foo', 'Attributes' => [], 'MessageAttributes' => [], 'MessageId' => 0, 'ReceiptHandle' => 'a'],
87 | ]);
88 | $this->sqsClient->shouldReceive('receiveMessage')->once()->with([
89 | 'QueueUrl' => $url,
90 | 'AttributeNames' => ['All'],
91 | 'MaxNumberOfMessages' => 1,
92 | 'VisibilityTimeout' => $timeout,
93 | ])->andReturn($receiveModel);
94 |
95 | $deleteModel = m::mock(ResultInterface::class);
96 | $deleteModel->shouldReceive('get')->once()->with('Failed')->andReturn([]);
97 | $this->sqsClient->shouldReceive('deleteMessageBatch')->once()->with([
98 | 'QueueUrl' => $url,
99 | 'Entries' => [['Id' => 0, 'ReceiptHandle' => 'a']],
100 | ])->andReturn($deleteModel);
101 |
102 | $msgs = [];
103 | $this->client->receive(function ($msg) use (&$msgs) {
104 | $msgs[] = $msg;
105 | });
106 |
107 | assertThat($msgs, is(arrayWithSize(1)));
108 | }
109 |
110 | public function testReceiveWithReceiveMessageReturningLessThanMaxNumberOfMessages()
111 | {
112 | $url = $this->stubCreateQueue();
113 | $timeout = $this->stubQueueVisibilityTimeout($url);
114 |
115 | $receiveModel = m::mock(ResultInterface::class);
116 | $receiveModel->shouldReceive('get')->with('Messages')->andReturn(
117 | [
118 | [
119 | 'Body' => 'foo',
120 | 'Attributes' => [],
121 | 'MessageAttributes' => [],
122 | 'MessageId' => 0,
123 | 'ReceiptHandle' => 'a',
124 | ],
125 | [
126 | 'Body' => 'foo',
127 | 'Attributes' => [],
128 | 'MessageAttributes' => [],
129 | 'MessageId' => 0,
130 | 'ReceiptHandle' => 'a',
131 | ],
132 | [
133 | 'Body' => 'foo',
134 | 'Attributes' => [],
135 | 'MessageAttributes' => [],
136 | 'MessageId' => 0,
137 | 'ReceiptHandle' => 'a',
138 | ],
139 | [
140 | 'Body' => 'foo',
141 | 'Attributes' => [],
142 | 'MessageAttributes' => [],
143 | 'MessageId' => 0,
144 | 'ReceiptHandle' => 'a',
145 | ],
146 | [
147 | 'Body' => 'foo',
148 | 'Attributes' => [],
149 | 'MessageAttributes' => [],
150 | 'MessageId' => 0,
151 | 'ReceiptHandle' => 'a',
152 | ],
153 | [
154 | 'Body' => 'foo',
155 | 'Attributes' => [],
156 | 'MessageAttributes' => [],
157 | 'MessageId' => 0,
158 | 'ReceiptHandle' => 'a',
159 | ],
160 | [
161 | 'Body' => 'foo',
162 | 'Attributes' => [],
163 | 'MessageAttributes' => [],
164 | 'MessageId' => 0,
165 | 'ReceiptHandle' => 'a',
166 | ],
167 | [
168 | 'Body' => 'foo',
169 | 'Attributes' => [],
170 | 'MessageAttributes' => [],
171 | 'MessageId' => 0,
172 | 'ReceiptHandle' => 'a',
173 | ],
174 | [
175 | 'Body' => 'foo',
176 | 'Attributes' => [],
177 | 'MessageAttributes' => [],
178 | 'MessageId' => 0,
179 | 'ReceiptHandle' => 'a',
180 | ],
181 | ],
182 | [
183 | [
184 | 'Body' => 'foo',
185 | 'Attributes' => [],
186 | 'MessageAttributes' => [],
187 | 'MessageId' => 0,
188 | 'ReceiptHandle' => 'a',
189 | ],
190 | [
191 | 'Body' => 'foo',
192 | 'Attributes' => [],
193 | 'MessageAttributes' => [],
194 | 'MessageId' => 0,
195 | 'ReceiptHandle' => 'a',
196 | ],
197 | ],
198 | null
199 | );
200 |
201 | $this->sqsClient->shouldReceive('receiveMessage')->andReturn($receiveModel);
202 |
203 | $deleteModel = m::mock(ResultInterface::class);
204 | $deleteModel->shouldReceive('get')->twice()->with('Failed')->andReturn([]);
205 | $this->sqsClient->shouldReceive('deleteMessageBatch')->with(m::type('array'))->andReturn($deleteModel);
206 |
207 | $msgs = [];
208 | $this->client->receive(function ($msg) use (&$msgs) {
209 | $msgs[] = $msg;
210 | }, 11);
211 |
212 | assertThat($msgs, is(arrayWithSize(11)));
213 | }
214 |
215 | public function testReceiveWithLimit()
216 | {
217 | $url = $this->stubCreateQueue();
218 | $timeout = $this->stubQueueVisibilityTimeout($url);
219 |
220 | $receiveModel = m::mock(ResultInterface::class);
221 | $receiveModel->shouldReceive('get')->once()->with('Messages')->andReturn([
222 | ['Body' => 'foo', 'Attributes' => [], 'MessageAttributes' => [], 'MessageId' => 0, 'ReceiptHandle' => 'a'],
223 | ]);
224 | $this->sqsClient->shouldReceive('receiveMessage')->once()->with([
225 | 'QueueUrl' => $url,
226 | 'AttributeNames' => ['All'],
227 | 'MaxNumberOfMessages' => SqsAdapter::BATCHSIZE_RECEIVE,
228 | 'VisibilityTimeout' => $timeout,
229 | ])->andReturn($receiveModel);
230 |
231 | $deleteModel = m::mock(ResultInterface::class);
232 | $deleteModel->shouldReceive('get')->once()->with('Failed')->andReturn([]);
233 | $this->sqsClient->shouldReceive('deleteMessageBatch')->once()->with([
234 | 'QueueUrl' => $url,
235 | 'Entries' => [['Id' => 0, 'ReceiptHandle' => 'a']],
236 | ])->andReturn($deleteModel);
237 |
238 | $msgs = [];
239 | $this->client->receive(function ($msg, $done) use (&$msgs) {
240 | $msgs[] = $msg;
241 | $done();
242 | }, 100);
243 |
244 | assertThat($msgs, is(arrayWithSize(1)));
245 | }
246 |
247 | public function testReceiveWithPolling()
248 | {
249 | $url = $this->stubCreateQueue();
250 | $timeout = $this->stubQueueVisibilityTimeout($url);
251 |
252 | $receiveModel = m::mock(ResultInterface::class);
253 | $receiveModel->shouldReceive('get')->once()->with('Messages')->andReturn([
254 | ['Body' => 'foo', 'Attributes' => [], 'MessageAttributes' => [], 'MessageId' => 0, 'ReceiptHandle' => 'a'],
255 | ]);
256 | $this->sqsClient->shouldReceive('receiveMessage')->once()->with([
257 | 'QueueUrl' => $url,
258 | 'AttributeNames' => ['All'],
259 | 'MaxNumberOfMessages' => SqsAdapter::BATCHSIZE_RECEIVE,
260 | 'VisibilityTimeout' => $timeout,
261 | ])->andReturn($receiveModel);
262 |
263 | $deleteModel = m::mock(ResultInterface::class);
264 | $deleteModel->shouldReceive('get')->once()->with('Failed')->andReturn([]);
265 | $this->sqsClient->shouldReceive('deleteMessageBatch')->once()->with([
266 | 'QueueUrl' => $url,
267 | 'Entries' => [['Id' => 0, 'ReceiptHandle' => 'a']],
268 | ])->andReturn($deleteModel);
269 |
270 | $msgs = [];
271 | $this->client->receive(function ($msg, $done) use (&$msgs) {
272 | $msgs[] = $msg;
273 | $done();
274 | }, null);
275 |
276 | assertThat($msgs, is(arrayWithSize(1)));
277 | }
278 |
279 | public function testSend()
280 | {
281 | $url = $this->stubCreateQueue();
282 | $model = m::mock(ResultInterface::class);
283 | $model->shouldReceive('get')->once()->with('Failed')->andReturn([]);
284 |
285 | $this->sqsClient->shouldReceive('sendMessageBatch')->once()->with([
286 | 'QueueUrl' => $url,
287 | 'Entries' => [['Id' => 0, 'MessageBody' => 'foo', 'MessageAttributes' => []]],
288 | ])->andReturn($model);
289 |
290 | $this->client->send([$this->client->create('foo')]);
291 | }
292 |
293 | public function testPurge()
294 | {
295 | $url = $this->stubCreateQueue();
296 | $timeout = $this->stubQueueVisibilityTimeout($url);
297 |
298 | $receiveModel = m::mock(ResultInterface::class);
299 | $receiveModel->shouldReceive('get')->once()->with('Messages')->andReturn([]);
300 | $this->sqsClient->shouldReceive('receiveMessage')->once()->with([
301 | 'QueueUrl' => $url,
302 | 'AttributeNames' => ['All'],
303 | 'MaxNumberOfMessages' => 1,
304 | 'VisibilityTimeout' => $timeout,
305 | ])->andReturn($receiveModel);
306 |
307 | $purgeModel = m::mock(ResultInterface::class);
308 | $this->sqsClient->shouldReceive('purgeQueue')->once()->with([
309 | 'QueueUrl' => $url,
310 | ])->andReturn($purgeModel);
311 |
312 | $this->client->purge();
313 |
314 | $msgs = [];
315 | $this->client->receive(function ($msg) use (&$msgs) {
316 | $msgs[] = $msg;
317 | });
318 |
319 | assertThat($msgs, is(emptyArray()));
320 | }
321 |
322 | public function testDelete()
323 | {
324 | $url = $this->stubCreateQueue();
325 |
326 | $deleteModel = m::mock(ResultInterface::class);
327 | $this->sqsClient->shouldReceive('deleteQueue')->once()->with([
328 | 'QueueUrl' => $url,
329 | ])->andReturn($deleteModel);
330 |
331 | $this->client->delete();
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/src/Adapter/SqsAdapter.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter;
17 |
18 | use Aws\Sqs\SqsClient;
19 | use Graze\Queue\Adapter\Exception\FailedAcknowledgementException;
20 | use Graze\Queue\Adapter\Exception\FailedEnqueueException;
21 | use Graze\Queue\Adapter\Exception\FailedExtensionException;
22 | use Graze\Queue\Adapter\Exception\FailedRejectionException;
23 | use Graze\Queue\Message\MessageFactoryInterface;
24 | use Graze\Queue\Message\MessageInterface;
25 |
26 | /**
27 | * Amazon AWS SQS Adapter.
28 | *
29 | * By default this adapter uses standard polling, which may return an empty response
30 | * even if messages exist on the queue.
31 | *
32 | * > This happens when Amazon SQS uses short (standard) polling, the default behavior,
33 | * > where only a subset of the servers (based on a weighted random distribution) are
34 | * > queried to see if any messages are available to include in the response.
35 | *
36 | * You may want to consider setting the `ReceiveMessageWaitTimeSeconds`
37 | * option to enable long polling the queue, which queries all of the servers.
38 | *
39 | * @link https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-long-polling.html
40 | * @link http://docs.aws.amazon.com/aws-sdk-php/guide/latest/service-sqs.html
41 | * @link http://docs.aws.amazon.com/aws-sdk-php/latest/class-Aws.Sqs.SqsClient.html#_createQueue
42 | * @link http://docs.aws.amazon.com/aws-sdk-php/latest/class-Aws.Sqs.SqsClient.html#_deleteMessageBatch
43 | * @link http://docs.aws.amazon.com/aws-sdk-php/latest/class-Aws.Sqs.SqsClient.html#_receiveMessage
44 | * @link http://docs.aws.amazon.com/aws-sdk-php/latest/class-Aws.Sqs.SqsClient.html#_sendMessageBatch
45 | */
46 | final class SqsAdapter implements AdapterInterface, NamedInterface
47 | {
48 | const BATCHSIZE_DELETE = 10;
49 | const BATCHSIZE_RECEIVE = 10;
50 | const BATCHSIZE_SEND = 10;
51 | const BATCHSIZE_EXTEND = 10;
52 |
53 | /** @var SqsClient */
54 | protected $client;
55 |
56 | /** @var array */
57 | protected $options;
58 |
59 | /** @var string */
60 | protected $name;
61 |
62 | /** @var string */
63 | protected $url;
64 |
65 | /**
66 | * @param SqsClient $client
67 | * @param string $name
68 | * @param array $options - DelaySeconds The time in seconds that the delivery of all
69 | * messages in the queue will be delayed.
70 | * - MaximumMessageSize The limit of how many bytes a message
71 | * can contain before Amazon SQS rejects it.
72 | * - MessageRetentionPeriod The number of seconds Amazon SQS
73 | * retains a message.
74 | * - Policy The queue's policy. A valid form-url-encoded policy.
75 | * - ReceiveMessageWaitTimeSeconds The time for which a
76 | * ReceiveMessage call will wait for a message to arrive.
77 | * - VisibilityTimeout The visibility timeout for the queue.
78 | */
79 | public function __construct(SqsClient $client, $name, array $options = [])
80 | {
81 | $this->client = $client;
82 | $this->name = $name;
83 | $this->options = $options;
84 | }
85 |
86 | /**
87 | * @param MessageInterface[] $messages
88 | */
89 | public function acknowledge(array $messages)
90 | {
91 | $url = $this->getQueueUrl();
92 | $errors = [];
93 | $batches = array_chunk($this->createDeleteEntries($messages), self::BATCHSIZE_DELETE);
94 |
95 | foreach ($batches as $batch) {
96 | $results = $this->client->deleteMessageBatch([
97 | 'QueueUrl' => $url,
98 | 'Entries' => $batch,
99 | ]);
100 |
101 | $errors = array_merge($errors, $results->get('Failed') ?: []);
102 | }
103 | $map = function ($result) use ($messages) {
104 | return $messages[$result['Id']];
105 | };
106 | $failed = array_map($map, $errors);
107 |
108 | if (!empty($failed)) {
109 | throw new FailedAcknowledgementException($this, $failed, $errors);
110 | }
111 | }
112 |
113 | /**
114 | * @param MessageInterface[] $messages
115 | * @param int $duration Number of seconds to ensure that this message stays being processed and not
116 | * put back on the queue
117 | *
118 | * @return void
119 | */
120 | public function extend(array $messages, $duration)
121 | {
122 | $url = $this->getQueueUrl();
123 | $errors = [];
124 | $batches = array_chunk($this->createVisibilityTimeoutEntries($messages, $duration), self::BATCHSIZE_EXTEND);
125 |
126 | foreach ($batches as $batch) {
127 | $results = $this->client->changeMessageVisibilityBatch([
128 | 'QueueUrl' => $url,
129 | 'Entries' => $batch,
130 | ]);
131 |
132 | $errors = array_merge($errors, $results->get('Failed') ?: []);
133 | }
134 | $map = function ($result) use ($messages) {
135 | return $messages[$result['Id']];
136 | };
137 | $failed = array_map($map, $errors);
138 |
139 | if (!empty($failed)) {
140 | throw new FailedExtensionException($this, $failed, $errors);
141 | }
142 | }
143 |
144 | /**
145 | * @param MessageInterface[] $messages
146 | *
147 | * @throws FailedAcknowledgementException|void
148 | */
149 | public function reject(array $messages)
150 | {
151 | $url = $this->getQueueUrl();
152 | $errors = [];
153 | $batches = array_chunk($this->createRejectEntries($messages), self::BATCHSIZE_DELETE);
154 |
155 | foreach ($batches as $batch) {
156 | $results = $this->client->changeMessageVisibilityBatch([
157 | 'QueueUrl' => $url,
158 | 'Entries' => $batch,
159 | ]);
160 |
161 | $errors = array_merge($errors, $results->get('Failed') ?: []);
162 | }
163 | $map = function ($result) use ($messages) {
164 | return $messages[$result['Id']];
165 | };
166 | $failed = array_map($map, $errors);
167 |
168 | if (!empty($failed)) {
169 | throw new FailedRejectionException($this, $failed, $errors);
170 | }
171 | }
172 |
173 | /**
174 | * @param MessageFactoryInterface $factory
175 | * @param int $limit
176 | *
177 | * @return \Generator
178 | */
179 | public function dequeue(MessageFactoryInterface $factory, $limit)
180 | {
181 | $remaining = $limit ?: 0;
182 |
183 | while (null === $limit || $remaining > 0) {
184 | /**
185 | * If a limit has been specified, set {@see $size} so that we don't return more
186 | * than the requested number of messages if it's less than the batch size.
187 | */
188 | $size = ($limit !== null) ? min($remaining, self::BATCHSIZE_RECEIVE) : self::BATCHSIZE_RECEIVE;
189 |
190 | $timestamp = time() + $this->getQueueVisibilityTimeout();
191 | $validator = function () use ($timestamp) {
192 | return time() < $timestamp;
193 | };
194 |
195 | $results = $this->client->receiveMessage(array_filter([
196 | 'QueueUrl' => $this->getQueueUrl(),
197 | 'AttributeNames' => ['All'],
198 | 'MaxNumberOfMessages' => $size,
199 | 'VisibilityTimeout' => $this->getOption('VisibilityTimeout'),
200 | 'WaitTimeSeconds' => $this->getOption('ReceiveMessageWaitTimeSeconds'),
201 | ]));
202 |
203 | $messages = $results->get('Messages') ?: [];
204 |
205 | if (count($messages) === 0) {
206 | break;
207 | }
208 |
209 | foreach ($messages as $result) {
210 | yield $factory->createMessage(
211 | $result['Body'],
212 | [
213 | 'metadata' => $this->createMessageMetadata($result),
214 | 'validator' => $validator,
215 | ]
216 | );
217 | }
218 |
219 | // Decrement the number of messages remaining.
220 | $remaining -= count($messages);
221 | }
222 | }
223 |
224 | /**
225 | * @param MessageInterface[] $messages
226 | */
227 | public function enqueue(array $messages)
228 | {
229 | $url = $this->getQueueUrl();
230 | $errors = [];
231 | $batches = array_chunk($this->createEnqueueEntries($messages), self::BATCHSIZE_SEND);
232 |
233 | foreach ($batches as $batch) {
234 | $results = $this->client->sendMessageBatch([
235 | 'QueueUrl' => $url,
236 | 'Entries' => $batch,
237 | ]);
238 |
239 | $errors = array_merge($errors, $results->get('Failed') ?: []);
240 | }
241 | $map = function ($result) use ($messages) {
242 | return $messages[$result['Id']];
243 | };
244 | $failed = array_map($map, $errors);
245 |
246 | if (!empty($failed)) {
247 | throw new FailedEnqueueException($this, $failed, $errors);
248 | }
249 | }
250 |
251 | /**
252 | * {@inheritdoc}
253 | */
254 | public function purge()
255 | {
256 | $this->client->purgeQueue(['QueueUrl' => $this->getQueueUrl()]);
257 | }
258 |
259 | /**
260 | * {@inheritdoc}
261 | */
262 | public function delete()
263 | {
264 | $this->client->deleteQueue(['QueueUrl' => $this->getQueueUrl()]);
265 | }
266 |
267 | /**
268 | * @param MessageInterface[] $messages
269 | *
270 | * @return array
271 | */
272 | protected function createDeleteEntries(array $messages)
273 | {
274 | array_walk(
275 | $messages,
276 | function (MessageInterface &$message, $id) {
277 | $metadata = $message->getMetadata();
278 | $message = [
279 | 'Id' => $id,
280 | 'ReceiptHandle' => $metadata->get('ReceiptHandle'),
281 | ];
282 | }
283 | );
284 |
285 | return $messages;
286 | }
287 |
288 | /**
289 | * @param MessageInterface[] $messages
290 | *
291 | * @return array
292 | */
293 | protected function createRejectEntries(array $messages)
294 | {
295 | return $this->createVisibilityTimeoutEntries($messages, 0);
296 | }
297 |
298 | /**
299 | * @param MessageInterface[] $messages
300 | * @param int $timeout
301 | *
302 | * @return array
303 | */
304 | private function createVisibilityTimeoutEntries(array $messages, $timeout)
305 | {
306 | array_walk(
307 | $messages,
308 | function (MessageInterface &$message, $id) use ($timeout) {
309 | $metadata = $message->getMetadata();
310 | $message = [
311 | 'Id' => $id,
312 | 'ReceiptHandle' => $metadata->get('ReceiptHandle'),
313 | 'VisibilityTimeout' => $timeout,
314 | ];
315 | }
316 | );
317 |
318 | return $messages;
319 | }
320 |
321 | /**
322 | * @param MessageInterface[] $messages
323 | *
324 | * @return array
325 | */
326 | protected function createEnqueueEntries(array $messages)
327 | {
328 | array_walk(
329 | $messages,
330 | function (MessageInterface &$message, $id) {
331 | $metadata = $message->getMetadata();
332 | $message = [
333 | 'Id' => $id,
334 | 'MessageBody' => $message->getBody(),
335 | 'MessageAttributes' => $metadata->get('MessageAttributes') ?: [],
336 | ];
337 | if (!is_null($metadata->get('DelaySeconds'))) {
338 | $message['DelaySeconds'] = $metadata->get('DelaySeconds');
339 | }
340 | }
341 | );
342 |
343 | return $messages;
344 | }
345 |
346 | /**
347 | * @param array $result
348 | *
349 | * @return array
350 | */
351 | protected function createMessageMetadata(array $result)
352 | {
353 | return array_intersect_key(
354 | $result,
355 | [
356 | 'Attributes' => [],
357 | 'MessageAttributes' => [],
358 | 'MessageId' => null,
359 | 'ReceiptHandle' => null,
360 | ]
361 | );
362 | }
363 |
364 | /**
365 | * @param string $name
366 | * @param mixed $default
367 | *
368 | * @return mixed
369 | */
370 | protected function getOption($name, $default = null)
371 | {
372 | return isset($this->options[$name]) ? $this->options[$name] : $default;
373 | }
374 |
375 | /**
376 | * @return string
377 | */
378 | protected function getQueueUrl()
379 | {
380 | if (!$this->url) {
381 | $result = $this->client->createQueue([
382 | 'QueueName' => $this->name,
383 | 'Attributes' => $this->options,
384 | ]);
385 |
386 | $this->url = $result->get('QueueUrl');
387 | }
388 |
389 | return $this->url;
390 | }
391 |
392 | /**
393 | * @return int
394 | */
395 | protected function getQueueVisibilityTimeout()
396 | {
397 | if (!isset($this->options['VisibilityTimeout'])) {
398 | $result = $this->client->getQueueAttributes([
399 | 'QueueUrl' => $this->getQueueUrl(),
400 | 'AttributeNames' => ['VisibilityTimeout'],
401 | ]);
402 |
403 | $attributes = $result->get('Attributes');
404 | $this->options['VisibilityTimeout'] = $attributes['VisibilityTimeout'];
405 | }
406 |
407 | return $this->options['VisibilityTimeout'];
408 | }
409 |
410 | /**
411 | * @return string
412 | */
413 | public function getQueueName()
414 | {
415 | return $this->name;
416 | }
417 | }
418 |
--------------------------------------------------------------------------------
/tests/unit/Adapter/SqsAdapterTest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * @license https://github.com/graze/queue/blob/master/LICENSE MIT
12 | *
13 | * @link https://github.com/graze/queue
14 | */
15 |
16 | namespace Graze\Queue\Adapter;
17 |
18 | use Aws\ResultInterface;
19 | use Aws\Sqs\SqsClient;
20 | use Graze\DataStructure\Container\ContainerInterface;
21 | use Graze\Queue\Adapter\Exception\FailedAcknowledgementException;
22 | use Graze\Queue\Adapter\Exception\FailedExtensionException;
23 | use Graze\Queue\Adapter\Exception\FailedRejectionException;
24 | use Graze\Queue\Message\MessageFactoryInterface;
25 | use Graze\Queue\Message\MessageInterface;
26 | use Mockery as m;
27 | use Mockery\MockInterface;
28 | use Graze\Queue\Test\TestCase;
29 |
30 | class SqsAdapterTest extends TestCase
31 | {
32 | /** @var MessageInterface|MockInterface */
33 | private $messageA;
34 | /** @var MessageInterface|MockInterface */
35 | private $messageB;
36 | /** @var MessageInterface|MockInterface */
37 | private $messageC;
38 | /** @var MessageInterface[]|MockInterface[] */
39 | private $messages;
40 | /** @var ResultInterface|MockInterface */
41 | private $model;
42 | /** @var MessageFactoryInterface|MockInterface */
43 | private $factory;
44 | /** @var SqsClient */
45 | private $client;
46 |
47 | public function setUp()
48 | {
49 | $this->client = m::mock(SqsClient::class);
50 | $this->model = m::mock(ResultInterface::class);
51 | $this->factory = m::mock(MessageFactoryInterface::class);
52 |
53 | $this->messageA = $a = m::mock(MessageInterface::class);
54 | $this->messageB = $b = m::mock(MessageInterface::class);
55 | $this->messageC = $c = m::mock(MessageInterface::class);
56 | $this->messages = [$a, $b, $c];
57 | }
58 |
59 | /**
60 | * @param string $body
61 | * @param int $id
62 | * @param string $handle
63 | */
64 | protected function stubCreateDequeueMessage($body, $id, $handle)
65 | {
66 | $this->factory->shouldReceive('createMessage')->once()->with(
67 | $body,
68 | m::on(function ($opts) use ($id, $handle) {
69 | $meta = ['Attributes' => [], 'MessageAttributes' => [], 'MessageId' => $id, 'ReceiptHandle' => $handle];
70 | $validator = isset($opts['validator']) && is_callable($opts['validator']);
71 |
72 | return isset($opts['metadata']) && $opts['metadata'] === $meta && $validator;
73 | })
74 | )->andReturn($this->messageA);
75 | }
76 |
77 | /**
78 | * @param string $name
79 | * @param array $options
80 | *
81 | * @return string
82 | */
83 | protected function stubCreateQueue($name, array $options = [])
84 | {
85 | $url = 'foo://bar';
86 | $model = m::mock(ResultInterface::class);
87 | $model->shouldReceive('get')->once()->with('QueueUrl')->andReturn($url);
88 |
89 | $this->client->shouldReceive('createQueue')->once()->with([
90 | 'QueueName' => $name,
91 | 'Attributes' => $options,
92 | ])->andReturn($model);
93 |
94 | return $url;
95 | }
96 |
97 | /**
98 | * @param string $url
99 | *
100 | * @return int
101 | */
102 | protected function stubQueueVisibilityTimeout($url)
103 | {
104 | $timeout = 120;
105 | $model = m::mock(ResultInterface::class);
106 | $model->shouldReceive('get')->once()->with('Attributes')->andReturn(['VisibilityTimeout' => $timeout]);
107 |
108 | $this->client->shouldReceive('getQueueAttributes')->once()->with([
109 | 'QueueUrl' => $url,
110 | 'AttributeNames' => ['VisibilityTimeout'],
111 | ])->andReturn($model);
112 |
113 | return $timeout;
114 | }
115 |
116 | public function testInterface()
117 | {
118 | assertThat(new SqsAdapter($this->client, 'foo'), is(anInstanceOf('Graze\Queue\Adapter\AdapterInterface')));
119 | }
120 |
121 | public function testAcknowledge()
122 | {
123 | $adapter = new SqsAdapter($this->client, 'foo');
124 | $url = $this->stubCreateQueue('foo');
125 |
126 | $this->messageA->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('foo');
127 | $this->messageB->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('bar');
128 | $this->messageC->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('baz');
129 |
130 | $this->model->shouldReceive('get')->once()->with('Failed')->andReturn([]);
131 |
132 | $this->client->shouldReceive('deleteMessageBatch')->once()->with([
133 | 'QueueUrl' => $url,
134 | 'Entries' => [
135 | ['Id' => 0, 'ReceiptHandle' => 'foo'],
136 | ['Id' => 1, 'ReceiptHandle' => 'bar'],
137 | ['Id' => 2, 'ReceiptHandle' => 'baz'],
138 | ],
139 | ])->andReturn($this->model);
140 |
141 | $adapter->acknowledge($this->messages);
142 | }
143 |
144 | public function testFailureToAcknowledgeForSomeMessages()
145 | {
146 | $adapter = new SqsAdapter($this->client, 'foo');
147 | $url = $this->stubCreateQueue('foo');
148 |
149 | $this->messageA->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('foo');
150 | $this->messageB->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('bar');
151 | $this->messageC->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('baz');
152 |
153 | $this->model->shouldReceive('get')->once()->with('Failed')->andReturn([
154 | ['Id' => 2, 'Code' => 123, 'SenderFault' => true, 'Message' => 'baz is gone'],
155 | ]);
156 |
157 | $this->client->shouldReceive('deleteMessageBatch')->once()->with([
158 | 'QueueUrl' => $url,
159 | 'Entries' => [
160 | ['Id' => 0, 'ReceiptHandle' => 'foo'],
161 | ['Id' => 1, 'ReceiptHandle' => 'bar'],
162 | ['Id' => 2, 'ReceiptHandle' => 'baz'],
163 | ],
164 | ])->andReturn($this->model);
165 |
166 | $errorThrown = false;
167 | try {
168 | $adapter->acknowledge($this->messages);
169 | } catch (FailedAcknowledgementException $e) {
170 | assertThat($e->getMessages(), is(anArray([$this->messageC])));
171 | assertThat(
172 | $e->getDebug(),
173 | is(anArray([['Id' => 2, 'Code' => 123, 'SenderFault' => true, 'Message' => 'baz is gone']]))
174 | );
175 | $errorThrown = true;
176 | }
177 |
178 | assertThat('an exception is thrown', $errorThrown);
179 | }
180 |
181 | public function testReject()
182 | {
183 | $adapter = new SqsAdapter($this->client, 'foo');
184 | $url = $this->stubCreateQueue('foo');
185 |
186 | $this->messageA->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('foo');
187 | $this->messageB->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('bar');
188 | $this->messageC->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('baz');
189 |
190 | $this->model->shouldReceive('get')->once()->with('Failed')->andReturn([]);
191 |
192 | $this->client->shouldReceive('changeMessageVisibilityBatch')->once()->with([
193 | 'QueueUrl' => $url,
194 | 'Entries' => [
195 | ['Id' => 0, 'ReceiptHandle' => 'foo', 'VisibilityTimeout' => 0],
196 | ['Id' => 1, 'ReceiptHandle' => 'bar', 'VisibilityTimeout' => 0],
197 | ['Id' => 2, 'ReceiptHandle' => 'baz', 'VisibilityTimeout' => 0],
198 | ],
199 | ])->andReturn($this->model);
200 |
201 | $adapter->reject($this->messages);
202 | }
203 |
204 | public function testFailureToRejectForSomeMessages()
205 | {
206 | $adapter = new SqsAdapter($this->client, 'foo');
207 | $url = $this->stubCreateQueue('foo');
208 |
209 | $this->messageA->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('foo');
210 | $this->messageB->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('bar');
211 | $this->messageC->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('baz');
212 |
213 | $this->model->shouldReceive('get')->once()->with('Failed')->andReturn([
214 | ['Id' => 2, 'Code' => 123, 'SenderFault' => true, 'Message' => 'baz is gone'],
215 | ]);
216 |
217 | $this->client->shouldReceive('changeMessageVisibilityBatch')->once()->with([
218 | 'QueueUrl' => $url,
219 | 'Entries' => [
220 | ['Id' => 0, 'ReceiptHandle' => 'foo', 'VisibilityTimeout' => 0],
221 | ['Id' => 1, 'ReceiptHandle' => 'bar', 'VisibilityTimeout' => 0],
222 | ['Id' => 2, 'ReceiptHandle' => 'baz', 'VisibilityTimeout' => 0],
223 | ],
224 | ])->andReturn($this->model);
225 |
226 | $errorThrown = false;
227 | try {
228 | $adapter->reject($this->messages);
229 | } catch (FailedRejectionException $e) {
230 | assertThat($e->getMessages(), is(anArray([$this->messageC])));
231 | assertThat(
232 | $e->getDebug(),
233 | is(anArray([['Id' => 2, 'Code' => 123, 'SenderFault' => true, 'Message' => 'baz is gone']]))
234 | );
235 | $errorThrown = true;
236 | }
237 |
238 | assertThat('an exception is thrown', $errorThrown);
239 | }
240 |
241 | public function testExtension()
242 | {
243 | $adapter = new SqsAdapter($this->client, 'foo');
244 | $url = $this->stubCreateQueue('foo');
245 |
246 | $this->messageA->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('foo');
247 | $this->messageB->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('bar');
248 | $this->messageC->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('baz');
249 |
250 | $this->model->shouldReceive('get')->once()->with('Failed')->andReturn([]);
251 |
252 | $this->client->shouldReceive('changeMessageVisibilityBatch')->once()->with([
253 | 'QueueUrl' => $url,
254 | 'Entries' => [
255 | ['Id' => 0, 'ReceiptHandle' => 'foo', 'VisibilityTimeout' => 1200],
256 | ['Id' => 1, 'ReceiptHandle' => 'bar', 'VisibilityTimeout' => 1200],
257 | ['Id' => 2, 'ReceiptHandle' => 'baz', 'VisibilityTimeout' => 1200],
258 | ],
259 | ])->andReturn($this->model);
260 |
261 | $adapter->extend($this->messages, 1200);
262 | }
263 |
264 | public function testFailureToExtendForSomeMessages()
265 | {
266 | $adapter = new SqsAdapter($this->client, 'foo');
267 | $url = $this->stubCreateQueue('foo');
268 |
269 | $this->messageA->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('foo');
270 | $this->messageB->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('bar');
271 | $this->messageC->shouldReceive('getMetadata->get')->once()->with('ReceiptHandle')->andReturn('baz');
272 |
273 | $this->model->shouldReceive('get')->once()->with('Failed')->andReturn([
274 | ['Id' => 2, 'Code' => 123, 'SenderFault' => true, 'Message' => 'baz is gone'],
275 | ]);
276 |
277 | $this->client->shouldReceive('changeMessageVisibilityBatch')->once()->with([
278 | 'QueueUrl' => $url,
279 | 'Entries' => [
280 | ['Id' => 0, 'ReceiptHandle' => 'foo', 'VisibilityTimeout' => 1200],
281 | ['Id' => 1, 'ReceiptHandle' => 'bar', 'VisibilityTimeout' => 1200],
282 | ['Id' => 2, 'ReceiptHandle' => 'baz', 'VisibilityTimeout' => 1200],
283 | ],
284 | ])->andReturn($this->model);
285 |
286 | $errorThrown = false;
287 | try {
288 | $adapter->extend($this->messages, 1200);
289 | } catch (FailedExtensionException $e) {
290 | assertThat($e->getMessages(), is(anArray([$this->messageC])));
291 | assertThat(
292 | $e->getDebug(),
293 | is(anArray([['Id' => 2, 'Code' => 123, 'SenderFault' => true, 'Message' => 'baz is gone']]))
294 | );
295 | $errorThrown = true;
296 | }
297 |
298 | assertThat('an exception is thrown', $errorThrown);
299 | }
300 |
301 | public function testDequeue()
302 | {
303 | $adapter = new SqsAdapter($this->client, 'foo');
304 | $url = $this->stubCreateQueue('foo');
305 | $timeout = $this->stubQueueVisibilityTimeout($url);
306 |
307 | $this->stubCreateDequeueMessage('foo', 0, 'a');
308 | $this->stubCreateDequeueMessage('bar', 1, 'b');
309 | $this->stubCreateDequeueMessage('baz', 2, 'c');
310 |
311 | $this->model->shouldReceive('get')->once()->with('Messages')->andReturn([
312 | ['Body' => 'foo', 'Attributes' => [], 'MessageAttributes' => [], 'MessageId' => 0, 'ReceiptHandle' => 'a'],
313 | ['Body' => 'bar', 'Attributes' => [], 'MessageAttributes' => [], 'MessageId' => 1, 'ReceiptHandle' => 'b'],
314 | ['Body' => 'baz', 'Attributes' => [], 'MessageAttributes' => [], 'MessageId' => 2, 'ReceiptHandle' => 'c'],
315 | ]);
316 |
317 | $this->client->shouldReceive('receiveMessage')->once()->with([
318 | 'QueueUrl' => $url,
319 | 'AttributeNames' => ['All'],
320 | 'MaxNumberOfMessages' => 3,
321 | 'VisibilityTimeout' => $timeout,
322 | ])->andReturn($this->model);
323 |
324 | $iterator = $adapter->dequeue($this->factory, 3);
325 |
326 | assertThat($iterator, is(anInstanceOf('Generator')));
327 | assertThat(iterator_to_array($iterator), is(equalTo($this->messages)));
328 | }
329 |
330 | public function testDequeueInBatches()
331 | {
332 | $adapter = new SqsAdapter($this->client, 'foo');
333 | $url = $this->stubCreateQueue('foo');
334 | $timeout = $this->stubQueueVisibilityTimeout($url);
335 |
336 | $limit = SqsAdapter::BATCHSIZE_RECEIVE;
337 |
338 | $return = [];
339 | $messages = [];
340 |
341 | for ($i = 0; $i < $limit; $i++) {
342 | $this->stubCreateDequeueMessage('tmp' . $i, $i, 'h' . $i);
343 | $return[] = [
344 | 'Body' => 'tmp' . $i,
345 | 'Attributes' => [],
346 | 'MessageAttributes' => [],
347 | 'MessageId' => $i,
348 | 'ReceiptHandle' => 'h' . $i,
349 | ];
350 | $messages[] = $this->messageA;
351 | }
352 |
353 | $this->model->shouldReceive('get')->once()->with('Messages')->andReturn($return);
354 |
355 | $this->client->shouldReceive('receiveMessage')->once()->with([
356 | 'QueueUrl' => $url,
357 | 'AttributeNames' => ['All'],
358 | 'MaxNumberOfMessages' => $limit,
359 | 'VisibilityTimeout' => $timeout,
360 | ])->andReturn($this->model);
361 |
362 | $iterator = $adapter->dequeue($this->factory, $limit);
363 |
364 | assertThat($iterator, is(anInstanceOf('Generator')));
365 | assertThat(iterator_to_array($iterator), is(equalTo($messages)));
366 | }
367 |
368 | public function testEnqueue()
369 | {
370 | $adapter = new SqsAdapter($this->client, 'foo');
371 | $url = $this->stubCreateQueue('foo');
372 |
373 | $metadata = m::mock(ContainerInterface::class);
374 | $metadata->shouldReceive('get')
375 | ->with('MessageAttributes')
376 | ->times(3)
377 | ->andReturn(null);
378 | $metadata->shouldReceive('get')
379 | ->with('DelaySeconds')
380 | ->andReturn(null);
381 |
382 | $this->messageA->shouldReceive('getBody')->once()->withNoArgs()->andReturn('foo');
383 | $this->messageB->shouldReceive('getBody')->once()->withNoArgs()->andReturn('bar');
384 | $this->messageC->shouldReceive('getBody')->once()->withNoArgs()->andReturn('baz');
385 | $this->messageA->shouldReceive('getMetadata')->andReturn($metadata);
386 | $this->messageB->shouldReceive('getMetadata')->andReturn($metadata);
387 | $this->messageC->shouldReceive('getMetadata')->andReturn($metadata);
388 |
389 | $this->model->shouldReceive('get')->once()->with('Failed')->andReturn([]);
390 |
391 | $this->client->shouldReceive('sendMessageBatch')->once()->with([
392 | 'QueueUrl' => $url,
393 | 'Entries' => [
394 | ['Id' => 0, 'MessageBody' => 'foo', 'MessageAttributes' => []],
395 | ['Id' => 1, 'MessageBody' => 'bar', 'MessageAttributes' => []],
396 | ['Id' => 2, 'MessageBody' => 'baz', 'MessageAttributes' => []],
397 | ],
398 | ])->andReturn($this->model);
399 |
400 | $adapter->enqueue($this->messages);
401 | }
402 |
403 | public function testEnqueueWithDelaySecondsMetadata()
404 | {
405 | $adapter = new SqsAdapter($this->client, 'foo');
406 | $url = $this->stubCreateQueue('foo');
407 |
408 | $metadataA = m::mock(ContainerInterface::class);
409 | $metadataA->shouldReceive('get')->with('MessageAttributes')->once()->andReturn(null);
410 | $metadataA->shouldReceive('get')->with('DelaySeconds')->andReturn(1);
411 | $metadataB = m::mock(ContainerInterface::class);
412 | $metadataB->shouldReceive('get')->with('MessageAttributes')->once()->andReturn(null);
413 | $metadataB->shouldReceive('get')->with('DelaySeconds')->andReturn(2);
414 | $metadataC = m::mock(ContainerInterface::class);
415 | $metadataC->shouldReceive('get')->with('MessageAttributes')->once()->andReturn(null);
416 | $metadataC->shouldReceive('get')->with('DelaySeconds')->andReturn(3);
417 |
418 | $this->messageA->shouldReceive('getBody')->once()->withNoArgs()->andReturn('foo');
419 | $this->messageB->shouldReceive('getBody')->once()->withNoArgs()->andReturn('bar');
420 | $this->messageC->shouldReceive('getBody')->once()->withNoArgs()->andReturn('baz');
421 | $this->messageA->shouldReceive('getMetadata')->andReturn($metadataA);
422 | $this->messageB->shouldReceive('getMetadata')->andReturn($metadataB);
423 | $this->messageC->shouldReceive('getMetadata')->andReturn($metadataC);
424 |
425 | $this->model->shouldReceive('get')->once()->with('Failed')->andReturn([]);
426 |
427 | $this->client->shouldReceive('sendMessageBatch')->once()->with([
428 | 'QueueUrl' => $url,
429 | 'Entries' => [
430 | ['Id' => 0, 'MessageBody' => 'foo', 'MessageAttributes' => [], 'DelaySeconds' => 1],
431 | ['Id' => 1, 'MessageBody' => 'bar', 'MessageAttributes' => [], 'DelaySeconds' => 2],
432 | ['Id' => 2, 'MessageBody' => 'baz', 'MessageAttributes' => [], 'DelaySeconds' => 3],
433 | ],
434 | ])->andReturn($this->model);
435 |
436 | $adapter->enqueue($this->messages);
437 | }
438 |
439 | public function testEnqueueWithDelaySecondsQueueConfiguration()
440 | {
441 | $options = ['DelaySeconds' => 10];
442 |
443 | $adapter = new SqsAdapter($this->client, 'foo', $options);
444 | $url = $this->stubCreateQueue('foo', $options);
445 |
446 | $metadataA = m::mock(ContainerInterface::class);
447 | $metadataA->shouldReceive('get')->with('MessageAttributes')->once()->andReturn(null);
448 | $metadataA->shouldReceive('get')->with('DelaySeconds')->andReturn(null);
449 | $metadataB = m::mock(ContainerInterface::class);
450 | $metadataB->shouldReceive('get')->with('MessageAttributes')->once()->andReturn(null);
451 | $metadataB->shouldReceive('get')->with('DelaySeconds')->andReturn(0);
452 | $metadataC = m::mock(ContainerInterface::class);
453 | $metadataC->shouldReceive('get')->with('MessageAttributes')->once()->andReturn(null);
454 | $metadataC->shouldReceive('get')->with('DelaySeconds')->andReturn(2);
455 |
456 | $this->messageA->shouldReceive('getBody')->once()->withNoArgs()->andReturn('foo');
457 | $this->messageB->shouldReceive('getBody')->once()->withNoArgs()->andReturn('bar');
458 | $this->messageC->shouldReceive('getBody')->once()->withNoArgs()->andReturn('baz');
459 | $this->messageA->shouldReceive('getMetadata')->andReturn($metadataA);
460 | $this->messageB->shouldReceive('getMetadata')->andReturn($metadataB);
461 | $this->messageC->shouldReceive('getMetadata')->andReturn($metadataC);
462 |
463 | $this->model->shouldReceive('get')->once()->with('Failed')->andReturn([]);
464 |
465 | $this->client->shouldReceive('sendMessageBatch')->once()->with([
466 | 'QueueUrl' => $url,
467 | 'Entries' => [
468 | ['Id' => 0, 'MessageBody' => 'foo', 'MessageAttributes' => []],
469 | ['Id' => 1, 'MessageBody' => 'bar', 'MessageAttributes' => [], 'DelaySeconds' => 0],
470 | ['Id' => 2, 'MessageBody' => 'baz', 'MessageAttributes' => [], 'DelaySeconds' => 2],
471 | ],
472 | ])->andReturn($this->model);
473 |
474 | $adapter->enqueue($this->messages);
475 | }
476 |
477 | public function testReceiveMessageWaitTimeSecondsOption()
478 | {
479 | $options = ['ReceiveMessageWaitTimeSeconds' => 20];
480 |
481 | $adapter = new SqsAdapter($this->client, 'foo', $options);
482 | $url = $this->stubCreateQueue('foo', $options);
483 | $timeout = $this->stubQueueVisibilityTimeout($url);
484 |
485 | $this->stubCreateDequeueMessage('foo', 0, 'a');
486 | $this->stubCreateDequeueMessage('bar', 1, 'b');
487 | $this->stubCreateDequeueMessage('baz', 2, 'c');
488 |
489 | $this->model->shouldReceive('get')->once()->with('Messages')->andReturn([
490 | ['Body' => 'foo', 'Attributes' => [], 'MessageAttributes' => [], 'MessageId' => 0, 'ReceiptHandle' => 'a'],
491 | ['Body' => 'bar', 'Attributes' => [], 'MessageAttributes' => [], 'MessageId' => 1, 'ReceiptHandle' => 'b'],
492 | ['Body' => 'baz', 'Attributes' => [], 'MessageAttributes' => [], 'MessageId' => 2, 'ReceiptHandle' => 'c'],
493 | ]);
494 |
495 | $this->client->shouldReceive('receiveMessage')->once()->with([
496 | 'QueueUrl' => $url,
497 | 'AttributeNames' => ['All'],
498 | 'MaxNumberOfMessages' => 3,
499 | 'VisibilityTimeout' => $timeout,
500 | 'WaitTimeSeconds' => 20,
501 | ])->andReturn($this->model);
502 |
503 | $iterator = $adapter->dequeue($this->factory, 3);
504 |
505 | assertThat($iterator, is(anInstanceOf('Generator')));
506 | assertThat(iterator_to_array($iterator), is(equalTo($this->messages)));
507 | }
508 |
509 | public function testPurge()
510 | {
511 | $adapter = new SqsAdapter($this->client, 'foo');
512 | $url = $this->stubCreateQueue('foo');
513 |
514 | $this->client->shouldReceive('purgeQueue')->once()->with([
515 | 'QueueUrl' => $url,
516 | ])->andReturn($this->model);
517 |
518 | assertThat($adapter->purge(), is(nullValue()));
519 | }
520 |
521 | public function testDelete()
522 | {
523 | $adapter = new SqsAdapter($this->client, 'foo');
524 | $url = $this->stubCreateQueue('foo');
525 |
526 | $this->client->shouldReceive('deleteQueue')->once()->with([
527 | 'QueueUrl' => $url,
528 | ])->andReturn($this->model);
529 |
530 | assertThat($adapter->delete(), is(nullValue()));
531 | }
532 | }
533 |
--------------------------------------------------------------------------------