├── .coveralls.yml
├── .gitignore
├── .travis.yml
├── Command
├── QueueAttrCommand.php
├── QueueCreateCommand.php
├── QueueDeleteCommand.php
├── QueueListCommand.php
├── QueuePingCommand.php
├── QueuePurgeCommand.php
├── QueueUpdateCommand.php
└── QueueWorkerCommand.php
├── DependencyInjection
├── Compiler
│ └── SQSQueuePass.php
├── Configuration.php
└── TriTranSqsQueueExtension.php
├── README.md
├── Resources
└── config
│ └── services.xml
├── Service
├── BaseQueue.php
├── BaseWorker.php
├── Message.php
├── MessageCollection.php
├── QueueFactory.php
├── QueueManager.php
└── Worker
│ └── AbstractWorker.php
├── Tests
├── Functional
│ └── Command
│ │ ├── QueueAttrCommandTest.php
│ │ ├── QueueCreateCommandTest.php
│ │ ├── QueueDeleteCommandTest.php
│ │ ├── QueueListCommandTest.php
│ │ ├── QueuePingCommandTest.php
│ │ ├── QueuePurgeCommandTest.php
│ │ ├── QueueUpdateCommandTest.php
│ │ └── QueueWorkerCommandTest.php
├── Unit
│ ├── DependencyInjection
│ │ ├── Compiler
│ │ │ └── SQSQueuePassTest.php
│ │ ├── ConfigurationTest.php
│ │ └── TriTranSqsQueueExtensionTest.php
│ └── Service
│ │ ├── BaseQueueTest.php
│ │ ├── MessageTest.php
│ │ ├── QueueFactoryTest.php
│ │ ├── QueueManagerTest.php
│ │ └── Worker
│ │ └── AbstractWorkerTest.php
└── app
│ ├── AppKernel.php
│ ├── KernelTestCase.php
│ ├── Worker
│ └── BasicWorker.php
│ └── config
│ └── config_test.yml
├── TriTranSqsQueueBundle.php
├── composer.json
└── phpunit.xml.dist
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | coverage_clover: coverage.xml
2 | service_name: travis-ci
3 | json_path: ./
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /Tests/app/cache/
3 | /Tests/app/logs/
4 | /Tests/app/build/
5 | /composer.lock
6 | /phpunit.xml
7 | /var/
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: php
3 | php:
4 | - 7.0
5 | - 7.1
6 | - 7.2
7 | before_script:
8 | - composer install --no-interaction --prefer-dist
9 | script:
10 | - vendor/bin/phpunit --coverage-clover=coverage.xml
11 | - vendor/bin/phpcs -p --standard=PSR2 --ignore=var/,vendor/,Tests/app/,Resources/public/ ./
12 | after_script:
13 | - travis_retry bash <(curl -s https://codecov.io/bash)
14 |
--------------------------------------------------------------------------------
/Command/QueueAttrCommand.php:
--------------------------------------------------------------------------------
1 | setName('tritran:sqs_queue:attr')
28 | ->addArgument(
29 | 'url',
30 | InputArgument::REQUIRED,
31 | 'Queue Url which you want to retrieve its attributes'
32 | )
33 | ->setDescription('Retrieve the attribute of a specified queue');
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | protected function execute(InputInterface $input, OutputInterface $output)
40 | {
41 | $queueUrl = $input->getArgument('url');
42 |
43 | $io = new SymfonyStyle($input, $output);
44 | $io->title(sprintf('Start getting the attributes of queue URL %s', $queueUrl));
45 |
46 | /** @var QueueManager $queueManager */
47 | $queueManager = $this->container->get('tritran.sqs_queue.queue_manager');
48 | $result = $queueManager->getQueueAttributes($queueUrl);
49 | $io->table(['Attribute Name', 'Value'], array_map(function ($k, $v) {
50 | return [$k, $v];
51 | }, array_keys($result), $result));
52 |
53 | $io->text('Updated successfully');
54 | $io->success('Done');
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Command/QueueCreateCommand.php:
--------------------------------------------------------------------------------
1 | setName('tritran:sqs_queue:create')
29 | ->addArgument(
30 | 'name',
31 | InputArgument::REQUIRED,
32 | 'Queue ID which you want to send message'
33 | )
34 | ->addOption(
35 | 'delay_seconds',
36 | null,
37 | InputOption::VALUE_REQUIRED,
38 | 'DelaySeconds',
39 | 0
40 | )
41 | ->addOption(
42 | 'maximum_message_size',
43 | null,
44 | InputOption::VALUE_REQUIRED,
45 | 'MaximumMessageSize',
46 | 262144
47 | )
48 | ->addOption(
49 | 'message_retention_period',
50 | null,
51 | InputOption::VALUE_REQUIRED,
52 | 'MessageRetentionPeriod',
53 | 345600
54 | )
55 | ->addOption(
56 | 'receive_message_wait_time_seconds',
57 | null,
58 | InputOption::VALUE_REQUIRED,
59 | 'ReceiveMessageWaitTimeSeconds',
60 | 0
61 | )
62 | ->addOption(
63 | 'visibility_timeout',
64 | null,
65 | InputOption::VALUE_REQUIRED,
66 | 'VisibilityTimeout',
67 | 30
68 | )
69 | ->addOption(
70 | 'content_based_deduplication',
71 | null,
72 | InputOption::VALUE_NONE,
73 | 'ContentBasedDeduplication'
74 | )
75 | ->setDescription('Create a queue by name and basic attributions');
76 | }
77 |
78 | /**
79 | * {@inheritdoc}
80 | */
81 | protected function execute(InputInterface $input, OutputInterface $output)
82 | {
83 | $queueName = $input->getArgument('name');
84 | if ($this->container->has(sprintf('tritran.sqs_queue.%s', $queueName))) {
85 | throw new \InvalidArgumentException(sprintf('Queue [%s] exists. Please use another name.', $queueName));
86 | }
87 |
88 | $io = new SymfonyStyle($input, $output);
89 | $io->title(sprintf('Start creating a new queue which name is %s', $queueName));
90 |
91 | /** @var QueueManager $queueManager */
92 | $queueManager = $this->container->get('tritran.sqs_queue.queue_manager');
93 | $queueUrl = $queueManager->createQueue($queueName, [
94 | 'DelaySeconds' => $input->getOption('delay_seconds'),
95 | 'MaximumMessageSize' => $input->getOption('maximum_message_size'),
96 | 'MessageRetentionPeriod' => $input->getOption('message_retention_period'),
97 | 'ReceiveMessageWaitTimeSeconds' => $input->getOption('receive_message_wait_time_seconds'),
98 | 'VisibilityTimeout' => $input->getOption('visibility_timeout'),
99 | 'ContentBasedDeduplication' => $input->getOption('content_based_deduplication'),
100 | ]);
101 |
102 | $io->text(sprintf('Created successfully. New Queue URL: %s', $queueUrl));
103 | $io->success('Done');
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Command/QueueDeleteCommand.php:
--------------------------------------------------------------------------------
1 | setName('tritran:sqs_queue:delete')
29 | ->addArgument(
30 | 'url',
31 | InputArgument::REQUIRED,
32 | 'Queue Url which you want to remove'
33 | )
34 | ->addOption(
35 | 'force',
36 | 'f',
37 | InputOption::VALUE_NONE,
38 | 'Set this parameter to execute this command'
39 | )
40 | ->setDescription('Delete a queue by url and all its messages');
41 | }
42 |
43 | /**
44 | * {@inheritdoc}
45 | */
46 | protected function execute(InputInterface $input, OutputInterface $output)
47 | {
48 | $io = new SymfonyStyle($input, $output);
49 | if (!$input->getOption('force')) {
50 | $io->note('Option --force is mandatory to drop data.');
51 | $io->warning('This action should not be used in the production environment.');
52 |
53 | return;
54 | }
55 |
56 | $queueUrl = $input->getArgument('url');
57 |
58 | $io->title(sprintf('Start deleting the specified queue by URL %s', $queueUrl));
59 |
60 | /** @var QueueManager $queueManager */
61 | $queueManager = $this->container->get('tritran.sqs_queue.queue_manager');
62 | $queueManager->deleteQueue($queueUrl);
63 |
64 | $io->text('Deleted successfully');
65 | $io->success('Done');
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Command/QueueListCommand.php:
--------------------------------------------------------------------------------
1 | setName('tritran:sqs_queue:list')
28 | ->addOption(
29 | 'prefix',
30 | null,
31 | InputOption::VALUE_REQUIRED,
32 | 'Queues with a name that begins with the specified value are returned.',
33 | ''
34 | )
35 | ->setDescription('Returns a list of your queues.');
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | */
41 | protected function execute(InputInterface $input, OutputInterface $output)
42 | {
43 | $io = new SymfonyStyle($input, $output);
44 | $io->title('Start getting the list of existing queues in SQS');
45 |
46 | /** @var QueueManager $queueManager */
47 | $queueManager = $this->container->get('tritran.sqs_queue.queue_manager');
48 | $result = $queueManager->listQueue($input->getOption('prefix'));
49 |
50 | if (empty($result)) {
51 | $io->text('You don\'t have any queue at this moment. Please go to AWS Console to create a new one.');
52 | } else {
53 | $io->table(['Queue URL'], array_map(function ($value) {
54 | return [$value];
55 | }, $result));
56 | }
57 |
58 | $io->success('Done');
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Command/QueuePingCommand.php:
--------------------------------------------------------------------------------
1 | setName('tritran:sqs_queue:ping')
28 | ->addArgument(
29 | 'name',
30 | InputArgument::REQUIRED,
31 | 'Queue ID which you want to send message'
32 | )
33 | ->setDescription('Send a simply message to a queue, for DEBUG only');
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | protected function execute(InputInterface $input, OutputInterface $output)
40 | {
41 | $queueName = $input->getArgument('name');
42 | if (!$this->container->has(sprintf('tritran.sqs_queue.%s', $queueName))) {
43 | throw new \InvalidArgumentException(sprintf('Queue [%s] does not exist.', $queueName));
44 | }
45 |
46 | $io = new SymfonyStyle($input, $output);
47 | $io->title(sprintf('Start sending a Hello message to SQS %s', $queueName));
48 |
49 | /** @var BaseQueue $queue */
50 | $queue = $this->container->get(sprintf('tritran.sqs_queue.%s', $queueName));
51 | $messageId = $queue->ping();
52 |
53 | $io->text(sprintf('Sent successfully. MessageID: %s', $messageId));
54 | $io->success('Done');
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Command/QueuePurgeCommand.php:
--------------------------------------------------------------------------------
1 | setName('tritran:sqs_queue:purge')
29 | ->addArgument(
30 | 'name',
31 | InputArgument::REQUIRED,
32 | 'Queue ID which you want to send message'
33 | )
34 | ->addOption(
35 | 'force',
36 | 'f',
37 | InputOption::VALUE_NONE,
38 | 'Set this parameter to execute this command'
39 | )
40 | ->setDescription('Deletes the messages in a queue specified by the QueueURL parameter.');
41 | }
42 |
43 | /**
44 | * {@inheritdoc}
45 | */
46 | protected function execute(InputInterface $input, OutputInterface $output)
47 | {
48 | $io = new SymfonyStyle($input, $output);
49 |
50 | if (!$input->getOption('force')) {
51 | $io->note('Option --force is mandatory to drop data.');
52 | $io->warning('This action should not be used in the production environment.');
53 |
54 | return;
55 | }
56 |
57 | $queueName = $input->getArgument('name');
58 | if (!$this->container->has(sprintf('tritran.sqs_queue.%s', $queueName))) {
59 | throw new \InvalidArgumentException(sprintf('Queue [%s] does not exist.', $queueName));
60 | }
61 |
62 | $io->title(sprintf('Start purge all your message in SQS %s', $queueName));
63 |
64 | /** @var BaseQueue $queue */
65 | $queue = $this->container->get(sprintf('tritran.sqs_queue.%s', $queueName));
66 | $queue->purge();
67 |
68 | $io->text('All message in your specified queue were removed successfully');
69 | $io->success('Done');
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Command/QueueUpdateCommand.php:
--------------------------------------------------------------------------------
1 | setName('tritran:sqs_queue:update')
29 | ->addOption(
30 | 'force',
31 | 'f',
32 | InputOption::VALUE_NONE,
33 | 'Set this parameter to execute this command'
34 | )
35 | ->setDescription('Update Queue attribute based on configuration');
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | */
41 | protected function execute(InputInterface $input, OutputInterface $output)
42 | {
43 | $io = new SymfonyStyle($input, $output);
44 |
45 | if (!$input->getOption('force')) {
46 | $io->note('Option --force is mandatory to update data.');
47 | $io->warning('This action should not be used in the production environment.');
48 |
49 | return;
50 | }
51 |
52 | if (!$this->container->hasParameter('tritran.sqs_queue.queues')) {
53 | $io->warning('Queue Configuration is missing.');
54 |
55 | return;
56 | }
57 |
58 | /** @var QueueManager $queueManager */
59 | $queueManager = $this->container->get('tritran.sqs_queue.queue_manager');
60 | $awsQueues = $queueManager->listQueue();
61 |
62 | /** @var array $localQueues */
63 | $localQueues = $this->container->getParameter('tritran.sqs_queue.queues');
64 | foreach ($localQueues as $queueName => $queueOption) {
65 | if (in_array($queueOption['queue_url'], $awsQueues, true)) {
66 | $io->text(sprintf('We will update %s', $queueOption['queue_url']));
67 |
68 | /** @var BaseQueue $queue */
69 | $queue = $this->container->get(sprintf('tritran.sqs_queue.%s', $queueName));
70 | $queueManager->setQueueAttributes($queue->getQueueUrl(), $queue->getAttributes());
71 |
72 | $io->table(['Attribute Name', 'Value'], array_map(function ($k, $v) {
73 | return [$k, $v];
74 | }, array_keys($queue->getAttributes()), $queue->getAttributes()));
75 | }
76 | }
77 |
78 | $io->success('Done');
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Command/QueueWorkerCommand.php:
--------------------------------------------------------------------------------
1 | setName('tritran:sqs_queue:worker')
30 | ->addArgument('name', InputArgument::REQUIRED, 'Queue Name', null)
31 | ->addOption('messages', 'm', InputOption::VALUE_OPTIONAL, 'Messages to consume', 0)
32 | ->addOption('limit', 'l', InputOption::VALUE_OPTIONAL, 'Max messages to consume per request', 1)
33 | ->setDescription('Start a worker that will listen to a specified SQS queue');
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | protected function execute(InputInterface $input, OutputInterface $output)
40 | {
41 | $queueName = $input->getArgument('name');
42 | if (!$this->container->has(sprintf('tritran.sqs_queue.%s', $queueName))) {
43 | throw new \InvalidArgumentException(sprintf('Queue [%s] does not exist.', $queueName));
44 | }
45 | $amount = $input->getOption('messages');
46 | if ($amount < 0) {
47 | throw new \InvalidArgumentException('The -m option should be null or greater than 0');
48 | }
49 |
50 | $limit = $input->getOption('limit');
51 | if ($limit < 1) {
52 | throw new \InvalidArgumentException('The -l option should be null or greater than 1');
53 | }
54 |
55 | $io = new SymfonyStyle($input, $output);
56 | $io->title(sprintf('Start listening to queue %s', $queueName));
57 |
58 | /** @var BaseQueue $queue */
59 | $queue = $this->container->get(sprintf('tritran.sqs_queue.%s', $queueName));
60 |
61 | /** @var BaseWorker $worker */
62 | $worker = $this->container->get('tritran.sqs_queue.queue_worker');
63 | $worker->start($queue, $amount, $limit);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/DependencyInjection/Compiler/SQSQueuePass.php:
--------------------------------------------------------------------------------
1 | hasDefinition('aws.sqs')) {
26 | throw new \InvalidArgumentException(
27 | 'AWS SQSClient is required to use queue.'
28 | );
29 | }
30 |
31 | if ($container->hasParameter('tritran.sqs_queue.queues')) {
32 | /** @var array $queues */
33 | $queues = $container->getParameter('tritran.sqs_queue.queues');
34 | foreach ($queues as $queueName => $queueOption) {
35 | $defaultQueueServices = ['queues', 'queue_factory', 'queue_manager', 'queue_worker'];
36 | if (in_array($queueName, $defaultQueueServices, true)) {
37 | throw new \InvalidArgumentException(sprintf(
38 | 'Invalid queue-name [%s]. Predefined queue-name: (%s)',
39 | $queueName,
40 | implode(', ', $defaultQueueServices)
41 | ));
42 | }
43 |
44 | $queueOption['worker'] = preg_replace('/^@/', '', $queueOption['worker']);
45 | if ($container->has($queueOption['worker'])) {
46 | $callable = new Reference($queueOption['worker']);
47 | } elseif (class_exists($queueOption['worker'])) {
48 | $callable = new Definition($queueOption['worker']);
49 | } else {
50 | throw new \InvalidArgumentException(
51 | sprintf('Invalid worker of queue [%s]', $queueName)
52 | );
53 | }
54 |
55 | $queueAttr = [
56 | 'DelaySeconds' =>
57 | $queueOption['attributes']['delay_seconds'] ?? 0,
58 | 'MaximumMessageSize' =>
59 | $queueOption['attributes']['maximum_message_size'] ?? 262144,
60 | 'MessageRetentionPeriod' =>
61 | $queueOption['attributes']['message_retention_period'] ?? 345600,
62 | 'ReceiveMessageWaitTimeSeconds' =>
63 | $queueOption['attributes']['receive_message_wait_time_seconds'] ?? 20,
64 | 'VisibilityTimeout' =>
65 | $queueOption['attributes']['visibility_timeout'] ?? 30,
66 | 'RedrivePolicy' => !empty($queueOption['attributes']['redrive_policy']['dead_letter_queue'])
67 | ? json_encode([
68 | 'deadLetterTargetArn' =>
69 | $queueOption['attributes']['redrive_policy']['dead_letter_queue'] ?? '',
70 | 'maxReceiveCount' =>
71 | $queueOption['attributes']['redrive_policy']['max_receive_count'] ?? 5,
72 | ]) : ''
73 | ];
74 | if (QueueManager::isFifoQueue($queueName)) {
75 | $queueAttr = array_merge($queueAttr, [
76 | 'ContentBasedDeduplication' =>
77 | $queueOption['attributes']['content_based_deduplication'] ?? true
78 | ]);
79 | }
80 |
81 | $queueDefinition = new Definition(BaseQueue::class);
82 | $queueDefinition
83 | ->setFactory(
84 | [
85 | new Reference('tritran.sqs_queue.queue_factory'),
86 | 'create'
87 | ]
88 | )
89 | ->setPublic(true)
90 | ->setArguments([
91 | $queueName,
92 | $queueOption['queue_url'],
93 | $callable,
94 | $queueAttr
95 | ]);
96 |
97 | $queueId = sprintf('tritran.sqs_queue.%s', $queueName);
98 | $container->setDefinition($queueId, $queueDefinition);
99 | }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 | root('tritran_sqs_queue');
30 | } else {
31 | $rootNode = $treeBuilder->getRootNode();
32 | }
33 |
34 | $rootNode
35 | ->children()
36 | ->append($this->getSQSQueueNodeDef())
37 | ->end();
38 |
39 | return $treeBuilder;
40 | }
41 |
42 | /**
43 | * @return ArrayNodeDefinition
44 | */
45 | private function queuesNodeDef()
46 | {
47 | $node = new ArrayNodeDefinition('queues');
48 |
49 | return $node
50 | ->useAttributeAsKey('name')
51 | ->prototype('array')
52 | ->children()
53 | ->append((new ScalarNodeDefinition('queue_url'))->isRequired())
54 | ->append((new ScalarNodeDefinition('worker'))->isRequired())
55 | ->append($this->getSQSQueueAttributesNodeDef())
56 | ->end()
57 | ->end();
58 | }
59 |
60 | /**
61 | * @return NodeDefinition|ParentNodeDefinitionInterface
62 | */
63 | protected function getSQSQueueAttributesNodeDef()
64 | {
65 | $node = new ArrayNodeDefinition('attributes');
66 |
67 | return $node
68 | ->children()
69 | ->append((new IntegerNodeDefinition('delay_seconds'))->defaultValue(0)->min(0)->max(900))// zero second
70 | ->append((new IntegerNodeDefinition('maximum_message_size'))->defaultValue(262144)->min(1024)->max(262144))// 256 KiB
71 | ->append((new IntegerNodeDefinition('message_retention_period'))->defaultValue(345600)->min(60)->max(1209600))// 4 days
72 | ->append((new IntegerNodeDefinition('receive_message_wait_time_seconds'))->defaultValue(20)->min(0)->max(20))// seconds
73 | ->append((new IntegerNodeDefinition('visibility_timeout'))->defaultValue(30)->min(0)->max(43200))// second
74 | ->append((new BooleanNodeDefinition('content_based_deduplication'))->defaultValue(false))// second
75 | ->append($this->getSQSRedrivePolicyNode())
76 | ->end();
77 | }
78 |
79 | /**
80 | * @return NodeDefinition|ParentNodeDefinitionInterface
81 | */
82 | protected function getSQSRedrivePolicyNode()
83 | {
84 | $node = new ArrayNodeDefinition('redrive_policy');
85 |
86 | return $node
87 | ->canBeUnset()
88 | ->children()
89 | ->append((new IntegerNodeDefinition('max_receive_count'))->defaultValue(5)->min(1)->max(1000))
90 | ->append((new ScalarNodeDefinition('dead_letter_queue'))->isRequired())
91 | ->end();
92 | }
93 |
94 | /**
95 | * @return NodeDefinition|ParentNodeDefinitionInterface
96 | */
97 | protected function getSQSQueueNodeDef()
98 | {
99 | $node = new ArrayNodeDefinition('sqs_queue');
100 |
101 | return $node
102 | ->canBeUnset()
103 | ->children()
104 | ->append($this->queuesNodeDef())
105 | ->end();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/DependencyInjection/TriTranSqsQueueExtension.php:
--------------------------------------------------------------------------------
1 | processConfiguration($configuration, $configs);
24 | $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
25 | $loader->load('services.xml');
26 |
27 | $this->configSQSQueue($container, $config);
28 | }
29 |
30 | /**
31 | * @param ContainerBuilder $container
32 | * @param array $config
33 | */
34 | private function configSQSQueue(ContainerBuilder $container, array $config)
35 | {
36 | $engineConfig = $config['sqs_queue'] ?? [];
37 |
38 | if (isset($engineConfig['queues']) && !empty($engineConfig['queues'])) {
39 | $container->setParameter('tritran.sqs_queue.queues', $config['sqs_queue']['queues']);
40 | }
41 | }
42 |
43 | /**
44 | * @inheritdoc
45 | */
46 | public function getAlias()
47 | {
48 | return 'tritran_sqs_queue';
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Simple AWS SQS Queue for Symfony
2 | ================================
3 |
4 | This bundle provides an easy way to work with AWS SQS
5 |
6 | [](https://insight.sensiolabs.com/projects/966e2515-0177-4b80-8ef0-cfc9bd800a81)
7 | [](https://packagist.org/packages/tritran/sqs-queue-bundle)
8 | [](https://packagist.org/packages/tritran/sqs-queue-bundle)
9 | [](https://travis-ci.org/trandangtri/sqs-queue-bundle)
10 | [](https://codecov.io/gh/trandangtri/sqs-queue-bundle)
11 |
12 | Installation
13 | ---
14 |
15 | Follow 5 quick steps to setup this bundle.
16 |
17 | ### Step 1: Download the Bundle
18 |
19 | Open a command console, enter your project directory and execute the following
20 | command to download the latest stable version of this bundle:
21 |
22 | ```bash
23 | $ composer require tritran/sqs-queue-bundle
24 | ```
25 |
26 | > This command requires you to have Composer installed globally
27 |
28 | ### Step 2: Enable the Bundle
29 |
30 | Register bundles in `app/AppKernel.php`:
31 |
32 | ```php
33 | class AppKernel extends Kernel
34 | {
35 | public function registerBundles()
36 | {
37 | return [
38 | // ...
39 | new \Aws\Symfony\AwsBundle(),
40 | new \TriTran\SqsQueueBundle\TriTranSqsQueueBundle(),
41 | ];
42 | }
43 |
44 | // ...
45 | }
46 | ```
47 |
48 | > In a default Symfony application that uses [Symfony Flex](https://symfony.com/doc/current/setup/flex.html), bundles are enabled/disabled automatically for you when installing/removing them, so you could ignore this step.
49 |
50 | ### Step 3: Update AWS SQS Credential
51 |
52 | This bundle is using [AWS SDK for PHP](https://github.com/aws/aws-sdk-php-symfony). Full documentation of the configuration options available can be read in the [SDK Guide](http://docs.aws.amazon.com/aws-sdk-php/v3/guide/guide/configuration.html).
53 |
54 | Below are sample configuration for AWS Credential in YAML format
55 |
56 | ```yml
57 | # app/config/config.yml
58 |
59 | aws:
60 | version: latest
61 | region: us-central-1
62 | credentials:
63 | key: not-a-real-key
64 | secret: "@not-a-real-secret"
65 | ```
66 |
67 | ### Step 4: Configure the Queues
68 |
69 | Below are sample configuration for some queues in YAML format
70 |
71 | ```yml
72 | # app/config/config.yml
73 |
74 | tritran_sqs_queue:
75 | sqs_queue:
76 | queues:
77 | emailpool:
78 | queue_url: 'https://sqs.eu-central-1.amazonaws.com/49504XX59872/emailpool'
79 | worker: "@acl.service.emailpool"
80 | attributes:
81 | receive_message_wait_time_seconds: 20
82 | visibility_timeout: 30
83 | reminder:
84 | queue_url: 'https://sqs.eu-central-1.amazonaws.com/49504XX59872/reminder'
85 | worker: 'AclBundle\Service\Worker\ReminderWorker'
86 | ```
87 |
88 | Full documentation of the queue options available can be read in the [Queue Attributes](http://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SetQueueAttributes.html).
89 |
90 | > Now, you could access to queue `emailpool` or `reminder` service via `tritran.sqs_queue.emailpool` or `tritran.sqs_queue.reminder`, it's an interface of [BaseQueue](https://github.com/trandangtri/sqs-queue-bundle/blob/master/Service/BaseQueue.php)
91 |
92 | Below are a sample implementation of sending a message to a specified queue
93 |
94 | ```php
95 | namespace AclBundle\Controller;
96 |
97 | use Symfony\Bundle\FrameworkBundle\Controller\Controller;
98 | use TriTran\SqsQueueBundle\Service\Message;
99 |
100 | /**
101 | * Class DefaultController
102 | *
103 | * @package AclBundle\Controller
104 | */
105 | class DefaultController extends Controller
106 | {
107 | public function indexAction()
108 | {
109 | // ...
110 |
111 | $data = [
112 | 'from' => 'sender@domain.com',
113 | 'to' => 'receiver@domain.com',
114 | 'subject' => 'Greeting Message',
115 | 'body' => 'Congratulation! You have just received a message which was sent from AWS SQS Queue'
116 | ];
117 | $this->get('tritran.sqs_queue.emailpool')
118 | ->sendMessage((new Message())->setBody(serialize($data)));
119 |
120 | // ...
121 | }
122 | }
123 | ```
124 |
125 | > For a FIFO queue, you must associate a non-empty `MessageGroupId` with a message. Otherwise, the action fails.
126 | > You may provide a `MessageDeduplicationId` explicitly. If you aren't able to provide a `MessageDeduplicationId` and you enable `ContentBasedDeduplication` for your queue, Amazon SQS uses a SHA-256 hash to generate the `MessageDeduplicationId` using the body of the message (but not the attributes of the message).
127 | > For more information about FIFO queue, please take a look at [Amazon SQS FIFO (First-In-First-Out) Queues](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html)
128 |
129 | #### Queue Behaviours
130 |
131 | |Behaviour|Arguments|Description|
132 | |---|---|---|
133 | |sendMessage|`Message` $message
`int` $delay = 0|Delivers a message to the specified queue.|
134 | |receiveMessage|`int` $limit = 1|Retrieves one or more messages (up to 10), from the specified queue. Using the WaitTimeSeconds parameter enables long-poll support. For more information, see [Amazon SQS Long Polling](http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-long-polling.html) in the Amazon SQS Developer Guide.|
135 | |deleteMessage|`string` $receiptHandle|Deletes the specified message from the specified queue. You specify the message by using the *message's receipt handle* and not the *MessageId* you receive when you send the message. Even if the message is locked by another reader due to the visibility timeout setting, it is still deleted from the queue. If you leave a message in the queue for longer than the queue's configured retention period, Amazon SQS automatically deletes the message.|
136 | |purge| |Deletes the messages in a queue specified by the QueueURL parameter. **Note**: you can't retrieve a message deleted from a queue.|
137 |
138 | #### Queue Manager Behaviours
139 |
140 | > You could access [QueueManager](https://github.com/trandangtri/sqs-queue-bundle/blob/master/Service/QueueManager.php) via service `tritran.sqs_queue.queue_manager`
141 |
142 | |Behaviour|Arguments|Description|
143 | |---|---|---|
144 | |listQueue|`string` $prefix = ''|Returns a list of your queues. The maximum number of queues that can be returned is 1,000. If you specify a value for the optional *prefix* parameter, only queues with a name that begins with the specified value are returned.|
145 | |createQueue|`string` $queueName
`array` $queueAttribute|Creates a new standard or FIFO queue. You can pass one or more attributes in the request.|
146 | |deleteQueue|`string` $queueUrl|Deletes the queue specified by the **QueueUrl**, regardless of the queue's contents. If the specified queue doesn't exist, Amazon SQS returns a successful response.|
147 | |setQueueAttributes|`string` $queueUrl
`array` $queueAttribute|Sets the value of one or more queue attributes. When you change a queue's attributes, the change can take up to 60 seconds for most of the attributes to propagate throughout the Amazon SQS system|
148 | |getQueueAttributes|`string` $queueUrl|Gets attributes for the specified queue.|
149 |
150 | ### Step 5: Setup a worker
151 |
152 | Below are a sample implementation of a worker, which will listen to a queue to handle the messages inside.
153 |
154 | ```php
155 | namespace AclBundle\Service\Worker;
156 |
157 | use TriTran\SqsQueueBundle\Service\Message;
158 | use TriTran\SqsQueueBundle\Service\Worker\AbstractWorker;
159 |
160 | class ReminderWorker extends AbstractWorker
161 | {
162 | /**
163 | * @param Message $message
164 | *
165 | * @return boolean
166 | */
167 | protected function execute(Message $message)
168 | {
169 | echo 'The message is: ' . $message->getBody();
170 |
171 | return true;
172 | }
173 | }
174 | ```
175 |
176 | And then you could make it executed as daemon in console via:
177 |
178 | ```bash
179 | bin/console tritran:sqs_queue:worker reminder
180 | ```
181 |
182 | > Note: **reminder** is the name of queue which you configured in the config.yml in step 4.
183 |
184 | ### Appendix: Useful Console Commands
185 |
186 | |Behaviour|Description|
187 | |---|---|
188 | |tritran:sqs_queue:create|Creates a new standard or FIFO queue. You can pass one or more attributes in the request.|
189 | |tritran:sqs_queue:update|Update queue attribute based on its configuration which shown in config.yml|
190 | |tritran:sqs_queue:delete|Delete a queue by url and all its messages||
191 | |tritran:sqs_queue:attr|Retrieve the attribute of a specified queue|
192 | |tritran:sqs_queue:purge|Deletes the messages in a queue specified by the QueueURL parameter.|
193 | |tritran:sqs_queue:worker|Start a worker that will listen to a specified SQS queue|
194 | |tritran:sqs_queue:ping|Send a simply message to a queue, for DEBUG only|
195 |
196 | > Note: Please using `-h` for more information for each command.
197 |
--------------------------------------------------------------------------------
/Resources/config/services.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/Service/BaseQueue.php:
--------------------------------------------------------------------------------
1 | client = $client;
57 | $this->queueUrl = $queueUrl;
58 | $this->queueName = $queueName;
59 | $this->queueWorker = $queueWorker;
60 | $this->attributes = $attributes;
61 | }
62 |
63 | /**
64 | * @return string
65 | */
66 | public function ping()
67 | {
68 | $message = (new Message())->setBody('ping');
69 |
70 | return $this->sendMessage($message);
71 | }
72 |
73 | /**
74 | * @param Message $message
75 | * @param int $delay
76 | *
77 | * @return string
78 | */
79 | public function sendMessage(Message $message, int $delay = 0)
80 | {
81 | $params = [
82 | 'QueueUrl' => $this->queueUrl,
83 | 'MessageBody' => $message->getBody(),
84 | 'MessageAttributes' => $message->getAttributes()
85 | ];
86 |
87 | if ($this->isFIFO()) {
88 | if ($delay) {
89 | trigger_error('FIFO queues don\'t support per-message delays, only per-queue delays.', E_USER_WARNING);
90 | $delay = 0;
91 | }
92 |
93 | if (empty($message->getGroupId())) {
94 | throw new \InvalidArgumentException('MessageGroupId is required for FIFO queues.');
95 | }
96 | $params['MessageGroupId'] = $message->getGroupId();
97 |
98 | if (!empty($message->getDeduplicationId())) {
99 | $params['MessageDeduplicationId'] = $message->getDeduplicationId();
100 | }
101 | }
102 |
103 | if ($delay) {
104 | $params['DelaySeconds'] = $delay;
105 | }
106 |
107 | try {
108 | $result = $this->client->sendMessage($params);
109 | $messageId = $result->get('MessageId');
110 | } catch (AwsException $e) {
111 | throw new \InvalidArgumentException($e->getAwsErrorMessage());
112 | }
113 |
114 | return $messageId;
115 | }
116 |
117 | /**
118 | * Retrieves one or more messages (up to 10), from the specified queue.
119 | *
120 | * @param int $limit
121 | *
122 | * @return MessageCollection|Message[]
123 | */
124 | public function receiveMessage(int $limit = 1)
125 | {
126 | $collection = new MessageCollection([]);
127 |
128 | try {
129 | $result = $this->client->receiveMessage([
130 | 'QueueUrl' => $this->queueUrl,
131 | 'AttributeNames' => ['All'],
132 | 'MessageAttributeNames' => ['All'],
133 | 'MaxNumberOfMessages' => $limit,
134 | 'VisibilityTimeout' => $this->attributes['VisibilityTimeout'] ?? 30,
135 | 'WaitTimeSeconds' => $this->attributes['ReceiveMessageWaitTimeSeconds'] ?? 0,
136 | ]);
137 |
138 | $messages = $result->get('Messages') ?? [];
139 | foreach ($messages as $message) {
140 | $collection->append(
141 | (new Message())
142 | ->setId($message['MessageId'])
143 | ->setBody($message['Body'])
144 | ->setReceiptHandle($message['ReceiptHandle'])
145 | ->setAttributes($message['Attributes'])
146 | );
147 | }
148 | } catch (AwsException $e) {
149 | throw new \InvalidArgumentException($e->getAwsErrorMessage());
150 | }
151 |
152 | return $collection;
153 | }
154 |
155 | /**
156 | * Deletes the specified message from the specified queue
157 | *
158 | * @param Message $message
159 | *
160 | * @return bool
161 | */
162 | public function deleteMessage(Message $message)
163 | {
164 | try {
165 | $this->client->deleteMessage([
166 | 'QueueUrl' => $this->queueUrl,
167 | 'ReceiptHandle' => $message->getReceiptHandle()
168 | ]);
169 |
170 | return true;
171 | } catch (AwsException $e) {
172 | throw new \InvalidArgumentException($e->getAwsErrorMessage());
173 | }
174 | }
175 |
176 | /**
177 | * Releases a message back to the queue, making it visible again
178 | *
179 | * @param Message $message
180 | *
181 | * @return bool
182 | */
183 | public function releaseMessage(Message $message)
184 | {
185 | try {
186 | $this->client->changeMessageVisibility([
187 | 'QueueUrl' => $this->queueUrl,
188 | 'ReceiptHandle' => $message->getReceiptHandle(),
189 | 'VisibilityTimeout' => 0
190 | ]);
191 |
192 | return true;
193 | } catch (AwsException $e) {
194 | throw new \InvalidArgumentException($e->getAwsErrorMessage());
195 | }
196 | }
197 |
198 | /**
199 | * Deletes the messages in a queue.
200 | * When you use the this action, you can't retrieve a message deleted from a queue.
201 | *
202 | * @return bool
203 | */
204 | public function purge()
205 | {
206 | try {
207 | $this->client->purgeQueue([
208 | 'QueueUrl' => $this->queueUrl
209 | ]);
210 |
211 | return true;
212 | } catch (AwsException $e) {
213 | throw new \InvalidArgumentException($e->getAwsErrorMessage());
214 | }
215 | }
216 |
217 | /**
218 | * @return string
219 | */
220 | public function getQueueUrl(): string
221 | {
222 | return $this->queueUrl;
223 | }
224 |
225 | /**
226 | * @param string $queueUrl
227 | *
228 | * @return $this
229 | */
230 | public function setQueueUrl(string $queueUrl)
231 | {
232 | $this->queueUrl = $queueUrl;
233 |
234 | return $this;
235 | }
236 |
237 | /**
238 | * @return AbstractWorker
239 | */
240 | public function getQueueWorker(): AbstractWorker
241 | {
242 | return $this->queueWorker;
243 | }
244 |
245 | /**
246 | * @return string
247 | */
248 | public function getQueueName(): string
249 | {
250 | return $this->queueName;
251 | }
252 |
253 | /**
254 | * @return array
255 | */
256 | public function getAttributes(): array
257 | {
258 | return $this->attributes;
259 | }
260 |
261 | /**
262 | * @param array $attributes
263 | *
264 | * @return $this
265 | */
266 | public function setAttributes(array $attributes)
267 | {
268 | $this->attributes = $attributes;
269 |
270 | return $this;
271 | }
272 |
273 | /**
274 | * @return SqsClient
275 | */
276 | public function getClient(): SqsClient
277 | {
278 | return $this->client;
279 | }
280 |
281 | /**
282 | * @return bool
283 | */
284 | final public function isFIFO(): bool
285 | {
286 | return QueueManager::isFifoQueue($this->getQueueName());
287 | }
288 | }
289 |
--------------------------------------------------------------------------------
/Service/BaseWorker.php:
--------------------------------------------------------------------------------
1 | consumed = 0;
28 | $this->consume($queue, $amount, $limit);
29 | }
30 |
31 | /**
32 | * @param BaseQueue $queue
33 | * @param int $amount
34 | * @param int $limit
35 | */
36 | private function consume(BaseQueue $queue, int $amount = 0, int $limit = 1)
37 | {
38 | while (true) {
39 | if ($amount && $this->consumed >= $amount) {
40 | break;
41 | }
42 | $this->fetchMessage($queue, $limit);
43 | }
44 | }
45 |
46 | /**
47 | * @param BaseQueue $queue
48 | * @param int $limit
49 | */
50 | private function fetchMessage(BaseQueue $queue, int $limit = 1)
51 | {
52 | $consumer = $queue->getQueueWorker();
53 |
54 | /** @var MessageCollection $result */
55 | $messages = $queue->receiveMessage($limit);
56 |
57 | $messages->rewind();
58 | while ($messages->valid()) {
59 | $this->consumed++;
60 |
61 | /** @var Message $message */
62 | $message = $messages->current();
63 |
64 | $this->logger && $this->logger->info(sprintf('Processing message ID: %s', $message->getId()));
65 | $result = $consumer->process($message);
66 |
67 | if ($result !== false) {
68 | $this->logger && $this->logger->info(
69 | sprintf('Successfully processed message ID: %s', $message->getId())
70 | );
71 | $queue->deleteMessage($message);
72 | } else {
73 | $this->logger && $this->logger->warning(
74 | sprintf('Cannot process message ID: %s, will release it back to queue', $message->getId())
75 | );
76 | $queue->releaseMessage($message);
77 | }
78 |
79 | $messages->next();
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Service/Message.php:
--------------------------------------------------------------------------------
1 | body = $body;
56 | $this->attributes = $attributes;
57 | $this->groupId = $groupId;
58 | $this->deduplicationId = $deduplicationId;
59 | }
60 |
61 | /**
62 | * @return string
63 | */
64 | public function getId(): string
65 | {
66 | return $this->id;
67 | }
68 |
69 | /**
70 | * @param string $id
71 | *
72 | * @return Message
73 | */
74 | public function setId(string $id)
75 | {
76 | $this->id = $id;
77 |
78 | return $this;
79 | }
80 |
81 | /**
82 | * @return string
83 | */
84 | public function getBody(): string
85 | {
86 | return $this->body;
87 | }
88 |
89 | /**
90 | * @param string $body
91 | *
92 | * @return Message
93 | */
94 | public function setBody(string $body)
95 | {
96 | $this->body = $body;
97 |
98 | return $this;
99 | }
100 |
101 | /**
102 | * @return array
103 | */
104 | public function getAttributes(): array
105 | {
106 | return $this->attributes;
107 | }
108 |
109 | /**
110 | * @param array $attributes
111 | *
112 | * @return Message
113 | */
114 | public function setAttributes(array $attributes)
115 | {
116 | $this->attributes = $attributes;
117 |
118 | return $this;
119 | }
120 |
121 | /**
122 | * @return string
123 | */
124 | public function getGroupId(): string
125 | {
126 | return $this->groupId;
127 | }
128 |
129 | /**
130 | * @param string $groupId
131 | *
132 | * @return Message
133 | */
134 | public function setGroupId(string $groupId)
135 | {
136 | $this->groupId = $groupId;
137 |
138 | return $this;
139 | }
140 |
141 | /**
142 | * @return string
143 | */
144 | public function getDeduplicationId(): string
145 | {
146 | return $this->deduplicationId;
147 | }
148 |
149 | /**
150 | * @param string $deduplicationId
151 | *
152 | * @return Message
153 | */
154 | public function setDeduplicationId(string $deduplicationId)
155 | {
156 | $this->deduplicationId = $deduplicationId;
157 |
158 | return $this;
159 | }
160 |
161 | /**
162 | * @return string
163 | */
164 | public function getReceiptHandle(): string
165 | {
166 | return $this->receiptHandle;
167 | }
168 |
169 | /**
170 | * @param string $receiptHandle
171 | *
172 | * @return Message
173 | */
174 | public function setReceiptHandle(string $receiptHandle)
175 | {
176 | $this->receiptHandle = $receiptHandle;
177 |
178 | return $this;
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/Service/MessageCollection.php:
--------------------------------------------------------------------------------
1 | client = $client;
32 | }
33 |
34 | /**
35 | * @param SqsClient $client
36 | * @param string $queueName
37 | * @param string $queueUrl
38 | * @param AbstractWorker $queueWorker
39 | * @param array $options
40 | *
41 | * @return BaseQueue
42 | */
43 | public static function createQueue(
44 | SqsClient $client,
45 | string $queueName,
46 | string $queueUrl,
47 | AbstractWorker $queueWorker,
48 | array $options = []
49 | ) {
50 | $instance = new self($client);
51 |
52 | return $instance->create($queueName, $queueUrl, $queueWorker, $options);
53 | }
54 |
55 | /**
56 | * @param string $queueName
57 | * @param string $queueUrl
58 | * @param AbstractWorker $queueWorker
59 | * @param array $options
60 | *
61 | * @return BaseQueue
62 | */
63 | public function create(
64 | string $queueName,
65 | string $queueUrl,
66 | AbstractWorker $queueWorker,
67 | array $options = []
68 | ) {
69 | if ($this->queues === null) {
70 | $this->queues = [];
71 | }
72 |
73 | if (isset($this->queues[$queueUrl])) {
74 | return $this->queues[$queueUrl];
75 | }
76 |
77 | $queue = new BaseQueue($this->client, $queueName, $queueUrl, $queueWorker, $options);
78 | $this->queues[$queueUrl] = $queue;
79 |
80 | return $queue;
81 | }
82 |
83 | /**
84 | * @return SqsClient
85 | */
86 | public function getClient(): SqsClient
87 | {
88 | return $this->client;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Service/QueueManager.php:
--------------------------------------------------------------------------------
1 | 0,
24 | 'MaximumMessageSize' => 262144, // 256 KiB
25 | 'MessageRetentionPeriod' => 345600, // 4 days
26 | 'ReceiveMessageWaitTimeSeconds' => 0,
27 | 'VisibilityTimeout' => 30,
28 | 'RedrivePolicy' => ''
29 | ];
30 |
31 | const QUEUE_FIFO_ATTR_DEFAULT = [
32 | 'ContentBasedDeduplication' => true
33 | ];
34 |
35 | /**
36 | * QueueManager constructor.
37 | *
38 | * @param SqsClient $client
39 | */
40 | public function __construct(SqsClient $client)
41 | {
42 | $this->client = $client;
43 | }
44 |
45 | /**
46 | * @param string $prefix
47 | *
48 | * @return array
49 | */
50 | public function listQueue(string $prefix = '')
51 | {
52 | $queues = [];
53 | $attr = [];
54 | if (!empty($prefix)) {
55 | $attr['QueueNamePrefix'] = $prefix;
56 | }
57 |
58 | try {
59 | $result = $this->client->listQueues($attr);
60 | if ($result->count()) {
61 | $queues = $result->get('QueueUrls');
62 | }
63 | } catch (AwsException $e) {
64 | throw new \InvalidArgumentException($e->getAwsErrorMessage());
65 | }
66 |
67 | return $queues;
68 | }
69 |
70 | /**
71 | * @param string $queueName
72 | * @param array $queueAttribute
73 | *
74 | * @return string
75 | */
76 | public function createQueue(string $queueName, array $queueAttribute = [])
77 | {
78 | $queryUrl = '';
79 |
80 | $queueAttributeDefault = self::QUEUE_ATTR_DEFAULT;
81 | if (static::isFifoQueue($queueName)) {
82 | $queueAttributeDefault = array_merge($queueAttributeDefault, self::QUEUE_FIFO_ATTR_DEFAULT);
83 | }
84 |
85 | $queueAttribute = array_filter($queueAttribute, function ($key) use ($queueAttributeDefault) {
86 | return array_key_exists($key, $queueAttributeDefault);
87 | }, ARRAY_FILTER_USE_KEY);
88 | $attr = [
89 | 'Attributes' => array_merge($queueAttributeDefault, $queueAttribute),
90 | 'QueueName' => $queueName
91 | ];
92 |
93 | try {
94 | $result = $this->client->createQueue($attr);
95 | if ($result->count()) {
96 | $queryUrl = $result->get('QueueUrl');
97 | }
98 | } catch (AwsException $e) {
99 | throw new \InvalidArgumentException($e->getAwsErrorMessage());
100 | }
101 |
102 | return $queryUrl;
103 | }
104 |
105 | /**
106 | * @param string $queueUrl
107 | *
108 | * @return bool
109 | */
110 | public function deleteQueue(string $queueUrl)
111 | {
112 | try {
113 | $this->client->deleteQueue(['QueueUrl' => $queueUrl]);
114 |
115 | return true;
116 | } catch (AwsException $e) {
117 | throw new \InvalidArgumentException($e->getAwsErrorMessage());
118 | }
119 | }
120 |
121 | /**
122 | * @param string $queueUrl
123 | * @param array $queueAttribute
124 | *
125 | * @return bool
126 | */
127 | public function setQueueAttributes(string $queueUrl, array $queueAttribute)
128 | {
129 | $queueAttribute = array_filter($queueAttribute, function ($key) {
130 | return array_key_exists($key, self::QUEUE_ATTR_DEFAULT);
131 | }, ARRAY_FILTER_USE_KEY);
132 | $attr = [
133 | 'Attributes' => $queueAttribute,
134 | 'QueueUrl' => $queueUrl
135 | ];
136 |
137 | try {
138 | $this->client->setQueueAttributes($attr);
139 |
140 | return true;
141 | } catch (AwsException $e) {
142 | throw new \InvalidArgumentException($e->getAwsErrorMessage());
143 | }
144 | }
145 |
146 | /**
147 | * @param string $queueUrl
148 | *
149 | * @return array
150 | */
151 | public function getQueueAttributes(string $queueUrl)
152 | {
153 | $attr = [];
154 |
155 | try {
156 | $result = $this->client->getQueueAttributes([
157 | 'AttributeNames' => ['All'],
158 | 'QueueUrl' => $queueUrl
159 | ]);
160 | if ($result->count()) {
161 | $attr = $result->get('Attributes');
162 | }
163 | } catch (AwsException $e) {
164 | throw new \InvalidArgumentException($e->getAwsErrorMessage());
165 | }
166 |
167 | return $attr;
168 | }
169 |
170 | /**
171 | * @return SqsClient
172 | */
173 | public function getClient(): SqsClient
174 | {
175 | return $this->client;
176 | }
177 |
178 | /**
179 | * @param $queueName
180 | *
181 | * @return bool
182 | */
183 | public static function isFifoQueue($queueName): bool
184 | {
185 | return '.fifo' === substr($queueName, -5);
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/Service/Worker/AbstractWorker.php:
--------------------------------------------------------------------------------
1 | getBody() === 'ping') {
24 | echo 'Pong. Now is ' . (new \DateTime('now'))->format('M d, Y H:i:s') . PHP_EOL;
25 |
26 | return true;
27 | }
28 |
29 | $this->preExecute($message);
30 | try {
31 | $result = $this->execute($message);
32 | } catch (\Exception $e) {
33 | $result = false;
34 | $this->error = $e->getMessage();
35 | }
36 | $this->postExecute($message);
37 |
38 | // Let worker does something on success or failure
39 | $result === true ? $this->onSucceeded() : $this->onFailed();
40 |
41 | return $result;
42 | }
43 |
44 | /**
45 | * @param Message $message
46 | */
47 | protected function preExecute(Message $message)
48 | {
49 | // Do something here
50 | }
51 |
52 | /**
53 | * Do something via post execution. It is better to proceed with task related to message.
54 | *
55 | * @param Message $message
56 | */
57 | protected function postExecute(Message $message)
58 | {
59 | // Do something here
60 | }
61 |
62 | /**
63 | * @param Message $message
64 | *
65 | * @return boolean
66 | */
67 | abstract protected function execute(Message $message);
68 |
69 | /**
70 | * Event fired when worker has processed message successfully.
71 | *
72 | * @return void
73 | */
74 | protected function onSucceeded()
75 | {
76 | // Do something here
77 | }
78 |
79 | /**
80 | * Event fired when worker has failed to process message.
81 | *
82 | * @return void
83 | */
84 | protected function onFailed()
85 | {
86 | // Do something here
87 | }
88 |
89 | /**
90 | * Check if worker has error after processing.
91 | * By default, error is set to null
.
92 | *
93 | * @return bool
94 | */
95 | public function hasError()
96 | {
97 | return $this->error !== null;
98 | }
99 |
100 | /**
101 | * Get worker error message.
102 | *
103 | * @return string
104 | */
105 | public function error()
106 | {
107 | return $this->error;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/Tests/Functional/Command/QueueAttrCommandTest.php:
--------------------------------------------------------------------------------
1 | queueManager = $this->getMockBuilder(QueueManager::class)
25 | ->disableOriginalConstructor()
26 | ->getMock();
27 | $this->queueManager
28 | ->expects($this->any())
29 | ->method('getQueueAttributes')
30 | ->with('my-queue-url')
31 | ->willReturn(['att1' => 'value1', 'att2' => 'value2']);
32 |
33 | $this->getContainer()->set('tritran.sqs_queue.queue_manager', $this->queueManager);
34 | }
35 |
36 | /**
37 | * Test: Retrieve the attribute of a specified queue.
38 | */
39 | public function testExecute()
40 | {
41 | $commandTester = $this->createCommandTester(new QueueAttrCommand());
42 | $commandTester->execute([
43 | 'url' => 'my-queue-url',
44 | ]);
45 |
46 | $output = $commandTester->getDisplay();
47 |
48 | $this->assertContains('value1', $output);
49 | $this->assertContains('value2', $output);
50 | $this->assertContains('Done', $output);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Tests/Functional/Command/QueueCreateCommandTest.php:
--------------------------------------------------------------------------------
1 | queueManager === null) {
26 | $this->queueManager = $this->getMockBuilder(QueueManager::class)
27 | ->disableOriginalConstructor()
28 | ->getMock();
29 | $this->queueManager
30 | ->expects($this->any())
31 | ->method('createQueue')
32 | ->with('my-queue-name', [
33 | 'DelaySeconds' => 1,
34 | 'MaximumMessageSize' => 1,
35 | 'MessageRetentionPeriod' => 1,
36 | 'ReceiveMessageWaitTimeSeconds' => 1,
37 | 'VisibilityTimeout' => 1,
38 | 'ContentBasedDeduplication' => true,
39 | ])
40 | ->willReturn('new-queue-url');
41 |
42 | $this->getContainer()->set('tritran.sqs_queue.queue_manager', $this->queueManager);
43 | }
44 | }
45 |
46 | /**
47 | * Test: Create a queue by name and basic attributions
48 | */
49 | public function testExecute()
50 | {
51 | $commandTester = $this->createCommandTester(new QueueCreateCommand());
52 | $commandTester->execute([
53 | 'name' => 'my-queue-name',
54 | '--delay_seconds' => 1,
55 | '--maximum_message_size' => 1,
56 | '--message_retention_period' => 1,
57 | '--receive_message_wait_time_seconds' => 1,
58 | '--visibility_timeout' => 1,
59 | '--content_based_deduplication' => true,
60 | ]);
61 |
62 | $output = $commandTester->getDisplay();
63 | $this->assertContains('Created successfully. New Queue URL: new-queue-url', $output);
64 | }
65 |
66 | /**
67 | * Test: Create a new queue which was configured or existed already.
68 | */
69 | public function testExecuteWithAnExistingQueueName()
70 | {
71 | $commandTester = $this->createCommandTester(new QueueCreateCommand());
72 |
73 | $this->expectException(\InvalidArgumentException::class);
74 | $commandTester->execute([
75 | 'name' => 'basic_queue' // Existed
76 | ]);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Tests/Functional/Command/QueueDeleteCommandTest.php:
--------------------------------------------------------------------------------
1 | queueManager === null) {
26 | $this->queueManager = $this->getMockBuilder(QueueManager::class)
27 | ->disableOriginalConstructor()
28 | ->getMock();
29 | $this->queueManager
30 | ->expects($this->any())
31 | ->method('deleteQueue')
32 | ->with('my-queue-url')
33 | ->willReturn(true);
34 |
35 | $this->getContainer()->set('tritran.sqs_queue.queue_manager', $this->queueManager);
36 | }
37 | }
38 |
39 | /**
40 | * Test: Delete a queue without force option
41 | */
42 | public function testExecuteWithoutForce()
43 | {
44 | $commandTester = $this->createCommandTester(new QueueDeleteCommand());
45 | $commandTester->execute([
46 | 'url' => 'my-queue-url'
47 | ]);
48 |
49 | $output = $commandTester->getDisplay();
50 | $this->assertContains('Option --force is mandatory to drop data', $output);
51 | }
52 |
53 | /**
54 | * Test: Delete a queue without force option
55 | */
56 | public function testExecute()
57 | {
58 | $commandTester = $this->createCommandTester(new QueueDeleteCommand());
59 | $commandTester->execute([
60 | 'url' => 'my-queue-url',
61 | '--force' => true
62 | ]);
63 |
64 | $output = $commandTester->getDisplay();
65 | $this->assertContains('Done', $output);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Tests/Functional/Command/QueueListCommandTest.php:
--------------------------------------------------------------------------------
1 | queueManager === null) {
26 | $this->queueManager = $this->getMockBuilder(QueueManager::class)
27 | ->disableOriginalConstructor()
28 | ->getMock();
29 | $this->queueManager
30 | ->expects($this->any())
31 | ->method('listQueue')
32 | ->willReturnCallback(function ($arg) {
33 | return $arg == 'invalid' ? [] : ['queue-url-1', 'queue-url-2'];
34 | });
35 |
36 | $this->getContainer()->set('tritran.sqs_queue.queue_manager', $this->queueManager);
37 | }
38 | }
39 |
40 | /**
41 | * Test: Returns a list of your queues.
42 | */
43 | public function testExecute()
44 | {
45 | $commandTester = $this->createCommandTester(new QueueListCommand());
46 | $commandTester->execute([
47 | '--prefix' => 'queue-prefix'
48 | ]);
49 |
50 | $output = $commandTester->getDisplay();
51 | $this->assertContains('queue-url-1', $output);
52 | $this->assertContains('queue-url-2', $output);
53 | $this->assertContains('Done', $output);
54 | }
55 |
56 | /**
57 | * Test: There are not any queue
58 | */
59 | public function testExecuteWithEmpty()
60 | {
61 | $commandTester = $this->createCommandTester(new QueueListCommand());
62 | $commandTester->execute([
63 | '--prefix' => 'invalid'
64 | ]);
65 | $output = $commandTester->getDisplay();
66 | $this->assertContains(
67 | 'You don\'t have any queue at this moment. Please go to AWS Console to create a new one.',
68 | $output
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Tests/Functional/Command/QueuePingCommandTest.php:
--------------------------------------------------------------------------------
1 | queue === null) {
26 | $this->queue = $this->getMockBuilder(BaseQueue::class)
27 | ->disableOriginalConstructor()
28 | ->getMock();
29 | $this->queue
30 | ->expects($this->any())
31 | ->method('ping')
32 | ->willReturn('new-message-id');
33 |
34 | $this->getContainer()->set('tritran.sqs_queue.basic_queue', $this->queue);
35 | }
36 | }
37 |
38 | /**
39 | * Test: Purge a queue with a non-existing queue
40 | */
41 | public function testExecuteWithNonExistingQueue()
42 | {
43 | $commandTester = $this->createCommandTester(new QueuePingCommand());
44 |
45 | $this->expectException(\InvalidArgumentException::class);
46 | $commandTester->execute([
47 | 'name' => 'non-existing-queue'
48 | ]);
49 | }
50 |
51 | /**
52 | * Test: Delete a queue without force option
53 | */
54 | public function testExecute()
55 | {
56 | $commandTester = $this->createCommandTester(new QueuePingCommand());
57 | $commandTester->execute([
58 | 'name' => 'basic_queue'
59 | ]);
60 |
61 | $output = $commandTester->getDisplay();
62 | $this->assertContains('Done', $output);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Tests/Functional/Command/QueuePurgeCommandTest.php:
--------------------------------------------------------------------------------
1 | queue === null) {
26 | $this->queue = $this->getMockBuilder(BaseQueue::class)
27 | ->disableOriginalConstructor()
28 | ->getMock();
29 | $this->queue
30 | ->expects($this->any())
31 | ->method('purge')
32 | ->willReturn(true);
33 |
34 | $this->getContainer()->set('tritran.sqs_queue.basic_queue', $this->queue);
35 | }
36 | }
37 |
38 | /**
39 | * Test: Purge a queue without force option
40 | */
41 | public function testExecuteWithoutForce()
42 | {
43 | $commandTester = $this->createCommandTester(new QueuePurgeCommand());
44 | $commandTester->execute([
45 | 'name' => 'my-queue-name'
46 | ]);
47 |
48 | $output = $commandTester->getDisplay();
49 | $this->assertContains('Option --force is mandatory to drop data', $output);
50 | }
51 |
52 | /**
53 | * Test: Purge a queue with a non-existing queue
54 | */
55 | public function testExecuteWithNonExistingQueue()
56 | {
57 | $commandTester = $this->createCommandTester(new QueuePurgeCommand());
58 |
59 | $this->expectException(\InvalidArgumentException::class);
60 | $commandTester->execute([
61 | 'name' => 'non-existing-queue',
62 | '--force' => true
63 | ]);
64 | }
65 |
66 | /**
67 | * Test: Delete a queue without force option
68 | */
69 | public function testExecute()
70 | {
71 | $commandTester = $this->createCommandTester(new QueuePurgeCommand());
72 | $commandTester->execute([
73 | 'name' => 'basic_queue',
74 | '--force' => true
75 | ]);
76 |
77 | $output = $commandTester->getDisplay();
78 | $this->assertContains('Done', $output);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Tests/Functional/Command/QueueUpdateCommandTest.php:
--------------------------------------------------------------------------------
1 | queueManager === null) {
25 | $this->queueManager = $this->getMockBuilder(QueueManager::class)
26 | ->disableOriginalConstructor()
27 | ->getMock();
28 | $this->queueManager
29 | ->expects($this->any())
30 | ->method('listQueue')
31 | ->willReturn(['aws-basic-queue-url']);
32 | $this->queueManager
33 | ->expects($this->any())
34 | ->method('setQueueAttributes')
35 | ->willReturn(true);
36 |
37 | $this->getContainer()->set('tritran.sqs_queue.queue_manager', $this->queueManager);
38 | }
39 | }
40 |
41 | /**
42 | * Test: Update Queue attribute based on configuration without force option.
43 | */
44 | public function testExecuteWithoutForce()
45 | {
46 | $commandTester = $this->createCommandTester(new QueueUpdateCommand());
47 | $commandTester->execute([]);
48 |
49 | $output = $commandTester->getDisplay();
50 | $this->assertContains('Option --force is mandatory to update data', $output);
51 | }
52 |
53 | /**
54 | * Test: Delete a queue without force option.
55 | */
56 | public function testExecute()
57 | {
58 | $commandTester = $this->createCommandTester(new QueueUpdateCommand());
59 | $commandTester->execute(['--force' => true]);
60 |
61 | $output = $commandTester->getDisplay();
62 | $this->assertContains('Done', $output);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Tests/Functional/Command/QueueWorkerCommandTest.php:
--------------------------------------------------------------------------------
1 | baseWorker === null) {
25 | $this->baseWorker = $this->getMockBuilder(BaseWorker::class)
26 | ->disableOriginalConstructor()
27 | ->getMock();
28 | $this->baseWorker
29 | ->expects($this->any())
30 | ->method('start')
31 | ->willReturn(true);
32 |
33 | $this->getContainer()->set('tritran.sqs_queue.queue_worker', $this->baseWorker);
34 | }
35 | }
36 |
37 | /**
38 | * Test: start a worker for a non-existing queue.
39 | */
40 | public function testExecuteWithNonExistingQueue()
41 | {
42 | $commandTester = $this->createCommandTester(new QueueWorkerCommand());
43 |
44 | $this->expectException(\InvalidArgumentException::class);
45 | $commandTester->execute([
46 | 'name' => 'non-existing-queue',
47 | ]);
48 | }
49 |
50 | /**
51 | * Test: start a worker with an invalid value of amount of messages.
52 | */
53 | public function testExecuteWithInvalidAmountMessages()
54 | {
55 | $commandTester = $this->createCommandTester(new QueueWorkerCommand());
56 |
57 | $this->expectException(\InvalidArgumentException::class);
58 | $commandTester->execute([
59 | 'name' => 'basic_queue',
60 | '--messages' => -1,
61 | ]);
62 | }
63 |
64 | /**
65 | * Test: Start a worker for listening to a queue.
66 | */
67 | public function testExecute()
68 | {
69 | $commandTester = $this->createCommandTester(new QueueWorkerCommand());
70 | $commandTester->execute([
71 | 'name' => 'basic_queue',
72 | ]);
73 |
74 | $output = $commandTester->getDisplay();
75 | $this->assertContains('Start listening to queue', $output);
76 | }
77 |
78 | /**
79 | * Test: invalid input limit should throw an exception.
80 | */
81 | public function testInvalidInputLimit()
82 | {
83 | $commandTester = $this->createCommandTester(new QueueWorkerCommand());
84 |
85 | $this->expectException(\InvalidArgumentException::class);
86 | $commandTester->execute([
87 | 'name' => 'basic_queue',
88 | '--limit' => 0,
89 | ]);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/Tests/Unit/DependencyInjection/Compiler/SQSQueuePassTest.php:
--------------------------------------------------------------------------------
1 | getContainer();
32 | $compiler = new SQSQueuePass();
33 |
34 | $this->expectException(\InvalidArgumentException::class);
35 | $compiler->process($container);
36 | }
37 |
38 | /**
39 | * Make sure the worker of queue should be a valid and callable service.
40 | */
41 | public function testProcessFailureWithBadWorker()
42 | {
43 | $container = $this->getContainer();
44 | $container->setDefinition('aws.sqs', new Definition());
45 | $container->setParameter(
46 | 'tritran.sqs_queue.queues',
47 | ['queue-name' => ['queue_url' => 'my-url', 'worker' => 'bad-worker']]
48 | );
49 |
50 | $compiler = new SQSQueuePass();
51 | $this->expectException(\InvalidArgumentException::class);
52 | $compiler->process($container);
53 | }
54 |
55 | /**
56 | * Make sure the name of queue should be different with predefined queue-name
57 | */
58 | public function testProcessFailureWithPredefinedQueueName()
59 | {
60 | $container = $this->getContainer();
61 | $container->setDefinition('aws.sqs', new Definition());
62 |
63 | $defaultQueueServices = ['queues', 'queue_factory', 'queue_manager', 'queue_worker', 'basic_queue'];
64 | $container->setParameter(
65 | 'tritran.sqs_queue.queues',
66 | [
67 | $defaultQueueServices[array_rand($defaultQueueServices)] => [
68 | 'queue_url' => 'my-url',
69 | 'worker' => 'bad-worker'
70 | ]
71 | ]
72 | );
73 |
74 | $compiler = new SQSQueuePass();
75 | $this->expectException(\InvalidArgumentException::class);
76 | $compiler->process($container);
77 | }
78 |
79 | /**
80 | * @return array
81 | */
82 | public function configurationProvider(): array
83 | {
84 | $container = $this->getContainer();
85 | $container->setDefinition('aws.sqs', new Definition());
86 |
87 | $basicWorker = BasicWorker::class;
88 | $basicWorkerAsService = 'tritran.sqs_queue.fixture.basic_worker';
89 | $container->setDefinition($basicWorkerAsService, new Definition($basicWorker));
90 |
91 | return [
92 | // Case #0: Load a worker with default attributes
93 | [
94 | $container,
95 | [
96 | 'basic-queue' => [
97 | 'queue_url' => 'basic-url',
98 | 'worker' => $basicWorker,
99 | 'attributes' => []
100 | ]
101 | ],
102 | [
103 | 'basic-queue' => [
104 | 'basic-url',
105 | new Definition($basicWorker),
106 | [
107 | 'DelaySeconds' => 0,
108 | 'MaximumMessageSize' => 262144,
109 | 'MessageRetentionPeriod' => 345600,
110 | 'ReceiveMessageWaitTimeSeconds' => 20,
111 | 'VisibilityTimeout' => 30,
112 | 'RedrivePolicy' => ''
113 | ]
114 | ]
115 | ]
116 | ],
117 | // Case #1: Load a worker as a callable class with some attributes
118 | [
119 | $container,
120 | [
121 | 'basic-queue' => [
122 | 'queue_url' => 'basic-url',
123 | 'worker' => $basicWorker,
124 | 'attributes' => [
125 | 'delay_seconds' => 1,
126 | 'maximum_message_size' => 1,
127 | 'message_retention_period' => 1,
128 | 'receive_message_wait_time_seconds' => 1,
129 | 'visibility_timeout' => 1,
130 | 'redrive_policy' => [
131 | 'dead_letter_queue' => 'basic_dead_letter_queue_1',
132 | 'max_receive_count' => 1
133 | ]
134 | ]
135 | ]
136 | ],
137 | [
138 | 'basic-queue' => [
139 | 'basic-url',
140 | new Definition($basicWorker),
141 | [
142 | 'DelaySeconds' => 1,
143 | 'MaximumMessageSize' => 1,
144 | 'MessageRetentionPeriod' => 1,
145 | 'ReceiveMessageWaitTimeSeconds' => 1,
146 | 'VisibilityTimeout' => 1,
147 | 'RedrivePolicy' => json_encode([
148 | 'deadLetterTargetArn' => 'basic_dead_letter_queue_1',
149 | 'maxReceiveCount' => 1
150 | ])
151 | ]
152 | ]
153 | ]
154 | ],
155 | // Case #2: Load a worker as a service
156 | [
157 | $container,
158 | [
159 | 'basic-queue' => [
160 | 'queue_url' => 'basic-url',
161 | 'worker' => $basicWorkerAsService,
162 | 'attributes' => [
163 | 'delay_seconds' => 2,
164 | 'maximum_message_size' => 2,
165 | 'message_retention_period' => 2,
166 | 'receive_message_wait_time_seconds' => 2,
167 | 'visibility_timeout' => 2,
168 | 'redrive_policy' => [
169 | 'dead_letter_queue' => 'basic_dead_letter_queue_2',
170 | 'max_receive_count' => 2
171 | ]
172 | ]
173 | ]
174 | ],
175 | [
176 | 'basic-queue' => [
177 | 'basic-url',
178 | new Reference($basicWorkerAsService),
179 | [
180 | 'DelaySeconds' => 2,
181 | 'MaximumMessageSize' => 2,
182 | 'MessageRetentionPeriod' => 2,
183 | 'ReceiveMessageWaitTimeSeconds' => 2,
184 | 'VisibilityTimeout' => 2,
185 | 'RedrivePolicy' => json_encode([
186 | 'deadLetterTargetArn' => 'basic_dead_letter_queue_2',
187 | 'maxReceiveCount' => 2
188 | ])
189 | ]
190 | ]
191 | ]
192 | ],
193 | // Case #3: Load a FIFO queue
194 | [
195 | $container,
196 | [
197 | 'queue.fifo' => [
198 | 'queue_url' => 'fifo-queue-url',
199 | 'worker' => $basicWorker,
200 | 'attributes' => []
201 | ]
202 | ],
203 | [
204 | 'queue.fifo' => [
205 | 'fifo-queue-url',
206 | new Definition($basicWorker),
207 | [
208 | 'DelaySeconds' => 0,
209 | 'MaximumMessageSize' => 262144,
210 | 'MessageRetentionPeriod' => 345600,
211 | 'ReceiveMessageWaitTimeSeconds' => 20,
212 | 'VisibilityTimeout' => 30,
213 | 'RedrivePolicy' => '',
214 | 'ContentBasedDeduplication' => true
215 | ]
216 | ]
217 | ]
218 | ],
219 | // Case #4: Load multi queues at the same time
220 | [
221 | $container,
222 | [
223 | 'basic-queue-1' => ['queue_url' => 'basic-url-1', 'worker' => $basicWorker],
224 | 'basic-queue-2' => ['queue_url' => 'basic-url-2', 'worker' => $basicWorkerAsService],
225 | 'queue.fifo' => ['queue_url' => 'fifo-queue-url', 'worker' => $basicWorkerAsService]
226 | ],
227 | [
228 | 'basic-queue-1' => [
229 | 'basic-url-1',
230 | new Definition($basicWorker),
231 | [
232 | 'DelaySeconds' => 0,
233 | 'MaximumMessageSize' => 262144,
234 | 'MessageRetentionPeriod' => 345600,
235 | 'ReceiveMessageWaitTimeSeconds' => 20,
236 | 'VisibilityTimeout' => 30,
237 | 'RedrivePolicy' => ''
238 | ]
239 | ],
240 | 'basic-queue-2' => [
241 | 'basic-url-2',
242 | new Reference($basicWorkerAsService),
243 | [
244 | 'DelaySeconds' => 0,
245 | 'MaximumMessageSize' => 262144,
246 | 'MessageRetentionPeriod' => 345600,
247 | 'ReceiveMessageWaitTimeSeconds' => 20,
248 | 'VisibilityTimeout' => 30,
249 | 'RedrivePolicy' => ''
250 | ]
251 | ],
252 | 'queue.fifo' => [
253 | 'fifo-queue-url',
254 | new Reference($basicWorkerAsService),
255 | [
256 | 'DelaySeconds' => 0,
257 | 'MaximumMessageSize' => 262144,
258 | 'MessageRetentionPeriod' => 345600,
259 | 'ReceiveMessageWaitTimeSeconds' => 20,
260 | 'VisibilityTimeout' => 30,
261 | 'RedrivePolicy' => '',
262 | 'ContentBasedDeduplication' => true
263 | ]
264 | ]
265 | ]
266 | ],
267 |
268 | ];
269 | }
270 |
271 | /**
272 | * Load configuration of Machine Engine
273 | *
274 | * @param ContainerBuilder $container
275 | * @param array $config
276 | * @param array $expectedArgs
277 | *
278 | * @dataProvider configurationProvider
279 | */
280 | public function testProcess($container, $config, $expectedArgs)
281 | {
282 | $container->setParameter('tritran.sqs_queue.queues', $config);
283 |
284 | $compiler = new SQSQueuePass();
285 | $compiler->process($container);
286 |
287 | foreach ($config as $queueName => $queueOption) {
288 | $queueId = sprintf('tritran.sqs_queue.%s', $queueName);
289 |
290 | $this->assertTrue($container->hasDefinition($queueId));
291 |
292 | $definition = $container->getDefinition($queueId);
293 | $this->assertEquals([
294 | new Reference('tritran.sqs_queue.queue_factory'),
295 | 'create'
296 | ], $definition->getFactory());
297 |
298 | $this->assertEquals(
299 | array_merge([$queueName], $expectedArgs[$queueName]),
300 | $definition->getArguments()
301 | );
302 | }
303 | }
304 | }
305 |
--------------------------------------------------------------------------------
/Tests/Unit/DependencyInjection/ConfigurationTest.php:
--------------------------------------------------------------------------------
1 | processConfiguration(new Configuration(), []);
22 | $expectedConfiguration = [];
23 | $this->assertEquals($expectedConfiguration, $processorConfig);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Tests/Unit/DependencyInjection/TriTranSqsQueueExtensionTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('tritran_sqs_queue', $extension->getAlias());
22 | }
23 |
24 | /**
25 | * @return ContainerBuilder
26 | */
27 | protected function getContainer(): ContainerBuilder
28 | {
29 | return new ContainerBuilder();
30 | }
31 |
32 | /**
33 | * Make sure the extension loaded all pre-defined services successfully
34 | */
35 | public function testPredefinedServicesLoaded()
36 | {
37 | $container = $this->getContainer();
38 | $extension = new TriTranSqsQueueExtension();
39 | $extension->load([], $container);
40 |
41 | $this->assertTrue($container->hasDefinition('tritran.sqs_queue.queue_factory'));
42 | $this->assertTrue($container->hasDefinition('tritran.sqs_queue.queue_worker'));
43 | $this->assertTrue($container->hasDefinition('tritran.sqs_queue.queue_manager'));
44 | }
45 |
46 | /**
47 | * Make sure the extension loaded all pre-defined parameters successfully via configuration
48 | */
49 | public function testPredefinedParametersLoaded()
50 | {
51 | $container = $this->getContainer();
52 | $extension = new TriTranSqsQueueExtension();
53 | $extension->load([
54 | 'tritran_sqs_queue' => [
55 | 'sqs_queue' => [
56 | 'queues' => [
57 | ['name' => 'queue-1', 'queue_url' => 'url-1', 'worker' => 'worker-1'],
58 | ['name' => 'queue-2', 'queue_url' => 'url-2', 'worker' => 'worker-2'],
59 | ]
60 | ]
61 | ]
62 | ], $container);
63 |
64 | $this->assertTrue($container->hasParameter('tritran.sqs_queue.queues'));
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Tests/Unit/Service/BaseQueueTest.php:
--------------------------------------------------------------------------------
1 | errors = [];
32 | set_error_handler([$this, "errorHandler"]);
33 | }
34 |
35 | /**
36 | * @param $errno
37 | * @param $errstr
38 | * @param $errfile
39 | * @param $errline
40 | * @param $errcontext
41 | */
42 | public function errorHandler($errno, $errstr, $errfile, $errline, $errcontext)
43 | {
44 | $this->errors[] = compact('errno', 'errstr', 'errfile', 'errline', 'errcontext');
45 | }
46 |
47 | /**
48 | * @param $errstr
49 | * @param $errno
50 | */
51 | public function assertError($errstr, $errno)
52 | {
53 | foreach ($this->errors as $error) {
54 | if ($error["errstr"] === $errstr
55 | && $error["errno"] === $errno) {
56 | $this->assertTrue(true);
57 |
58 | return;
59 | }
60 | }
61 | $this->fail(
62 | "Error with level " . $errno . " and message '" . $errstr . "' not found in ",
63 | var_export($this->errors, true)
64 | );
65 | }
66 |
67 | /**
68 | * @param array $entries
69 | *
70 | * @return \PHPUnit_Framework_MockObject_MockObject|Result
71 | */
72 | private function getAwsResult($entries)
73 | {
74 | /** @var \PHPUnit_Framework_MockObject_MockObject|Result $result */
75 | $result = $this->getMockBuilder(Result::class)->getMock();
76 | $result->expects($this->any())
77 | ->method('get')
78 | ->withAnyParameters()
79 | ->willReturnCallback(function ($arg) use ($entries) {
80 | return $entries[$arg] ?? [];
81 | });
82 |
83 | return $result;
84 | }
85 |
86 | /**
87 | * @return \PHPUnit_Framework_MockObject_MockObject|SqsClient
88 | */
89 | private function getAwsClient()
90 | {
91 | /** @var \PHPUnit_Framework_MockObject_MockObject|SqsClient $client */
92 | $client = $this->getMockBuilder(SqsClient::class)
93 | ->disableOriginalConstructor()
94 | ->setMethods(['sendMessage', 'receiveMessage', 'deleteMessage', 'changeMessageVisibility', 'purgeQueue'])
95 | ->getMock();
96 |
97 | return $client;
98 | }
99 |
100 | /**
101 | * @return BaseQueue
102 | */
103 | public function getQueue()
104 | {
105 | $client = $this->getAwsClient();
106 | $queueName = 'queue-name';
107 | $queueUrl = 'queue-url';
108 | $queueWorker = new BasicWorker();
109 | $queueAttr = ['a', 'b', 'c', 'd'];
110 |
111 | return new BaseQueue($client, $queueName, $queueUrl, $queueWorker, $queueAttr);
112 | }
113 |
114 | /**
115 | * Test: Construction
116 | */
117 | public function testConstruction()
118 | {
119 | $client = $this->getAwsClient();
120 | $queueName = 'queue-name';
121 | $queueUrl = 'queue-url';
122 | $queueWorker = new BasicWorker();
123 | $queueAttr = ['a', 'b', 'c', 'd'];
124 | $queue = new BaseQueue($client, $queueName, $queueUrl, $queueWorker, $queueAttr);
125 |
126 | $this->assertInstanceOf(BaseQueue::class, $queue);
127 | $this->assertEquals($client, $queue->getClient());
128 | $this->assertEquals($queueName, $queue->getQueueName());
129 | $this->assertEquals($queueUrl, $queue->getQueueUrl());
130 | $this->assertEquals($queueWorker, $queue->getQueueWorker());
131 | $this->assertEquals($queueAttr, $queue->getAttributes());
132 | }
133 |
134 | /**
135 | * Test: Getter/Setter
136 | */
137 | public function testGetterSetter()
138 | {
139 | $client = $this->getAwsClient();
140 | $queueUrl = 'queue-url';
141 | $queueAttr = ['a', 'b', 'c', 'd'];
142 |
143 | $queue = new BaseQueue($client, '', '', new BasicWorker(), []);
144 |
145 | $this->assertInstanceOf(BaseQueue::class, $queue->setQueueUrl($queueUrl));
146 | $this->assertEquals($queueUrl, $queue->getQueueUrl());
147 | $this->assertInstanceOf(BaseQueue::class, $queue->setAttributes($queueAttr));
148 | $this->assertEquals($queueAttr, $queue->getAttributes());
149 | }
150 |
151 | /**
152 | * Test: send message to a queue
153 | */
154 | public function testSendMessage()
155 | {
156 | $delay = random_int(1, 10);
157 | $messageBody = 'my-message';
158 | $messageAttr = ['x', 'y', 'z'];
159 | $queueUrl = 'queue-url';
160 |
161 | $client = $this->getAwsClient();
162 | $client->expects($this->any())
163 | ->method('sendMessage')
164 | ->with([
165 | 'DelaySeconds' => $delay,
166 | 'MessageAttributes' => $messageAttr,
167 | 'MessageBody' => $messageBody,
168 | 'QueueUrl' => $queueUrl
169 | ])
170 | ->willReturn($this->getAwsResult(['MessageId' => 'new-message-id']));
171 |
172 | $queue = new BaseQueue($client, 'queue-name', $queueUrl, new BasicWorker(), []);
173 | $this->assertEquals(
174 | 'new-message-id',
175 | $queue->sendMessage(new Message($messageBody, $messageAttr), $delay)
176 | );
177 | }
178 |
179 | /**
180 | * Test: send message to a queue
181 | */
182 | public function testSendPingMessage()
183 | {
184 | $queueUrl = 'queue-url';
185 |
186 | $client = $this->getAwsClient();
187 | $client->expects($this->any())
188 | ->method('sendMessage')
189 | ->with([
190 | 'MessageBody' => 'ping',
191 | 'MessageAttributes' => [],
192 | 'QueueUrl' => $queueUrl
193 | ])
194 | ->willReturn($this->getAwsResult(['MessageId' => 'new-message-id']));
195 |
196 | $queue = new BaseQueue($client, 'queue-name', $queueUrl, new BasicWorker(), []);
197 | $this->assertEquals('new-message-id', $queue->ping());
198 | }
199 |
200 | /**
201 | * Test: send message to a FIFO queue
202 | */
203 | public function testSendMessageToFifoQueue()
204 | {
205 | $messageBody = 'my-message';
206 | $messageAttr = ['x', 'y', 'z'];
207 | $queueUrl = 'queue-url';
208 | $groupId = 'group-name';
209 | $deduplicationId = 'deduplication-id';
210 |
211 | $client = $this->getAwsClient();
212 | $client->expects($this->any())
213 | ->method('sendMessage')
214 | ->with([
215 | 'MessageAttributes' => $messageAttr,
216 | 'MessageBody' => $messageBody,
217 | 'QueueUrl' => $queueUrl,
218 | 'MessageGroupId' => $groupId,
219 | 'MessageDeduplicationId' => $deduplicationId,
220 | ])
221 | ->willReturn($this->getAwsResult(['MessageId' => 'new-message-id']));
222 |
223 | $queue = new BaseQueue($client, 'queue-name.fifo', $queueUrl, new BasicWorker(), []);
224 | $this->assertEquals(
225 | 'new-message-id',
226 | $queue->sendMessage(new Message($messageBody, $messageAttr, $groupId, $deduplicationId))
227 | );
228 | }
229 |
230 | /**
231 | * Test: send message to a FIFO queue
232 | */
233 | public function testSendMessageToFifoQueueWithDelay()
234 | {
235 | $delay = random_int(0, 10);
236 | $messageBody = 'my-message';
237 | $messageAttr = ['x', 'y', 'z'];
238 | $queueUrl = 'queue-url';
239 | $groupId = 'group-name';
240 | $deduplicationId = 'deduplication-id';
241 |
242 | $client = $this->getAwsClient();
243 | $client->expects($this->any())
244 | ->method('sendMessage')
245 | ->with([
246 | 'MessageAttributes' => $messageAttr,
247 | 'MessageBody' => $messageBody,
248 | 'QueueUrl' => $queueUrl,
249 | 'MessageGroupId' => $groupId,
250 | 'MessageDeduplicationId' => $deduplicationId,
251 | ])
252 | ->willReturn($this->getAwsResult(['MessageId' => 'new-message-id']));
253 |
254 | $queue = new BaseQueue($client, 'queue-name.fifo', $queueUrl, new BasicWorker(), []);
255 | $queue->sendMessage(new Message($messageBody, $messageAttr, $groupId, $deduplicationId), $delay);
256 | $this->assertError('FIFO queues don\'t support per-message delays, only per-queue delays.', E_USER_WARNING);
257 | }
258 |
259 | /**
260 | * Test: send message to a queue in failure
261 | */
262 | public function testSendMessageFailure()
263 | {
264 | $client = $this->getAwsClient();
265 | $client->expects($this->any())
266 | ->method('sendMessage')
267 | ->withAnyParameters()
268 | ->willThrowException(new AwsException(
269 | 'AWS Client Exception',
270 | new Command('send-message-command')
271 | ));
272 |
273 | $queue = new BaseQueue($client, 'bad-queue-name', 'bad-queue-url', new BasicWorker(), []);
274 |
275 | $this->expectException(\InvalidArgumentException::class);
276 | $queue->sendMessage(new Message('my-message', []));
277 | }
278 |
279 | /**
280 | * Test: send message to a FIFO queue
281 | */
282 | public function testSendMessageToFifoQueueFailure()
283 | {
284 | $client = $this->getAwsClient();
285 | $client->expects($this->any())
286 | ->method('sendMessage')
287 | ->withAnyParameters()
288 | ->willThrowException(new AwsException(
289 | 'AWS Client Exception',
290 | new Command('send-message-command')
291 | ));
292 |
293 | $queue = new BaseQueue($client, 'queue-name.fifo', 'queue-url', new BasicWorker(), []);
294 |
295 | $this->expectException(\InvalidArgumentException::class);
296 | $this->expectExceptionMessage('MessageGroupId is required for FIFO queues.');
297 | $queue->sendMessage(new Message('my-message', [], ''));
298 | }
299 |
300 | /**
301 | * Test: send message to a FIFO queue
302 | */
303 | public function testSendMessageToFifoQueueWarning()
304 | {
305 | $client = $this->getAwsClient();
306 | $client->expects($this->any())
307 | ->method('sendMessage')
308 | ->withAnyParameters()
309 | ->willThrowException(new AwsException(
310 | 'AWS Client Exception',
311 | new Command('send-message-command')
312 | ));
313 |
314 | $queue = new BaseQueue($client, 'queue-name.fifo', 'queue-url', new BasicWorker(), []);
315 |
316 | $this->expectException(\InvalidArgumentException::class);
317 | $queue->sendMessage(new Message('my-message', [], ''), random_int(0, 10));
318 | }
319 |
320 | /**
321 | * Test: receive Message
322 | */
323 | public function testReceiveMessage()
324 | {
325 | $limit = random_int(1, 10);
326 | $queueUrl = 'queue-url';
327 | $queueAttr = [
328 | 'ReceiveMessageWaitTimeSeconds' => random_int(1, 10),
329 | 'VisibilityTimeout' => random_int(0, 30)
330 | ];
331 | $expected = [
332 | [
333 | 'MessageId' => 'my-message-id',
334 | 'Body' => 'my-body',
335 | 'ReceiptHandle' => 'receipt-handle',
336 | 'Attributes' => [],
337 | ]
338 | ];
339 |
340 | $client = $this->getAwsClient();
341 | $client->expects($this->any())
342 | ->method('receiveMessage')
343 | ->with([
344 | 'AttributeNames' => ['All'],
345 | 'MaxNumberOfMessages' => $limit,
346 | 'MessageAttributeNames' => ['All'],
347 | 'QueueUrl' => $queueUrl,
348 | 'WaitTimeSeconds' => $queueAttr['ReceiveMessageWaitTimeSeconds'],
349 | 'VisibilityTimeout' => $queueAttr['VisibilityTimeout'],
350 | ])
351 | ->willReturn($this->getAwsResult(['Messages' => $expected]));
352 |
353 | $queue = new BaseQueue($client, 'queue-name', $queueUrl, new BasicWorker(), $queueAttr);
354 | $result = $queue->receiveMessage($limit);
355 | $this->assertInstanceOf(MessageCollection::class, $result);
356 | $this->assertEquals($this->arrayMessageToCollection($expected), $result);
357 | }
358 |
359 | /**
360 | * @param array $messages
361 | *
362 | * @return MessageCollection
363 | */
364 | private function arrayMessageToCollection($messages)
365 | {
366 | $collection = new MessageCollection([]);
367 | foreach ($messages as $message) {
368 | $collection->append(
369 | (new Message())
370 | ->setId($message['MessageId'])
371 | ->setBody($message['Body'])
372 | ->setReceiptHandle($message['ReceiptHandle'])
373 | ->setAttributes($message['Attributes'])
374 | );
375 | }
376 |
377 | return $collection;
378 | }
379 |
380 | /**
381 | * Test: receive Message in failure
382 | */
383 | public function testReceiveMessageFailure()
384 | {
385 | $client = $this->getAwsClient();
386 | $client->expects($this->any())
387 | ->method('receiveMessage')
388 | ->withAnyParameters()
389 | ->willThrowException(new AwsException(
390 | 'AWS Client Exception',
391 | new Command('receive-message-command')
392 | ));
393 |
394 | $queue = new BaseQueue($client, 'bad-queue-name', 'bad-queue-url', new BasicWorker(), []);
395 | $this->expectException(\InvalidArgumentException::class);
396 | $queue->receiveMessage();
397 | }
398 |
399 | /**
400 | * Test: Delete a message from queue
401 | */
402 | public function testDeleteMessage()
403 | {
404 | $queueUrl = 'queue-url';
405 | $message = (new Message())->setReceiptHandle('my-receipt-handle');
406 |
407 | $client = $this->getAwsClient();
408 | $client->expects($this->any())
409 | ->method('deleteMessage')
410 | ->with([
411 | 'QueueUrl' => $queueUrl,
412 | 'ReceiptHandle' => $message->getReceiptHandle()
413 | ])
414 | ->willReturn(true);
415 |
416 | $queue = new BaseQueue($client, 'queue-name', $queueUrl, new BasicWorker(), []);
417 | $this->assertTrue($queue->deleteMessage($message));
418 | }
419 |
420 | /**
421 | * Test: Delete a message from queue in failure
422 | */
423 | public function testDeleteMessageFailure()
424 | {
425 | $queueUrl = 'bad-queue-url';
426 | $message = (new Message())->setReceiptHandle('my-receipt-handle');
427 |
428 | $client = $this->getAwsClient();
429 | $client->expects($this->any())
430 | ->method('deleteMessage')
431 | ->with([
432 | 'QueueUrl' => $queueUrl,
433 | 'ReceiptHandle' => $message->getReceiptHandle()
434 | ])
435 | ->willThrowException(new AwsException(
436 | 'AWS Client Exception',
437 | new Command('delete-message-command')
438 | ));
439 |
440 | $queue = new BaseQueue($client, 'bad-queue-name', $queueUrl, new BasicWorker(), []);
441 | $this->expectException(\InvalidArgumentException::class);
442 | $queue->deleteMessage($message);
443 | }
444 |
445 | /**
446 | * Test: Release a message from processing, making it visible again
447 | */
448 | public function testReleaseMessage()
449 | {
450 | $queueUrl = 'queue-url';
451 | $message = (new Message())->setReceiptHandle('my-receipt-handle');
452 |
453 | $client = $this->getAwsClient();
454 | $client->expects($this->any())
455 | ->method('changeMessageVisibility')
456 | ->with([
457 | 'QueueUrl' => $queueUrl,
458 | 'ReceiptHandle' => $message->getReceiptHandle(),
459 | 'VisibilityTimeout' => 0
460 | ])
461 | ->willReturn(true);
462 |
463 | $queue = new BaseQueue($client, 'queue-name', $queueUrl, new BasicWorker(), []);
464 | $this->assertTrue($queue->releaseMessage($message));
465 | }
466 |
467 | /**
468 | * Test: Release a message from processing in failure
469 | */
470 | public function testReleaseMessageFailure()
471 | {
472 | $queueUrl = 'bad-queue-url';
473 | $message = (new Message())->setReceiptHandle('my-receipt-handle');
474 |
475 | $client = $this->getAwsClient();
476 | $client->expects($this->any())
477 | ->method('changeMessageVisibility')
478 | ->with([
479 | 'QueueUrl' => $queueUrl,
480 | 'ReceiptHandle' => $message->getReceiptHandle(),
481 | 'VisibilityTimeout' => 0
482 | ])
483 | ->willThrowException(new AwsException(
484 | 'AWS Client Exception',
485 | new Command('release-message-command')
486 | ));
487 |
488 | $queue = new BaseQueue($client, 'bad-queue-name', $queueUrl, new BasicWorker(), []);
489 | $this->expectException(\InvalidArgumentException::class);
490 | $queue->releaseMessage($message);
491 | }
492 |
493 | /**
494 | * Test: Delete a message from queue
495 | */
496 | public function testPurge()
497 | {
498 | $queueUrl = 'queue-url';
499 |
500 | $client = $this->getAwsClient();
501 | $client->expects($this->any())
502 | ->method('purgeQueue')
503 | ->with([
504 | 'QueueUrl' => $queueUrl
505 | ])
506 | ->willReturn(true);
507 |
508 | $queue = new BaseQueue($client, 'queue-name', $queueUrl, new BasicWorker(), []);
509 | $this->assertTrue($queue->purge());
510 | }
511 |
512 | /**
513 | * Test: Delete a message from queue in failure
514 | */
515 | public function testPurgeFailure()
516 | {
517 | $queueUrl = 'bad-queue-url';
518 |
519 | $client = $this->getAwsClient();
520 | $client->expects($this->any())
521 | ->method('purgeQueue')
522 | ->with([
523 | 'QueueUrl' => $queueUrl
524 | ])
525 | ->willThrowException(new AwsException(
526 | 'AWS Client Exception',
527 | new Command('delete-message-command')
528 | ));
529 |
530 | $queue = new BaseQueue($client, 'bad-queue-name', $queueUrl, new BasicWorker(), []);
531 | $this->expectException(\InvalidArgumentException::class);
532 | $queue->purge();
533 | }
534 | }
535 |
--------------------------------------------------------------------------------
/Tests/Unit/Service/MessageTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Message::class, $message);
26 | $this->assertEquals($body, $message->getBody());
27 | $this->assertEquals($attributes, $message->getAttributes());
28 | $this->assertEquals($groupId, $message->getGroupId());
29 | $this->assertEquals($deduplicationID, $message->getDeduplicationId());
30 | }
31 |
32 | /**
33 | * Test: Getter/Setter
34 | */
35 | public function testGetterSetter()
36 | {
37 | $message = new Message('', [], '', '');
38 |
39 | $this->assertInstanceOf(Message::class, $message->setId('my-id'));
40 | $this->assertEquals('my-id', $message->getId());
41 | $this->assertInstanceOf(Message::class, $message->setBody('my-body'));
42 | $this->assertEquals('my-body', $message->getBody());
43 | $this->assertInstanceOf(Message::class, $message->setAttributes(['a', 'b', 'c']));
44 | $this->assertEquals(['a', 'b', 'c'], $message->getAttributes());
45 | $this->assertInstanceOf(Message::class, $message->setReceiptHandle('my-receipt'));
46 | $this->assertEquals('my-receipt', $message->getReceiptHandle());
47 | $this->assertInstanceOf(Message::class, $message->setGroupId('group-id'));
48 | $this->assertEquals('group-id', $message->getGroupId());
49 | $this->assertInstanceOf(Message::class, $message->setDeduplicationId('deduplication-id'));
50 | $this->assertEquals('deduplication-id', $message->getDeduplicationId());
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Tests/Unit/Service/QueueFactoryTest.php:
--------------------------------------------------------------------------------
1 | getMockBuilder(SqsClient::class)
24 | ->disableOriginalConstructor()
25 | ->getMock();
26 |
27 | return $client;
28 | }
29 |
30 | /**
31 | * Test: Construction
32 | */
33 | public function testConstruction()
34 | {
35 | $client = $this->getAwsClient();
36 | $factory = new QueueFactory($client);
37 |
38 | $this->assertInstanceOf(QueueFactory::class, $factory);
39 | $this->assertEquals($client, $factory->getClient());
40 | }
41 |
42 | /**
43 | * Test: Create a new queue via Factory
44 | */
45 | public function testCreate()
46 | {
47 | $client = $this->getAwsClient();
48 | $worker = new BasicWorker();
49 | $factory = new QueueFactory($client);
50 |
51 | $queue = $factory->create('queue-name', 'queue-url', $worker);
52 | $this->assertInstanceOf(BaseQueue::class, $queue);
53 |
54 | // Cached?
55 | $queue2 = $factory->create('queue-name', 'queue-url', $worker);
56 | $this->assertEquals($queue, $queue2);
57 | }
58 |
59 | /**
60 | * Test: Create a new queue via Factory
61 | */
62 | public function testCreateQueue()
63 | {
64 | $client = $this->getAwsClient();
65 | $this->assertInstanceOf(
66 | BaseQueue::class,
67 | QueueFactory::createQueue($client, 'queue-name', 'queue-url', new BasicWorker())
68 | );
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Tests/Unit/Service/QueueManagerTest.php:
--------------------------------------------------------------------------------
1 | getMockBuilder(Result::class)->getMock();
27 | $result->expects($this->any())
28 | ->method('count')
29 | ->willReturn(count($entries));
30 | $result->expects($this->any())
31 | ->method('get')
32 | ->withAnyParameters()
33 | ->willReturnCallback(function ($arg) use ($entries) {
34 | return $entries[$arg] ?? [];
35 | });
36 |
37 | return $result;
38 | }
39 |
40 | /**
41 | * @return \PHPUnit_Framework_MockObject_MockObject|SqsClient
42 | */
43 | private function getAwsClient()
44 | {
45 | /** @var \PHPUnit_Framework_MockObject_MockObject|SqsClient $client */
46 | $client = $this->getMockBuilder(SqsClient::class)
47 | ->disableOriginalConstructor()
48 | ->setMethods(['listQueues', 'createQueue', 'deleteQueue', 'setQueueAttributes', 'getQueueAttributes'])
49 | ->getMock();
50 |
51 | return $client;
52 | }
53 |
54 | /**
55 | * Test Construction
56 | */
57 | public function testConstruction()
58 | {
59 | $client = $this->getAwsClient();
60 | $manager = new QueueManager($client);
61 |
62 | $this->assertInstanceOf(QueueManager::class, $manager);
63 | $this->assertEquals($client, $manager->getClient());
64 | }
65 |
66 | /**
67 | * Test: get the list of queue
68 | */
69 | public function testListQueue()
70 | {
71 | $expected = ['queue-url-1', 'queue-url-2'];
72 |
73 | $client = $this->getAwsClient();
74 | $client->expects($this->any())
75 | ->method('listQueues')
76 | ->with(['QueueNamePrefix' => 'queue-prefix'])
77 | ->willReturn($this->getAwsResult(['QueueUrls' => $expected]));
78 |
79 | $manager = new QueueManager($client);
80 | $this->assertEquals($expected, $manager->listQueue('queue-prefix'));
81 | }
82 |
83 | /**
84 | * Test: get the list of queue in failure
85 | */
86 | public function testListQueueFailure()
87 | {
88 | $client = $this->getAwsClient();
89 | $client->expects($this->any())
90 | ->method('listQueues')
91 | ->with(['QueueNamePrefix' => 'bad-queue-prefix'])
92 | ->willThrowException(new AwsException(
93 | 'AWS Client Exception',
94 | new Command('list-queue-command')
95 | ));
96 |
97 | $manager = new QueueManager($client);
98 | $this->expectException(\InvalidArgumentException::class);
99 | $manager->listQueue('bad-queue-prefix');
100 | }
101 |
102 | /**
103 | * Test: try to create a new queue
104 | */
105 | public function testCreateQueue()
106 | {
107 | $client = $this->getAwsClient();
108 | $client->expects($this->any())
109 | ->method('createQueue')
110 | ->with([
111 | 'Attributes' => QueueManager::QUEUE_ATTR_DEFAULT,
112 | 'QueueName' => 'queue-url'
113 | ])
114 | ->willReturn($this->getAwsResult(['QueueUrl' => 'queue-url']));
115 |
116 | $manager = new QueueManager($client);
117 | $this->assertEquals('queue-url', $manager->createQueue('queue-url', QueueManager::QUEUE_ATTR_DEFAULT));
118 | }
119 |
120 | /**
121 | * Test: try to create a new fifo queue
122 | */
123 | public function testCreateFifoQueue()
124 | {
125 | $queueAttr = array_merge(QueueManager::QUEUE_ATTR_DEFAULT, QueueManager::QUEUE_FIFO_ATTR_DEFAULT);
126 | $queueName = 'queue-url.fifo';
127 |
128 | $client = $this->getAwsClient();
129 | $client->expects($this->any())
130 | ->method('createQueue')
131 | ->with([
132 | 'Attributes' => $queueAttr,
133 | 'QueueName' => $queueName
134 | ])
135 | ->willReturn($this->getAwsResult(['QueueUrl' => 'queue-url']));
136 |
137 | $manager = new QueueManager($client);
138 | $this->assertEquals('queue-url', $manager->createQueue($queueName, $queueAttr));
139 | }
140 |
141 | /**
142 | * Test: try to create a new queue in failure
143 | */
144 | public function testCreateFailure()
145 | {
146 | $client = $this->getAwsClient();
147 | $client->expects($this->any())
148 | ->method('createQueue')
149 | ->with([
150 | 'Attributes' => QueueManager::QUEUE_ATTR_DEFAULT,
151 | 'QueueName' => 'bad-queue-url'
152 | ])
153 | ->willThrowException(new AwsException(
154 | 'AWS Client Exception',
155 | new Command('create-queue-command')
156 | ));
157 |
158 | $manager = new QueueManager($client);
159 | $this->expectException(\InvalidArgumentException::class);
160 | $manager->createQueue('bad-queue-url', QueueManager::QUEUE_ATTR_DEFAULT);
161 | }
162 |
163 | /**
164 | * Test: try to delete a new queue
165 | */
166 | public function testDeleteQueue()
167 | {
168 | $client = $this->getAwsClient();
169 | $client->expects($this->any())
170 | ->method('deleteQueue')
171 | ->with(['QueueUrl' => 'queue-url'])
172 | ->willReturn($this->getAwsResult([]));
173 |
174 | $manager = new QueueManager($client);
175 | $this->assertTrue($manager->deleteQueue('queue-url'));
176 | }
177 |
178 | /**
179 | * Test: try to delete a new queue in failure
180 | */
181 | public function testDeleteFailure()
182 | {
183 | $client = $this->getAwsClient();
184 | $client->expects($this->any())
185 | ->method('deleteQueue')
186 | ->with(['QueueUrl' => 'bad-queue-url'])
187 | ->willThrowException(new AwsException(
188 | 'AWS Client Exception',
189 | new Command('delete-queue-command')
190 | ));
191 |
192 | $manager = new QueueManager($client);
193 | $this->expectException(\InvalidArgumentException::class);
194 | $manager->deleteQueue('bad-queue-url');
195 | }
196 |
197 | /**
198 | * Test: Queue Attributes Setter/Getter
199 | */
200 | public function testQueueAttributesSetterGetter()
201 | {
202 | $attributes = [
203 | 'DelaySeconds' => rand(0, 100),
204 | 'MaximumMessageSize' => rand(0, 100),
205 | 'MessageRetentionPeriod' => rand(0, 100),
206 | 'ReceiveMessageWaitTimeSeconds' => rand(0, 100),
207 | 'VisibilityTimeout' => rand(0, 100)
208 | ];
209 |
210 | $client = $this->getAwsClient();
211 | $client->expects($this->any())
212 | ->method('setQueueAttributes')
213 | ->with([
214 | 'Attributes' => $attributes,
215 | 'QueueUrl' => 'queue-url'
216 | ])
217 | ->willReturn($this->getAwsResult([]));
218 |
219 | $client->expects($this->any())
220 | ->method('getQueueAttributes')
221 | ->with([
222 | 'AttributeNames' => ['All'],
223 | 'QueueUrl' => 'queue-url'
224 | ])
225 | ->willReturn($this->getAwsResult(['Attributes' => $attributes]));
226 |
227 | $manager = new QueueManager($client);
228 | $this->assertTrue($manager->setQueueAttributes('queue-url', $attributes));
229 | $this->assertEquals($attributes, $manager->getQueueAttributes('queue-url'));
230 | }
231 |
232 | /**
233 | * Test: Queue Attributes Setter in failure
234 | */
235 | public function testQueueAttributesSetterFailure()
236 | {
237 | $client = $this->getAwsClient();
238 | $client->expects($this->any())
239 | ->method('setQueueAttributes')
240 | ->withAnyParameters()
241 | ->willThrowException(new AwsException(
242 | 'AWS Client Exception',
243 | new Command('delete-queue-command')
244 | ));
245 |
246 | $manager = new QueueManager($client);
247 | $this->expectException(\InvalidArgumentException::class);
248 | $manager->setQueueAttributes('bad-queue-url', []);
249 | }
250 |
251 | /**
252 | * Test: Queue Attributes Getter in failure
253 | */
254 | public function testQueueAttributesGetterFailure()
255 | {
256 | $client = $this->getAwsClient();
257 | $client->expects($this->any())
258 | ->method('getQueueAttributes')
259 | ->withAnyParameters()
260 | ->willThrowException(new AwsException(
261 | 'AWS Client Exception',
262 | new Command('delete-queue-command')
263 | ));
264 |
265 | $manager = new QueueManager($client);
266 | $this->expectException(\InvalidArgumentException::class);
267 | $manager->getQueueAttributes('bad-queue-url');
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/Tests/Unit/Service/Worker/AbstractWorkerTest.php:
--------------------------------------------------------------------------------
1 | getMockBuilder(AbstractWorker::class)
22 | ->setMethods(['preExecute', 'postExecute', 'onSucceeded', 'onFailed'])
23 | ->getMockForAbstractClass();
24 |
25 | return $worker;
26 | }
27 |
28 | /**
29 | * Test: Main processing of each worker with a ping pong message
30 | */
31 | public function testProcessWithPingPong()
32 | {
33 | $worker = $this->getAbstractWorker();
34 |
35 | $message = (new Message())->setBody('ping');
36 | $expect = 'Pong. Now is ' . (new \DateTime('now'))->format('M d, Y H:i:s') . PHP_EOL;
37 |
38 | $this->expectOutputString($expect);
39 | $result = $worker->process($message);
40 | $this->assertTrue($result);
41 | }
42 |
43 | /**
44 | * Test: Main processing of each worker with a ping pong message
45 | */
46 | public function testProcessWithNormalMessage()
47 | {
48 | $message = (new Message())->setBody('my-message');
49 |
50 | $worker = $this->getAbstractWorker();
51 | $worker->expects($this->once())
52 | ->method('execute')
53 | ->with($message)
54 | ->willReturn(true);
55 | $worker->expects($this->once())->method(('preExecute'))->with($message);
56 | $worker->expects($this->once())->method(('postExecute'))->with($message);
57 | $worker->expects($this->once())->method('onSucceeded');
58 | $worker->expects($this->never())->method('onFailed');
59 |
60 | $result = $worker->process($message);
61 |
62 | $this->assertTrue($result);
63 | $this->assertFalse($worker->hasError());
64 | $this->assertNull($worker->error());
65 | }
66 |
67 | /**
68 | * Test: Main processing of each worker in failure
69 | */
70 | public function testProcessInFailure()
71 | {
72 | $message = (new Message())->setBody('my-message');
73 |
74 | $worker = $this->getAbstractWorker();
75 | $worker->expects($this->once())
76 | ->method('execute')
77 | ->with($message)
78 | ->willThrowException(new \Exception());
79 | $worker->expects($this->once())->method(('preExecute'))->with($message);
80 | $worker->expects($this->once())->method(('postExecute'))->with($message);
81 | $worker->expects($this->once())->method('onFailed');
82 | $worker->expects($this->never())->method('onSucceeded');
83 |
84 | $result = $worker->process($message);
85 |
86 | $this->assertFalse($result);
87 | $this->assertTrue($worker->hasError());
88 | $this->assertNotNull($worker->error());
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Tests/app/AppKernel.php:
--------------------------------------------------------------------------------
1 | load(__DIR__ . '/config/config_' . $this->getEnvironment() . '.yml');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Tests/app/KernelTestCase.php:
--------------------------------------------------------------------------------
1 | cont = $container;
30 | }
31 |
32 | /**
33 | * Returns service container.
34 | *
35 | * @param bool $reinitialize Force kernel reinitialization.
36 | * @param array $kernelOptions Options used passed to kernel if it needs to be initialized.
37 | *
38 | * @return ContainerInterface
39 | */
40 | protected function getContainer($reinitialize = false, array $kernelOptions = [])
41 | {
42 | if ($this->cont === null || $reinitialize) {
43 | static::bootKernel($kernelOptions);
44 | $this->cont = static::$kernel->getContainer();
45 | }
46 |
47 | return $this->cont;
48 | }
49 |
50 | /**
51 | * {@inheritdoc}
52 | */
53 | protected static function createKernel(array $options = [])
54 | {
55 | $kernel = new AppKernel('test', true);
56 | $kernel->boot();
57 |
58 | return $kernel;
59 | }
60 |
61 | /**
62 | * @param Command $command
63 | *
64 | * @return CommandTester
65 | */
66 | public function createCommandTester(Command $command)
67 | {
68 | $application = new Application(static::$kernel);
69 | $application->add($command);
70 |
71 | $commandTester = new CommandTester($application->find($command->getName()));
72 |
73 | return $commandTester;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Tests/app/Worker/BasicWorker.php:
--------------------------------------------------------------------------------
1 | addCompilerPass(new SQSQueuePass());
24 | }
25 |
26 | /**
27 | * @inheritdoc
28 | */
29 | public function getContainerExtension()
30 | {
31 | if (null === $this->extension) {
32 | $this->extension = new TriTranSqsQueueExtension();
33 | }
34 |
35 | return $this->extension;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tritran/sqs-queue-bundle",
3 | "description": "Simple SQS Queue for Symfony",
4 | "type": "symfony-bundle",
5 | "homepage": "http://www.ideason.vn",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Tri D. Tran",
10 | "homepage": "http://www.ideason.vn"
11 | }
12 | ],
13 | "require": {
14 | "php": ">=7.0.0",
15 | "ext-json": "*",
16 | "symfony/dependency-injection": "~2.7|~3.3|~4.0",
17 | "symfony/config": "~2.8|~3.3|~4.0",
18 | "symfony/console": "~2.7|~3.3|~4.0",
19 | "aws/aws-sdk-php-symfony": "~2.0"
20 | },
21 | "require-dev": {
22 | "symfony/framework-bundle": "~2.3|~3.0|~4.0",
23 | "symfony/yaml": "~2.3|~3.0|~4.0",
24 | "phpunit/phpunit": "6.0.*",
25 | "phpmd/phpmd": "2.6.*",
26 | "squizlabs/php_codesniffer": "^3.0"
27 | },
28 | "suggest": {
29 | },
30 | "autoload": {
31 | "psr-4": {
32 | "TriTran\\SqsQueueBundle\\": ""
33 | },
34 | "exclude-from-classmap": [
35 | "/Tests/app/cache/",
36 | "/Tests/app/logs/"
37 | ]
38 | },
39 | "autoload-dev": {
40 | "psr-4": {
41 | "TriTran\\SqsQueueBundle\\": "Tests/"
42 | },
43 | "classmap": [
44 | "Tests/app"
45 | ]
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
16 | ./Tests/Unit/
17 |
18 |
19 | ./Tests/Functional/
20 |
21 |
22 | ./Tests/
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ./
33 |
34 | ./Tests
35 | ./vendor
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------