├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── easy-coding-standard.yml ├── phpspec.yml.dist ├── phpunit.xml.dist ├── spec ├── Bus │ └── SimpleBusSpec.php ├── Consumer │ └── RabbitMqConsumerSpec.php └── Denormalizer │ └── CompositeDenormalizerSpec.php ├── src ├── Bus │ ├── MessageBusInterface.php │ └── SimpleBus.php ├── Consumer │ └── RabbitMqConsumer.php ├── Denormalizer │ ├── CompositeDenormalizer.php │ ├── DenormalizationFailedException.php │ └── DenormalizerInterface.php ├── DependencyInjection │ ├── Compiler │ │ └── RegisterDenormalizersPass.php │ ├── Configuration.php │ └── RabbitMqSimpleBusExtension.php ├── RabbitMqSimpleBusBundle.php └── Resources │ └── config │ └── services.xml └── tests └── DependencyInjection └── Compiler └── RegisterDenormalizersPassTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | 3 | /composer.lock 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | tests 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/Bus/MessageBusInterface.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/Denormalizer/DenormalizationFailedException.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/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | root('rabbitmq_simplebus'); 19 | 20 | return $treeBuilder; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/RabbitMqSimpleBusBundle.php: -------------------------------------------------------------------------------- 1 | addCompilerPass(new RegisterDenormalizersPass()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Resources/config/services.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------