├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── composer.json ├── composer.lock ├── phpcs.xml ├── phpunit.xml.dist ├── src ├── AbstractWorker.php ├── Adapter │ ├── AdapterInterface.php │ ├── ArrayAdapter.php │ ├── Exception │ │ ├── AdapterException.php │ │ ├── FailedAcknowledgementException.php │ │ ├── FailedEnqueueException.php │ │ ├── FailedExtensionException.php │ │ ├── FailedRejectionException.php │ │ └── MethodNotSupportedException.php │ ├── FirehoseAdapter.php │ ├── NamedInterface.php │ └── SqsAdapter.php ├── Client.php ├── ConsumerInterface.php ├── DeleterInterface.php ├── Handler │ ├── AbstractAcknowledgementHandler.php │ ├── BatchAcknowledgementHandler.php │ ├── EagerAcknowledgementHandler.php │ ├── NullAcknowledgementHandler.php │ └── ResultAcknowledgementHandler.php ├── Message │ ├── Message.php │ ├── MessageFactory.php │ ├── MessageFactoryInterface.php │ └── MessageInterface.php ├── ProducerInterface.php └── PurgerInterface.php └── tests ├── integration ├── ArrayIntegrationTest.php ├── FirehoseIntegrationTest.php └── SqsIntegrationTest.php ├── src └── TestCase.php └── unit ├── Adapter ├── ArrayAdapterTest.php ├── Exception │ ├── AdapterExceptionTest.php │ ├── FailedAcknowledgementExceptionTest.php │ ├── FailedEnqueueExceptionTest.php │ ├── FailedExtensionExceptionTest.php │ ├── FailedRejectionExceptionTest.php │ └── MethodNotSupportedExceptionTest.php ├── FirehoseAdapterTest.php └── SqsAdapterTest.php ├── ClientTest.php ├── Handler ├── BatchAcknowledgementHandlerTest.php ├── EagerAcknowledgementHandlerTest.php ├── NullAcknowledgementHandlerTest.php └── ResultAcknowledgementHandlerTest.php └── Message ├── MessageFactoryTest.php └── MessageTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | 3 | .idea 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | The graze PHP coding standard as defined in graze/standards. 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | --------------------------------------------------------------------------------