mike4git / chain-bundle
This bundle offers a generic way of creating a chain of (potentially responsible) handlers which delegate or finish the task.
Installs: 13
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 1
Forks: 0
Open Issues: 0
Type:symfony-bundle
Requires
- php: ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0
- symfony/config: ^6.4 || ^7.2
- symfony/dependency-injection: ^6.4 || ^7.2
- symfony/framework-bundle: ^6.4 || ^7.2
- symfony/http-kernel: ^6.4 || ^7.2
- symfony/property-access: ^6.4 || ^7.2
Requires (Dev)
- ergebnis/composer-normalize: ^2.42
- friendsofphp/php-cs-fixer: ^3.60
- jangregor/phpstan-prophecy: ^2.1
- matthiasnoback/symfony-dependency-injection-test: ^5.1
- nyholm/symfony-bundle-test: ^3.0
- phpspec/prophecy-phpunit: ^2.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.1.6
- phpstan/phpstan-symfony: ^2.0
- phpunit/phpunit: ^9.5
- qossmic/deptrac-shim: ^0.24 || ^1.0
- symfony/filesystem: ^6.4 || ^7.2
- symfony/phpunit-bridge: ^6.4 || ^7.2
- symfony/test-pack: ^1.0
- symfony/yaml: ^6.4 || ^7.2
README
This bundle offers a generic way of creating a chain of (potentially responsible) handlers which delegate or finish the task.
May be you have already heard about the Chain of Responsability Pattern?!
If you think you can handle that with Symfony Events, do so. If you think you can use Messenger-Middleware-Stack instead, of course - feel free.
But if you are searching for a generic way to put services to a chain, where each of the chain links can decides what is going on, use this bundle.
Installation
Require the bundle
composer require mike4git/chain-bundle
That's it.
Usage
Imagine you have a processing chain. And each link in the chain may need to perform a specific operation on a workpiece until the entire workpiece is complete.
Or you have a series of areas of responsibility, and a request passes through this Chain of Responsabilities until it reaches the one that can handle and respond to it.
In both cases, the chain-bundle helps you create a simple, structured solution based on a common design pattern.
All you need is a workpiece or a request represented as a PHP object. The only requirement is that this object must implement the marker interface Mike4Git\ChainBundle\Handler\Context\ChainHandlerContext
.
Each handler or responsible unit in your chain has two constraints:
- They must implement the Mike4Git\ChainBundle\Handler\ChainHandlerInterface, which requires two methods:
supports()
– to check if the handler can process the given requesthandle()
– to perform the actual processing
- Handlers are registered as tagged services, like this:
Mike4Git\ChainBundle\Tests\FizzBuzz\FizzHandler: tags: - { name: 'chain.handler', chain: 'fizzbuzz', priority: 100 } public: true
To trigger the processing chain, simply use the following code:
/** stop handling if fitting handler found - by default it is true */ $stopOnFound = false; /** @var ChainExecutor $executor */ $executor = $container->get(ChainExecutor::class); $processedWorkpiece = $executor->execute('Name Of The Processing Chain', $workpiece, $stopOnFound);
That's it.
AsChainHandler
Attribute
To make declaration of chain handlers easier, you can use the ChainHandler
attribute in your handler class:
#[AsChainHandler(chain: 'sample', priority: 100)] class MyHandler implements ChainHandlerInterface { ... }
But additionally you have to register MyHandler
as a Symfony service in your yaml file:
App\MyHandler: ~
Example
Let's implement a simple FizzBuzz chain using the chain-bundle. We want different handlers to decide if a number should be replaced with "Fizz", "Buzz", "FizzBuzz", or left as-is.
Define the Context
use Mike4Git\ChainBundle\Handler\Context\ChainHandlerContext; class FizzBuzzContext implements ChainHandlerContext { public function __construct( public int $number, public string $result = '' ) {} }
Create Handlers
One simple example for the FizzBuzzHandler
:
use Mike4Git\ChainBundle\Handler\ChainHandlerInterface; class FizzBuzzHandler implements ChainHandlerInterface { public function supports(ChainHandlerContext $context): bool { return $context instanceof FizzBuzzContext && $context->number % 15 === 0; } public function handle(ChainHandlerContext $context): ChainHandlerContext { $context->result = 'FizzBuzz'; return $context; } }
You can then add FizzHandler and BuzzHandler similarly and don't forget the DefaultNumberHandler.
Register Handlers in services.yaml
App\FizzBuzz\FizzBuzzHandler: tags: - { name: 'chain.handler', chain: 'fizzbuzz', priority: 300 }
Run the Chain
Finally, you can run the chain in your controller or service:
/** @var ChainExecutor $executor */ $executor = $container->get(ChainExecutor::class); $context = new FizzBuzzContext(15); $resultContext = $executor->execute('fizzbuzz', $context, true); echo $resultContext->result; // Outputs: FizzBuzz
Contribution
Feel free to open issues for any bug, feature request, or other ideas.
Please remember to create an issue before creating large pull requests.
Local Development
To develop on local machine, the vendor dependencies are required.
bin/composer install
We use composer scripts for our main quality tools. They can be executed via the bin/composer
file as well.
bin/composer cs:fix bin/composer phpstan bin/composer tests