├── .gitignore
├── easy-coding-standard.yml
├── phpspec.yml.dist
├── src
├── Denormalizer
│ ├── DenormalizationFailedException.php
│ ├── DenormalizerInterface.php
│ └── CompositeDenormalizer.php
├── Bus
│ ├── MessageBusInterface.php
│ └── SimpleBus.php
├── RabbitMqSimpleBusBundle.php
├── DependencyInjection
│ ├── Configuration.php
│ ├── RabbitMqSimpleBusExtension.php
│ └── Compiler
│ │ └── RegisterDenormalizersPass.php
├── Resources
│ └── config
│ │ └── services.xml
└── Consumer
│ └── RabbitMqConsumer.php
├── phpunit.xml.dist
├── .travis.yml
├── spec
├── Bus
│ └── SimpleBusSpec.php
├── Denormalizer
│ └── CompositeDenormalizerSpec.php
└── Consumer
│ └── RabbitMqConsumerSpec.php
├── composer.json
├── LICENSE
├── tests
└── DependencyInjection
│ └── Compiler
│ └── RegisterDenormalizersPassTest.php
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 |
3 | /composer.lock
4 |
--------------------------------------------------------------------------------
/easy-coding-standard.yml:
--------------------------------------------------------------------------------
1 | includes:
2 | - { resource: 'vendor/sylius-labs/coding-standard/easy-coding-standard.yml' }
3 |
--------------------------------------------------------------------------------
/phpspec.yml.dist:
--------------------------------------------------------------------------------
1 | suites:
2 | main:
3 | namespace: SyliusLabs\RabbitMqSimpleBusBundle
4 | psr4_prefix: SyliusLabs\RabbitMqSimpleBusBundle
5 |
--------------------------------------------------------------------------------
/src/Denormalizer/DenormalizationFailedException.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 | tests
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/RabbitMqSimpleBusBundle.php:
--------------------------------------------------------------------------------
1 | addCompilerPass(new RegisterDenormalizersPass());
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Denormalizer/DenormalizerInterface.php:
--------------------------------------------------------------------------------
1 | root('rabbitmq_simplebus');
19 |
20 | return $treeBuilder;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Bus/SimpleBus.php:
--------------------------------------------------------------------------------
1 | messageBus = $messageBus;
22 | }
23 |
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function handle($message): void
28 | {
29 | $this->messageBus->handle($message);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 7.1
5 | - 7.2
6 |
7 | env:
8 | matrix:
9 | - SYMFONY_VERSION="3.4.*"
10 | - SYMFONY_VERSION="4.0.*"
11 | - SYMFONY_VERSION="4.1.*"
12 |
13 | cache:
14 | directories:
15 | - ~/.composer/cache/files
16 |
17 | before_install:
18 | - phpenv config-rm xdebug.ini || true
19 | - echo "memory_limit=4096M" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
20 |
21 | install:
22 | - composer require "symfony/symfony:${SYMFONY_VERSION}" --no-interaction --no-update
23 | - composer update --no-interaction --prefer-dist
24 |
25 | script:
26 | - composer validate --strict
27 |
28 | - vendor/bin/phpspec run --format dot -vvv --no-interaction
29 | - vendor/bin/phpunit
30 |
--------------------------------------------------------------------------------
/spec/Bus/SimpleBusSpec.php:
--------------------------------------------------------------------------------
1 | beConstructedWith($messageBus);
16 | }
17 |
18 | function it_is_a_message_bus(): void
19 | {
20 | $this->shouldImplement(MessageBusInterface::class);
21 | }
22 |
23 | function it_delegates_handling_messages_to_simple_bus(MessageBus $messageBus): void
24 | {
25 | $message = new \stdClass();
26 |
27 | $messageBus->handle($message)->shouldBeCalled();
28 |
29 | $this->handle($message);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/DependencyInjection/RabbitMqSimpleBusExtension.php:
--------------------------------------------------------------------------------
1 | processConfiguration($this->getConfiguration([], $container), $config);
20 | $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
21 |
22 | $loader->load('services.xml');
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sylius-labs/rabbitmq-simplebus-bundle",
3 | "type": "symfony-bundle",
4 | "description": "Integrates RabbitMQ with SimpleBus.",
5 | "license": "MIT",
6 | "require": {
7 | "php": "^7.1",
8 | "php-amqplib/rabbitmq-bundle": "^1.12",
9 | "simple-bus/message-bus": "^3.0",
10 | "simple-bus/symfony-bridge": "^5.1",
11 | "symfony/monolog-bundle": "^3.1"
12 | },
13 | "require-dev": {
14 | "matthiasnoback/symfony-dependency-injection-test": "^3.0",
15 | "phpspec/phpspec": "^5.0",
16 | "phpunit/phpunit": "^7.0",
17 | "sylius-labs/coding-standard": "^2.0"
18 | },
19 | "autoload": {
20 | "psr-4": {
21 | "SyliusLabs\\RabbitMqSimpleBusBundle\\": "src/"
22 | }
23 | },
24 | "autoload-dev": {
25 | "psr-4": {
26 | "spec\\SyliusLabs\\RabbitMqSimpleBusBundle\\": "spec/",
27 | "Tests\\SyliusLabs\\RabbitMqSimpleBusBundle\\": "tests/"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/DependencyInjection/Compiler/RegisterDenormalizersPass.php:
--------------------------------------------------------------------------------
1 | hasDefinition('rabbitmq_simplebus.amqp_denormalizer')) {
19 | return;
20 | }
21 |
22 | $denormalizer = $container->findDefinition('rabbitmq_simplebus.amqp_denormalizer');
23 | foreach ($container->findTaggedServiceIds('rabbitmq_simplebus.amqp_denormalizer') as $id => $attributes) {
24 | $denormalizer->addMethodCall('addDenormalizer', [new Reference($id)]);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Resources/config/services.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 Kamil Kokot
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/src/Denormalizer/CompositeDenormalizer.php:
--------------------------------------------------------------------------------
1 | denormalizers[] = $denormalizer;
22 | }
23 |
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function supports(AMQPMessage $message): bool
28 | {
29 | foreach ($this->denormalizers as $denormalizer) {
30 | if ($denormalizer->supports($message)) {
31 | return true;
32 | }
33 | }
34 |
35 | return false;
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | */
41 | public function denormalize(AMQPMessage $message)
42 | {
43 | foreach ($this->denormalizers as $denormalizer) {
44 | if ($denormalizer->supports($message)) {
45 | return $denormalizer->denormalize($message);
46 | }
47 | }
48 |
49 | throw new DenormalizationFailedException('There is no denormalizer supporting this kind of message!');
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/DependencyInjection/Compiler/RegisterDenormalizersPassTest.php:
--------------------------------------------------------------------------------
1 | addCompilerPass(new RegisterDenormalizersPass());
21 | }
22 |
23 | /**
24 | * @test
25 | */
26 | public function it_registers_tagged_denormalizers_in_a_composite_one(): void
27 | {
28 | $compositeDenormalizer = new Definition();
29 | $this->setDefinition('rabbitmq_simplebus.amqp_denormalizer', $compositeDenormalizer);
30 |
31 | $taggedDenormalizer = new Definition();
32 | $taggedDenormalizer->addTag('rabbitmq_simplebus.amqp_denormalizer');
33 | $this->setDefinition('acme.amqp_denormalizer', $taggedDenormalizer);
34 |
35 | $this->compile();
36 |
37 | $this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
38 | 'rabbitmq_simplebus.amqp_denormalizer',
39 | 'addDenormalizer',
40 | [new Reference('acme.amqp_denormalizer')]
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Consumer/RabbitMqConsumer.php:
--------------------------------------------------------------------------------
1 | denormalizer = $denormalizer;
41 | $this->messageBus = $messageBus;
42 | $this->logger = $logger;
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | public function execute(AMQPMessage $message): void
49 | {
50 | try {
51 | $denormalizedMessage = $this->denormalizer->denormalize($message);
52 |
53 | $this->messageBus->handle($denormalizedMessage);
54 | } catch (\Throwable $throwable) {
55 | $this->logger->error(sprintf(
56 | 'Exception "%s" while handling an AMQP message: "%s". Stacktrace: %s',
57 | $throwable->getMessage(),
58 | $message->getBody(),
59 | $throwable->getTraceAsString()
60 | ));
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/spec/Denormalizer/CompositeDenormalizerSpec.php:
--------------------------------------------------------------------------------
1 | shouldImplement(DenormalizerInterface::class);
17 | }
18 |
19 | function it_does_not_support_a_message_if_there_are_no_denormalizers(): void
20 | {
21 | $amqpMessage = new AMQPMessage('Message body');
22 |
23 | $this->supports($amqpMessage)->shouldReturn(false);
24 | $this->shouldThrow(DenormalizationFailedException::class)->during('denormalize', [$amqpMessage]);
25 | }
26 |
27 | function it_supports_a_message_if_at_least_one_of_denormalizers_support_it(
28 | DenormalizerInterface $firstDenormalizer,
29 | DenormalizerInterface $secondDenormalizer
30 | ): void {
31 | $amqpMessage = new AMQPMessage('Message body');
32 | $denormalizedMessage = new \stdClass();
33 |
34 | $firstDenormalizer->supports($amqpMessage)->willReturn(false);
35 | $firstDenormalizer->denormalize($amqpMessage)->shouldNotBeCalled();
36 | $secondDenormalizer->supports($amqpMessage)->willReturn(true);
37 | $secondDenormalizer->denormalize($amqpMessage)->willReturn($denormalizedMessage);
38 |
39 | $this->addDenormalizer($firstDenormalizer);
40 | $this->addDenormalizer($secondDenormalizer);
41 |
42 | $this->supports($amqpMessage)->shouldReturn(true);
43 | $this->denormalize($amqpMessage)->shouldReturn($denormalizedMessage);
44 | }
45 |
46 | function it_does_not_support_a_message_if_none_of_the_denormalizers_support_it(
47 | DenormalizerInterface $firstDenormalizer,
48 | DenormalizerInterface $secondDenormalizer
49 | ): void {
50 | $amqpMessage = new AMQPMessage('Message body');
51 |
52 | $firstDenormalizer->supports($amqpMessage)->willReturn(false);
53 | $firstDenormalizer->denormalize($amqpMessage)->shouldNotBeCalled();
54 | $secondDenormalizer->supports($amqpMessage)->willReturn(false);
55 | $secondDenormalizer->denormalize($amqpMessage)->shouldNotBeCalled();
56 |
57 | $this->addDenormalizer($firstDenormalizer);
58 | $this->addDenormalizer($secondDenormalizer);
59 |
60 | $this->supports($amqpMessage)->shouldReturn(false);
61 | $this->shouldThrow(DenormalizationFailedException::class)->during('denormalize', [$amqpMessage]);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | RabbitMqSimpleBusBundle
2 | =======================
3 |
4 | Transforms AMQP messages received from RabbitMQ to event handled by SimpleBus.
5 |
6 | Installation
7 | ------------
8 |
9 | 1. Require this package:
10 |
11 | ```bash
12 | $ composer require sylius-labs/rabbitmq-simplebus-bundle
13 | ```
14 |
15 | 2. Add bundle to `AppKernel.php`:
16 |
17 | ```php
18 | public function registerBundles()
19 | {
20 | $bundles = [
21 | new \SyliusLabs\RabbitMqSimpleBusBundle\RabbitMqSimpleBusBundle(),
22 | ];
23 |
24 | return array_merge(parent::registerBundles(), $bundles);
25 | }
26 | ```
27 |
28 | Usage
29 | -----
30 |
31 | 1. Create your custom AMQP messages denormalizer:
32 |
33 | ```php
34 | // src/Acme/CustomDenormalizer.php
35 |
36 | namespace Acme;
37 |
38 | use PhpAmqpLib\Message\AMQPMessage;
39 | use SyliusLabs\RabbitMqSimpleBusBundle\Denormalizer\DenormalizationFailedException;
40 | use SyliusLabs\RabbitMqSimpleBusBundle\Denormalizer\DenormalizerInterface;
41 |
42 | class CustomDenormalizer implements DenormalizerInterface
43 | {
44 | public function supports(AMQPMessage $message)
45 | {
46 | return null !== json_decode($message->getBody(), true);
47 | }
48 |
49 | public function denormalize(AMQPMessage $message)
50 | {
51 | if (!$this->supports($message)) {
52 | throw new DenormalizationFailedException('Unsupported message!');
53 | }
54 |
55 | return new CustomEvent(json_decode($message->getBody(), true));
56 | }
57 | }
58 | ```
59 |
60 | 2. Tag your denormalizer service with `rabbitmq_simplebus.amqp_denormalizer`:
61 |
62 | ```xml
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | ```
74 |
75 | ```yaml
76 | # app/config/services.yml
77 |
78 | services:
79 | acme.custom_denormalizer:
80 | class: Acme\CustomDenormalizer
81 | tags:
82 | - { name: rabbitmq_simplebus.amqp_denormalizer }
83 | ```
84 |
85 | 3. [Configure RabbitMQ consumer](https://github.com/php-amqplib/RabbitMqBundle#usage):
86 |
87 | ```yaml
88 | # app/config/config.yml
89 |
90 | old_sound_rabbit_mq:
91 | connections:
92 | default:
93 | host: 'localhost'
94 | port: 5672
95 | user: 'guest'
96 | password: 'guest'
97 | consumers:
98 | rabbitmq_simplebus:
99 | connection: default
100 | exchange_options: { name: 'rabbitmq-simplebus', type: direct }
101 | queue_options: { name: 'rabbitmq-simplebus' }
102 | callback: rabbitmq_simplebus.consumer
103 | ```
104 |
105 | 4. Run RabbitMQ consumer:
106 |
107 | ```bash
108 | $ bin/console rabbitmq:consumer rabbitmq_simplebus
109 | ```
110 |
--------------------------------------------------------------------------------
/spec/Consumer/RabbitMqConsumerSpec.php:
--------------------------------------------------------------------------------
1 | beConstructedWith($denormalizer, $messageBus, $logger);
21 | }
22 |
23 | function it_is_a_oldsound_rabbitmq_bundle_consumer(): void
24 | {
25 | $this->shouldImplement(ConsumerInterface::class);
26 | }
27 |
28 | function it_uses_message_bus_to_dispatch_denormalized_message(
29 | DenormalizerInterface $denormalizer,
30 | MessageBusInterface $messageBus
31 | ): void {
32 | $amqpMessage = new AMQPMessage('Message body');
33 | $denormalizedMessage = new \stdClass();
34 |
35 | $denormalizer->denormalize($amqpMessage)->willReturn($denormalizedMessage);
36 |
37 | $messageBus->handle($denormalizedMessage)->shouldBeCalled();
38 |
39 | $this->execute($amqpMessage);
40 | }
41 |
42 | function it_logs_exception_message_if_denormalization_fails(
43 | DenormalizerInterface $denormalizer,
44 | LoggerInterface $logger
45 | ): void {
46 | $amqpMessage = new AMQPMessage('Invalid message body');
47 |
48 | $denormalizer->denormalize($amqpMessage)->willThrow(new DenormalizationFailedException('Message body is invalid'));
49 |
50 | $logger->error(Argument::containingString('Message body is invalid'))->shouldBeCalled();
51 | $logger->error(Argument::containingString('Invalid message body'))->shouldBeCalled();
52 |
53 | $this->execute($amqpMessage);
54 | }
55 |
56 | function it_logs_any_error_that_happened_during_denormalization(
57 | DenormalizerInterface $denormalizer,
58 | LoggerInterface $logger
59 | ): void {
60 | $amqpMessage = new AMQPMessage('Invalid message body');
61 |
62 | $denormalizer->denormalize($amqpMessage)->will(function () {
63 | /** @noinspection PhpUndefinedVariableInspection */
64 | return $undefinedVariable;
65 | });
66 |
67 | $logger->error(Argument::containingString('notice: Undefined variable: undefinedVariable'))->shouldBeCalled();
68 | $logger->error(Argument::containingString('Invalid message body'))->shouldBeCalled();
69 |
70 | $this->execute($amqpMessage);
71 | }
72 |
73 | function it_logs_any_error_that_happened_during_handling_denormalized_message(
74 | DenormalizerInterface $denormalizer,
75 | MessageBusInterface $messageBus,
76 | LoggerInterface $logger
77 | ): void {
78 | $amqpMessage = new AMQPMessage('Invalid message body');
79 | $denormalizedMessage = new \stdClass();
80 |
81 | $denormalizer->denormalize($amqpMessage)->willReturn($denormalizedMessage);
82 |
83 | $messageBus->handle($denormalizedMessage)->will(function () {
84 | /** @noinspection PhpUndefinedVariableInspection */
85 | return $undefinedVariable;
86 | });
87 |
88 | $logger->error(Argument::containingString('notice: Undefined variable: undefinedVariable'))->shouldBeCalled();
89 | $logger->error(Argument::containingString('Invalid message body'))->shouldBeCalled();
90 |
91 | $this->execute($amqpMessage);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------