-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from open-solid/decorators
add support for handler decorators
- Loading branch information
Showing
25 changed files
with
491 additions
and
111 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Message Bus Component | ||
|
46 changes: 46 additions & 0 deletions
46
src/Bridge/Doctrine/Decorator/DoctrineTransactionDecorator.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of OpenSolid package. | ||
* | ||
* (c) Yonel Ceruto <open@yceruto.dev> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace OpenSolid\Bus\Bridge\Doctrine\Decorator; | ||
|
||
use Doctrine\ORM\EntityManagerInterface; | ||
use Doctrine\Persistence\ManagerRegistry; | ||
use OpenSolid\Bus\Decorator\Decorator; | ||
|
||
class DoctrineTransactionDecorator implements Decorator | ||
{ | ||
private array $options = []; | ||
|
||
public function __construct( | ||
private readonly ManagerRegistry $registry, | ||
) { | ||
} | ||
|
||
public function decorate(\Closure $func): \Closure | ||
{ | ||
$manager = $this->registry->getManager($this->options['name'] ?? null); | ||
|
||
if (!$manager instanceof EntityManagerInterface) { | ||
throw new \LogicException('Doctrine ORM entity managers are only supported'); | ||
} | ||
|
||
return static function (mixed ...$args) use ($manager, $func): mixed { | ||
return $manager->wrapInTransaction(static fn (): mixed => $func(...$args)); | ||
}; | ||
} | ||
|
||
public function setOptions(array $options): void | ||
{ | ||
$this->options = $options; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of OpenSolid package. | ||
* | ||
* (c) Yonel Ceruto <open@yceruto.dev> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace OpenSolid\Bus\Bridge\Doctrine\Decorator; | ||
|
||
use OpenSolid\Bus\Decorator\Decorate; | ||
|
||
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] | ||
readonly class Transactional extends Decorate | ||
{ | ||
/** | ||
* @param string|null $entityManagerName the entity manager name (null for the default one) | ||
*/ | ||
public function __construct( | ||
?string $entityManagerName = null, | ||
) { | ||
parent::__construct(DoctrineTransactionDecorator::class, [ | ||
'name' => $entityManagerName, | ||
]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 90 additions & 0 deletions
90
src/Bridge/Symfony/DependencyInjection/CompilerPass/HandlingMiddlewarePass.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/* | ||
* This file is part of OpenSolid package. | ||
* | ||
* (c) Yonel Ceruto <open@yceruto.dev> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace OpenSolid\Bus\Bridge\Symfony\DependencyInjection\CompilerPass; | ||
|
||
use OpenSolid\Bus\Decorator\Decorate; | ||
use OpenSolid\Bus\Decorator\Decorator; | ||
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; | ||
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; | ||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\DependencyInjection\Exception\LogicException; | ||
use Symfony\Component\DependencyInjection\Reference; | ||
|
||
final readonly class HandlingMiddlewarePass implements CompilerPassInterface | ||
{ | ||
use PriorityTaggedServiceTrait; | ||
|
||
public function __construct( | ||
private string $messageHandlerTagName, | ||
private string $handlingMiddlewareId, | ||
private array $exclude = [], | ||
private bool $allowMultiple = false, | ||
private string $topic = 'message', | ||
) { | ||
} | ||
|
||
public function process(ContainerBuilder $container): void | ||
{ | ||
if (!$container->has($this->handlingMiddlewareId)) { | ||
return; | ||
} | ||
|
||
$handlers = $this->findAndSortTaggedServices( | ||
tagName: new TaggedIteratorArgument($this->messageHandlerTagName, 'class'), | ||
container: $container, | ||
exclude: $this->exclude, | ||
); | ||
|
||
$decorators = []; | ||
foreach ($handlers as $messageClass => $refs) { | ||
if (!$this->allowMultiple && \count($refs) > 1) { | ||
throw new LogicException(\sprintf('Only one handler is allowed for %s of type "%s". However, %d were found: %s', $this->topic, $messageClass, \count($refs), implode(', ', $refs))); | ||
} | ||
|
||
foreach ($refs as $ref) { | ||
/** @var class-string $handlerClass */ | ||
$handlerClass = $container->getDefinition((string) $ref)->getClass(); | ||
|
||
if (null === $refHandlerClass = $container->getReflectionClass($handlerClass)) { | ||
throw new LogicException('Missing reflection class.'); | ||
} | ||
|
||
/** @var array<\ReflectionAttribute<Decorate>> $attributes */ | ||
$attributes = $refHandlerClass->getMethod('__invoke')->getAttributes(Decorate::class, \ReflectionAttribute::IS_INSTANCEOF); | ||
|
||
foreach ($attributes as $attribute) { | ||
$instance = $attribute->newInstance(); | ||
$decorator = $container->getDefinition($instance->id); | ||
/** @var class-string $decoratorClass */ | ||
$decoratorClass = $decorator->getClass(); | ||
|
||
if (!is_subclass_of($decoratorClass, Decorator::class)) { | ||
throw new LogicException(\sprintf('The handler decorator "%s" must implement the "%s" interface.', $decoratorClass, Decorator::class)); | ||
} | ||
|
||
if (method_exists($decoratorClass, 'setOptions')) { | ||
$decorator->addMethodCall('setOptions', [$instance->options]); | ||
} | ||
|
||
$decorators[$handlerClass][$instance->id] = new Reference($instance->id); | ||
} | ||
} | ||
} | ||
|
||
$middleware = $container->findDefinition($this->handlingMiddlewareId); | ||
$middleware->replaceArgument(0, new ServiceLocatorArgument($handlers)); | ||
$middleware->replaceArgument(1, new ServiceLocatorArgument($decorators)); | ||
} | ||
} |
Oops, something went wrong.