From 878d2dfa73c7a8b374382c3c3eb9a89fcaf2b759 Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Thu, 25 May 2017 11:12:18 -0400 Subject: [PATCH 1/4] add auto-register compiler pass --- .../Compiler/AutoRegister.php | 58 +++++++++++++++++++ src/SimpleBusCommandBusBundle.php | 8 +++ src/SimpleBusEventBusBundle.php | 8 +++ 3 files changed, 74 insertions(+) create mode 100644 src/DependencyInjection/Compiler/AutoRegister.php diff --git a/src/DependencyInjection/Compiler/AutoRegister.php b/src/DependencyInjection/Compiler/AutoRegister.php new file mode 100644 index 0000000..ab7af1f --- /dev/null +++ b/src/DependencyInjection/Compiler/AutoRegister.php @@ -0,0 +1,58 @@ + + */ +final class AutoRegister implements CompilerPassInterface +{ + private $tagName; + private $tagAttribute; + + public function __construct($tagName, $tagAttribute) + { + $this->tagName = $tagName; + $this->tagAttribute = $tagAttribute; + } + + public function process(ContainerBuilder $container) + { + foreach ($container->findTaggedServiceIds($this->tagName) as $serviceId => $tags) { + foreach ($tags as $tagAttributes) { + + // if tag attributes are set, skip + if (isset($tagAttributes[$this->tagAttribute])) { + continue; + } + + $definition = $container->getDefinition($serviceId); + + // check if service id is class name + $reflectionClass = new \ReflectionClass($definition->getClass() ?: $serviceId); + + // if no __invoke method, skip + if (!$reflectionClass->hasMethod('__invoke')) { + continue; + } + + $invokeParameters = $reflectionClass->getMethod('__invoke')->getParameters(); + + // if no param or optional param, skip + if (count($invokeParameters) !== 1 || $invokeParameters[0]->isOptional()) { + return; + } + + // get the class name + $handles = $invokeParameters[0]->getClass()->getName(); + + // auto handle + $definition->clearTag($this->tagName); + $definition->addTag($this->tagName, [$this->tagAttribute => $handles]); + } + } + } +} diff --git a/src/SimpleBusCommandBusBundle.php b/src/SimpleBusCommandBusBundle.php index ca060b9..183fea9 100644 --- a/src/SimpleBusCommandBusBundle.php +++ b/src/SimpleBusCommandBusBundle.php @@ -3,8 +3,10 @@ namespace SimpleBus\SymfonyBridge; use SimpleBus\SymfonyBridge\DependencyInjection\CommandBusExtension; +use SimpleBus\SymfonyBridge\DependencyInjection\Compiler\AutoRegister; use SimpleBus\SymfonyBridge\DependencyInjection\Compiler\ConfigureMiddlewares; use SimpleBus\SymfonyBridge\DependencyInjection\Compiler\RegisterHandlers; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -33,6 +35,12 @@ public function build(ContainerBuilder $container) 'handles' ) ); + + $container->addCompilerPass( + new AutoRegister('command_handler', 'handles'), + PassConfig::TYPE_BEFORE_OPTIMIZATION, + 10 + ); } public function getContainerExtension() diff --git a/src/SimpleBusEventBusBundle.php b/src/SimpleBusEventBusBundle.php index 6fd6502..f999e7b 100644 --- a/src/SimpleBusEventBusBundle.php +++ b/src/SimpleBusEventBusBundle.php @@ -3,11 +3,13 @@ namespace SimpleBus\SymfonyBridge; use SimpleBus\SymfonyBridge\DependencyInjection\Compiler\AddMiddlewareTags; +use SimpleBus\SymfonyBridge\DependencyInjection\Compiler\AutoRegister; use SimpleBus\SymfonyBridge\DependencyInjection\Compiler\CompilerPassUtil; use SimpleBus\SymfonyBridge\DependencyInjection\Compiler\ConfigureMiddlewares; use SimpleBus\SymfonyBridge\DependencyInjection\Compiler\RegisterMessageRecorders; use SimpleBus\SymfonyBridge\DependencyInjection\Compiler\RegisterSubscribers; use SimpleBus\SymfonyBridge\DependencyInjection\EventBusExtension; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; @@ -56,6 +58,12 @@ public function build(ContainerBuilder $container) 200 ) ); + + $container->addCompilerPass( + new AutoRegister('event_subscriber', 'subscribes_to'), + PassConfig::TYPE_BEFORE_OPTIMIZATION, + 10 + ); } public function getContainerExtension() From a012766e43027acbbe07059ad6bb0ec96171f522 Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Wed, 14 Jun 2017 09:32:32 -0400 Subject: [PATCH 2/4] add functional tests for auto-register feature --- tests/Functional/SmokeTest.php | 51 +++++++++++++++++-- .../Functional/SmokeTest/Auto/AutoCommand.php | 7 +++ .../SmokeTest/Auto/AutoCommandHandler.php | 13 +++++ tests/Functional/SmokeTest/Auto/AutoEvent.php | 7 +++ .../SmokeTest/Auto/AutoEventSubscriber.php | 13 +++++ tests/Functional/SmokeTest/TestKernel.php | 5 +- tests/Functional/SmokeTest/config.yml | 46 ----------------- tests/Functional/SmokeTest/config1.yml | 49 ++++++++++++++++++ tests/Functional/SmokeTest/config2.yml | 13 +++++ 9 files changed, 151 insertions(+), 53 deletions(-) create mode 100644 tests/Functional/SmokeTest/Auto/AutoCommand.php create mode 100644 tests/Functional/SmokeTest/Auto/AutoCommandHandler.php create mode 100644 tests/Functional/SmokeTest/Auto/AutoEvent.php create mode 100644 tests/Functional/SmokeTest/Auto/AutoEventSubscriber.php create mode 100644 tests/Functional/SmokeTest/config1.yml create mode 100644 tests/Functional/SmokeTest/config2.yml diff --git a/tests/Functional/SmokeTest.php b/tests/Functional/SmokeTest.php index 0d0cf95..16869b6 100644 --- a/tests/Functional/SmokeTest.php +++ b/tests/Functional/SmokeTest.php @@ -4,20 +4,27 @@ use Doctrine\ORM\EntityManager; use Doctrine\ORM\Tools\SchemaTool; +use SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\Auto\AutoCommand; +use SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\Auto\AutoEvent; use SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\TestCommand; use SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\TestKernel; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\DependencyInjection\ContainerInterface; -class SmokeTest extends \PHPUnit_Framework_TestCase +class SmokeTest extends KernelTestCase { + protected static function getKernelClass() + { + return TestKernel::class; + } + /** * @test */ public function it_handles_a_command_then_dispatches_events_for_all_modified_entities() { - $kernel = new TestKernel('test', true); - $kernel->boot(); - $container = $kernel->getContainer(); + self::bootKernel(['environment' => 'config1']); + $container = self::$kernel->getContainer(); $this->createSchema($container); @@ -42,6 +49,42 @@ public function it_handles_a_command_then_dispatches_events_for_all_modified_ent $this->assertContains('event_bus.DEBUG: Finished notifying a subscriber', $loggedMessages); } + /** + * @test + */ + public function it_can_auto_register_event_subscribers() + { + self::bootKernel(['environment' => 'config2']); + $container = self::$kernel->getContainer(); + + $subscriber = $container->get('auto_event_subscriber'); + $event = new AutoEvent(); + + $this->assertNull($subscriber->handled); + + $container->get('event_bus')->handle($event); + + $this->assertSame($event, $subscriber->handled); + } + + /** + * @test + */ + public function it_can_auto_register_command_handlers() + { + self::bootKernel(['environment' => 'config2']); + $container = self::$kernel->getContainer(); + + $handler = $container->get('auto_command_handler'); + $command = new AutoCommand(); + + $this->assertNull($handler->handled); + + $container->get('command_bus')->handle($command); + + $this->assertSame($command, $handler->handled); + } + private function createSchema(ContainerInterface $container) { $entityManager = $container->get('doctrine.orm.entity_manager'); diff --git a/tests/Functional/SmokeTest/Auto/AutoCommand.php b/tests/Functional/SmokeTest/Auto/AutoCommand.php new file mode 100644 index 0000000..543e20a --- /dev/null +++ b/tests/Functional/SmokeTest/Auto/AutoCommand.php @@ -0,0 +1,7 @@ +handled = $command; + } +} diff --git a/tests/Functional/SmokeTest/Auto/AutoEvent.php b/tests/Functional/SmokeTest/Auto/AutoEvent.php new file mode 100644 index 0000000..e2aaa81 --- /dev/null +++ b/tests/Functional/SmokeTest/Auto/AutoEvent.php @@ -0,0 +1,7 @@ +handled = $event; + } +} diff --git a/tests/Functional/SmokeTest/TestKernel.php b/tests/Functional/SmokeTest/TestKernel.php index f48e2c9..e93afa5 100644 --- a/tests/Functional/SmokeTest/TestKernel.php +++ b/tests/Functional/SmokeTest/TestKernel.php @@ -18,8 +18,7 @@ public function __construct($environment, $debug) { parent::__construct($environment, $debug); - $this->tempDir = sys_get_temp_dir() . '/' . uniqid(); - mkdir($this->tempDir, 0777, true); + $this->tempDir = sys_get_temp_dir() . '/simplebus-symfony-bridge'; } public function registerBundles() @@ -35,7 +34,7 @@ public function registerBundles() public function registerContainerConfiguration(LoaderInterface $loader) { - $loader->load(__DIR__ . '/config.yml'); + $loader->load(sprintf('%s/%s.yml', __DIR__, $this->environment)); } public function getCacheDir() diff --git a/tests/Functional/SmokeTest/config.yml b/tests/Functional/SmokeTest/config.yml index fd1b3c9..18e2b77 100644 --- a/tests/Functional/SmokeTest/config.yml +++ b/tests/Functional/SmokeTest/config.yml @@ -1,38 +1,7 @@ -parameters: - log_file: %kernel.logs_dir%/%kernel.environment%.log - services: annotation_reader: class: Doctrine\Common\Annotations\AnnotationReader - test_command_handler: - class: SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\TestCommandHandler - arguments: - - '@doctrine.orm.default_entity_manager' - tags: - - { name: command_handler, handles: SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\TestCommand } - - some_other_test_command_handler: - class: SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\SomeOtherTestCommandHandler - arguments: - - '@event_recorder' - tags: - - { name: command_handler, handles: SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\SomeOtherTestCommand } - - test_event_subscriber: - class: SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\TestEntityCreatedEventSubscriber - tags: - - { name: event_subscriber, subscribes_to: test_entity_created } - arguments: - - '@command_bus' - - some_other_event_subscriber: - class: SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\SomeOtherEventSubscriber - tags: - - { name: event_subscriber, subscribes_to: some_other_event } - arguments: - - '@command_bus' - doctrine: dbal: driver: pdo_sqlite @@ -49,18 +18,3 @@ doctrine: prefix: SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\Entity alias: Test is_bundle: false - -command_bus: - command_name_resolver_strategy: class_based - logging: ~ - -event_bus: - event_name_resolver_strategy: named_message - logging: ~ - -monolog: - handlers: - main: - type: stream - path: %log_file% - level: debug diff --git a/tests/Functional/SmokeTest/config1.yml b/tests/Functional/SmokeTest/config1.yml new file mode 100644 index 0000000..5be937d --- /dev/null +++ b/tests/Functional/SmokeTest/config1.yml @@ -0,0 +1,49 @@ +imports: + - { resource: config.yml } + +parameters: + log_file: %kernel.logs_dir%/%kernel.environment%.log + +services: + test_command_handler: + class: SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\TestCommandHandler + arguments: + - '@doctrine.orm.default_entity_manager' + tags: + - { name: command_handler, handles: SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\TestCommand } + + some_other_test_command_handler: + class: SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\SomeOtherTestCommandHandler + arguments: + - '@event_recorder' + tags: + - { name: command_handler, handles: SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\SomeOtherTestCommand } + + test_event_subscriber: + class: SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\TestEntityCreatedEventSubscriber + tags: + - { name: event_subscriber, subscribes_to: test_entity_created } + arguments: + - '@command_bus' + + some_other_event_subscriber: + class: SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\SomeOtherEventSubscriber + tags: + - { name: event_subscriber, subscribes_to: some_other_event } + arguments: + - '@command_bus' + +command_bus: + command_name_resolver_strategy: class_based + logging: ~ + +event_bus: + event_name_resolver_strategy: named_message + logging: ~ + +monolog: + handlers: + main: + type: stream + path: %log_file% + level: debug diff --git a/tests/Functional/SmokeTest/config2.yml b/tests/Functional/SmokeTest/config2.yml new file mode 100644 index 0000000..ab3c75b --- /dev/null +++ b/tests/Functional/SmokeTest/config2.yml @@ -0,0 +1,13 @@ +imports: + - { resource: config.yml } + +services: + auto_command_handler: + class: SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\Auto\AutoCommandHandler + tags: + - { name: command_handler } + + auto_event_subscriber: + class: SimpleBus\SymfonyBridge\Tests\Functional\SmokeTest\Auto\AutoEventSubscriber + tags: + - { name: event_subscriber } From 425f5517957b8fed0a3e0170cfd11a5e58bb744a Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Wed, 14 Jun 2017 09:44:18 -0400 Subject: [PATCH 3/4] ensure auto-register compiler pass is added first --- src/SimpleBusCommandBusBundle.php | 12 ++++++------ src/SimpleBusEventBusBundle.php | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/SimpleBusCommandBusBundle.php b/src/SimpleBusCommandBusBundle.php index 183fea9..3232389 100644 --- a/src/SimpleBusCommandBusBundle.php +++ b/src/SimpleBusCommandBusBundle.php @@ -21,6 +21,12 @@ public function __construct($alias = 'command_bus') public function build(ContainerBuilder $container) { + $container->addCompilerPass( + new AutoRegister('command_handler', 'handles'), + PassConfig::TYPE_BEFORE_OPTIMIZATION, + 10 + ); + $container->addCompilerPass( new ConfigureMiddlewares( 'command_bus', @@ -35,12 +41,6 @@ public function build(ContainerBuilder $container) 'handles' ) ); - - $container->addCompilerPass( - new AutoRegister('command_handler', 'handles'), - PassConfig::TYPE_BEFORE_OPTIMIZATION, - 10 - ); } public function getContainerExtension() diff --git a/src/SimpleBusEventBusBundle.php b/src/SimpleBusEventBusBundle.php index f999e7b..d8b7b84 100644 --- a/src/SimpleBusEventBusBundle.php +++ b/src/SimpleBusEventBusBundle.php @@ -28,6 +28,12 @@ public function build(ContainerBuilder $container) { $this->checkRequirements(array('SimpleBusCommandBusBundle'), $container); + $container->addCompilerPass( + new AutoRegister('event_subscriber', 'subscribes_to'), + PassConfig::TYPE_BEFORE_OPTIMIZATION, + 10 + ); + $container->addCompilerPass( new ConfigureMiddlewares( 'event_bus', @@ -58,12 +64,6 @@ public function build(ContainerBuilder $container) 200 ) ); - - $container->addCompilerPass( - new AutoRegister('event_subscriber', 'subscribes_to'), - PassConfig::TYPE_BEFORE_OPTIMIZATION, - 10 - ); } public function getContainerExtension() From cad85d23b572696a79d93b38dcf538d440289e3a Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Wed, 14 Jun 2017 09:58:18 -0400 Subject: [PATCH 4/4] add auto-register documentation --- doc/command_bus_bundle.md | 8 ++++++++ doc/event_bus_bundle.md | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/doc/command_bus_bundle.md b/doc/command_bus_bundle.md index f1156a1..120c9dd 100644 --- a/doc/command_bus_bundle.md +++ b/doc/command_bus_bundle.md @@ -64,6 +64,14 @@ services: - { name: command_handler, handles: Fully\Qualified\Class\Name\Of\RegisterUser } ``` +### Auto-Register command handlers + +You can omit the `handles` tag attribute if your handler meets the following conditions: + +1. Uses the "class_based" command name resolving strategy +2. Handles a single command using the `__invoke` method +3. Has a single, non optional class type hinted `__invoke` method parameter + > #### Command handlers are lazy-loaded > > Since only one of the command handlers is going to handle any particular command, command handlers are lazy-loaded. diff --git a/doc/event_bus_bundle.md b/doc/event_bus_bundle.md index 3389b25..ddecf6c 100644 --- a/doc/event_bus_bundle.md +++ b/doc/event_bus_bundle.md @@ -65,6 +65,14 @@ services: - { name: event_subscriber, subscribes_to: Fully\Qualified\Class\Name\Of\UserRegistered } ``` +### Auto-Register event subscribers + +You can omit the `subscribes_to` tag attribute if your subscriber meets the following conditions: + +1. Uses the "class_based" event name resolving strategy +2. Subscribers to single event using the `__invoke` method +3. Has a single, non optional class type hinted `__invoke` method parameter + > #### Event subscribers are lazy-loaded > > Since only some of the event subscribers are going to handle any particular event, event subscribers are lazy-loaded.