├── Commands.php ├── PhpSerializerEventTransformer.php ├── Registry.php ├── AsyncEventDispatcher.php ├── AbstractPhpSerializerEventTransformer.php ├── .github └── workflows │ └── ci.yml ├── EventTransformer.php ├── DependencyInjection ├── Configuration.php ├── AsyncEventDispatcherExtension.php ├── AsyncTransformersPass.php └── AsyncEventsPass.php ├── AsyncListener.php ├── LICENSE ├── Resources └── config │ └── services.yml ├── AbstractAsyncListener.php ├── AbstractAsyncEventDispatcher.php ├── AsyncProcessor.php ├── composer.json ├── SimpleRegistry.php ├── README.md └── ContainerAwareRegistry.php /Commands.php: -------------------------------------------------------------------------------- 1 | context->createMessage(serialize($event)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Registry.php: -------------------------------------------------------------------------------- 1 | parentDispatch($event, $eventName); 10 | 11 | return $this->trueEventDispatcher->dispatch($event, $eventName); 12 | } 13 | 14 | protected function parentDispatch($event, $eventName) 15 | { 16 | return parent::dispatch($event, $eventName); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AbstractPhpSerializerEventTransformer.php: -------------------------------------------------------------------------------- 1 | context = $context; 18 | } 19 | 20 | public function toEvent($eventName, Message $message) 21 | { 22 | return unserialize($message->getBody()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | jobs: 8 | tests: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | php: ['7.4', '8.0', '8.1', '8.2'] 14 | 15 | name: PHP ${{ matrix.php }} tests 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - uses: shivammathur/setup-php@v2 21 | with: 22 | php-version: ${{ matrix.php }} 23 | coverage: none 24 | 25 | - uses: "ramsey/composer-install@v1" 26 | with: 27 | composer-options: "--prefer-source" 28 | 29 | - run: vendor/bin/phpunit --exclude-group=functional 30 | -------------------------------------------------------------------------------- /EventTransformer.php: -------------------------------------------------------------------------------- 1 | getRootNode(); 15 | } else { 16 | $tb = new TreeBuilder(); 17 | $rootNode = $tb->root('enqueue_async_event_dispatcher'); 18 | } 19 | 20 | $rootNode->children() 21 | ->scalarNode('context_service')->isRequired()->cannotBeEmpty()->end() 22 | ; 23 | 24 | return $tb; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /AsyncListener.php: -------------------------------------------------------------------------------- 1 | onEvent($event, $eventName); 12 | } 13 | 14 | /** 15 | * @param string $eventName 16 | */ 17 | public function onEvent(Event $event, $eventName) 18 | { 19 | if (false == isset($this->syncMode[$eventName])) { 20 | $transformerName = $this->registry->getTransformerNameForEvent($eventName); 21 | 22 | $message = $this->registry->getTransformer($transformerName)->toMessage($eventName, $event); 23 | $message->setProperty('event_name', $eventName); 24 | $message->setProperty('transformer_name', $transformerName); 25 | 26 | $this->context->createProducer()->send($this->eventQueue, $message); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Kotliar Maksym 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. -------------------------------------------------------------------------------- /Resources/config/services.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | enqueue_events_queue: 'symfony_events' 3 | 4 | services: 5 | # should be defined by the extension 6 | # enqueue.events.context: 7 | 8 | enqueue.events.registry: 9 | class: 'Enqueue\AsyncEventDispatcher\ContainerAwareRegistry' 10 | public: false 11 | arguments: [[], [], '@service_container'] 12 | 13 | enqueue.events.async_listener: 14 | class: 'Enqueue\AsyncEventDispatcher\AsyncListener' 15 | public: public 16 | arguments: ['@enqueue.events.context', '@enqueue.events.registry', '%enqueue_events_queue%'] 17 | 18 | 19 | enqueue.events.event_dispatcher: 20 | class: 'Enqueue\AsyncEventDispatcher\AsyncEventDispatcher' 21 | public: public 22 | arguments: 23 | - '@event_dispatcher' 24 | - '@enqueue.events.async_listener' 25 | 26 | enqueue.events.php_serializer_event_transformer: 27 | class: 'Enqueue\AsyncEventDispatcher\PhpSerializerEventTransformer' 28 | public: public 29 | arguments: 30 | - '@enqueue.events.context' 31 | tags: 32 | - {name: 'enqueue.event_transformer', eventName: '/.*/', transformerName: 'php_serializer', default: true } 33 | -------------------------------------------------------------------------------- /AbstractAsyncListener.php: -------------------------------------------------------------------------------- 1 | context = $context; 36 | $this->registry = $registry; 37 | $this->eventQueue = $eventQueue instanceof Queue ? $eventQueue : $context->createQueue($eventQueue); 38 | } 39 | 40 | public function resetSyncMode() 41 | { 42 | $this->syncMode = []; 43 | } 44 | 45 | /** 46 | * @param string $eventName 47 | */ 48 | public function syncMode($eventName) 49 | { 50 | $this->syncMode[$eventName] = true; 51 | } 52 | 53 | /** 54 | * @param string $eventName 55 | * 56 | * @return bool 57 | */ 58 | public function isSyncMode($eventName) 59 | { 60 | return isset($this->syncMode[$eventName]); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /AbstractAsyncEventDispatcher.php: -------------------------------------------------------------------------------- 1 | trueEventDispatcher = $trueEventDispatcher; 25 | $this->asyncListener = $asyncListener; 26 | } 27 | 28 | /** 29 | * This method dispatches only those listeners that were marked as async. 30 | * 31 | * @param string $eventName 32 | * @param ContractEvent|Event|null $event 33 | */ 34 | public function dispatchAsyncListenersOnly($eventName, $event = null) 35 | { 36 | try { 37 | $this->asyncListener->syncMode($eventName); 38 | 39 | $this->parentDispatch($event, $eventName); 40 | } finally { 41 | $this->asyncListener->resetSyncMode(); 42 | } 43 | } 44 | 45 | abstract protected function parentDispatch($event, $eventName); 46 | } 47 | -------------------------------------------------------------------------------- /AsyncProcessor.php: -------------------------------------------------------------------------------- 1 | registry = $registry; 26 | 27 | if (false == $dispatcher instanceof AsyncEventDispatcher) { 28 | throw new \InvalidArgumentException(sprintf('The dispatcher argument must be instance of "%s" but got "%s"', AsyncEventDispatcher::class, $dispatcher::class)); 29 | } 30 | 31 | $this->dispatcher = $dispatcher; 32 | } 33 | 34 | public function process(Message $message, Context $context) 35 | { 36 | if (false == $eventName = $message->getProperty('event_name')) { 37 | return Result::reject('The message is missing "event_name" property'); 38 | } 39 | if (false == $transformerName = $message->getProperty('transformer_name')) { 40 | return Result::reject('The message is missing "transformer_name" property'); 41 | } 42 | 43 | $event = $this->registry->getTransformer($transformerName)->toEvent($eventName, $message); 44 | 45 | $this->dispatcher->dispatchAsyncListenersOnly($eventName, $event); 46 | 47 | return self::ACK; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /DependencyInjection/AsyncEventDispatcherExtension.php: -------------------------------------------------------------------------------- 1 | processConfiguration(new Configuration(), $configs); 19 | 20 | $container->setAlias('enqueue.events.context', new Alias($config['context_service'], true)); 21 | 22 | $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 23 | $loader->load('services.yml'); 24 | 25 | $container->register('enqueue.events.async_processor', AsyncProcessor::class) 26 | ->addArgument(new Reference('enqueue.events.registry')) 27 | ->addArgument(new Reference('enqueue.events.event_dispatcher')) 28 | ->addTag('enqueue.processor', [ 29 | 'command' => Commands::DISPATCH_ASYNC_EVENTS, 30 | 'queue' => '%enqueue_events_queue%', 31 | 'prefix_queue' => false, 32 | 'exclusive' => true, 33 | ]) 34 | ->addTag('enqueue.transport.processor') 35 | ; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "enqueue/async-event-dispatcher", 3 | "type": "library", 4 | "description": "Symfony async event dispatcher", 5 | "keywords": ["messaging", "queue", "async event", "event dispatcher"], 6 | "homepage": "https://enqueue.forma-pro.com/", 7 | "license": "MIT", 8 | "require": { 9 | "php": "^8.1", 10 | "enqueue/enqueue": "^0.10", 11 | "queue-interop/queue-interop": "^0.8", 12 | "symfony/event-dispatcher": "^5.4|^6.0" 13 | }, 14 | "require-dev": { 15 | "phpunit/phpunit": "^9.5", 16 | "symfony/dependency-injection": "^5.4|^6.0", 17 | "symfony/config": "^5.4|^6.0", 18 | "symfony/http-kernel": "^5.4|^6.0", 19 | "symfony/filesystem": "^5.4|^6.0", 20 | "symfony/yaml": "^5.4|^6.0", 21 | "enqueue/null": "0.10.x-dev", 22 | "enqueue/fs": "0.10.x-dev", 23 | "enqueue/test": "0.10.x-dev" 24 | }, 25 | "support": { 26 | "email": "opensource@forma-pro.com", 27 | "issues": "https://github.com/php-enqueue/enqueue-dev/issues", 28 | "forum": "https://gitter.im/php-enqueue/Lobby", 29 | "source": "https://github.com/php-enqueue/enqueue-dev", 30 | "docs": "https://github.com/php-enqueue/enqueue-dev/blob/master/docs/index.md" 31 | }, 32 | "suggest": { 33 | "symfony/dependency-injection": "^5.4|^6.0 If you'd like to use async event dispatcher container extension." 34 | }, 35 | "autoload": { 36 | "psr-4": { "Enqueue\\AsyncEventDispatcher\\": "" }, 37 | "exclude-from-classmap": [ 38 | "/Tests/" 39 | ] 40 | }, 41 | "extra": { 42 | "branch-alias": { 43 | "dev-master": "0.10.x-dev" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /DependencyInjection/AsyncTransformersPass.php: -------------------------------------------------------------------------------- 1 | hasDefinition('enqueue.events.registry')) { 13 | return; 14 | } 15 | 16 | $transformerIdsMap = []; 17 | $eventNamesMap = []; 18 | $defaultTransformer = null; 19 | foreach ($container->findTaggedServiceIds('enqueue.event_transformer') as $serviceId => $tagAttributes) { 20 | foreach ($tagAttributes as $tagAttribute) { 21 | if (false == isset($tagAttribute['eventName'])) { 22 | throw new \LogicException('The eventName attribute must be set'); 23 | } 24 | 25 | $eventName = $tagAttribute['eventName']; 26 | 27 | $transformerName = isset($tagAttribute['transformerName']) ? $tagAttribute['transformerName'] : $serviceId; 28 | 29 | if (isset($tagAttribute['default']) && $tagAttribute['default']) { 30 | $defaultTransformer = [ 31 | 'id' => $serviceId, 32 | 'transformerName' => $transformerName, 33 | 'eventName' => $eventName, 34 | ]; 35 | } else { 36 | $eventNamesMap[$eventName] = $transformerName; 37 | $transformerIdsMap[$transformerName] = $serviceId; 38 | } 39 | } 40 | } 41 | 42 | if ($defaultTransformer) { 43 | $eventNamesMap[$defaultTransformer['eventName']] = $defaultTransformer['transformerName']; 44 | $transformerIdsMap[$defaultTransformer['transformerName']] = $defaultTransformer['id']; 45 | } 46 | 47 | $container->getDefinition('enqueue.events.registry') 48 | ->replaceArgument(0, $eventNamesMap) 49 | ->replaceArgument(1, $transformerIdsMap) 50 | ; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /SimpleRegistry.php: -------------------------------------------------------------------------------- 1 | transformerName] 19 | * @param string[] $transformersMap [transformerName => transformerObject] 20 | */ 21 | public function __construct(array $eventsMap, array $transformersMap) 22 | { 23 | $this->eventsMap = $eventsMap; 24 | $this->transformersMap = $transformersMap; 25 | } 26 | 27 | public function getTransformerNameForEvent($eventName) 28 | { 29 | $transformerName = null; 30 | if (array_key_exists($eventName, $this->eventsMap)) { 31 | $transformerName = $this->eventsMap[$eventName]; 32 | } else { 33 | foreach ($this->eventsMap as $eventNamePattern => $name) { 34 | if ('/' != $eventNamePattern[0]) { 35 | continue; 36 | } 37 | 38 | if (preg_match($eventNamePattern, $eventName)) { 39 | $transformerName = $name; 40 | 41 | break; 42 | } 43 | } 44 | } 45 | 46 | if (empty($transformerName)) { 47 | throw new \LogicException(sprintf('There is no transformer registered for the given event %s', $eventName)); 48 | } 49 | 50 | return $transformerName; 51 | } 52 | 53 | public function getTransformer($name) 54 | { 55 | if (false == array_key_exists($name, $this->transformersMap)) { 56 | throw new \LogicException(sprintf('There is no transformer named %s', $name)); 57 | } 58 | 59 | $transformer = $this->transformersMap[$name]; 60 | 61 | if (false == $transformer instanceof EventTransformer) { 62 | throw new \LogicException(sprintf('The container must return instance of %s but got %s', EventTransformer::class, is_object($transformer) ? $transformer::class : gettype($transformer))); 63 | } 64 | 65 | return $transformer; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |