CakePHP Tactician
, (*1)
CakePHP plugin for league/tactician
., (*2)
NOTE: The plugin is under development., (*3)
Installation
composer require robotusers/cakephp-tactician
bin/cake plugin load Robotusers/Tactician
Using the plugin
CakePHP integration
This plugin provides Controller and Model integration through Commander library., (*4)
Commander is a command bus abstraction library for PHP which enables you to decouple your code from a concrete command bus implementation., (*5)
Using the Commander (PHP 7.1+)
Install robotusers/commander
:, (*6)
composer require robotusers/commander
Set up your controllers:, (*7)
use Cake\Controller\Controller;
use Robotusers\Commander\CommandBusAwareInterface;
use Robotusers\Tactician\Bus\CommandBusAwareTrait;
class OrdersController extends Controller implements CommandBusAwareInterface
{
use CommandBusAwareTrait;
public function makeOrder()
{
// ...
$command = new MakeOrderCommand($data);
$this->handleCommand($command);
// ...
// or using quick convention enabled command handling:
$this->handleCommand('MakeOrder', $data);
// ...
}
}
For more information, read the docs., (*8)
Next you should configure the command bus which will be injected into your controllers and models that implement the CommandBusAwareInterface
., (*9)
Console (CakePHP 3.6+)
For console integration Tactician plugin provides a CommandFactory
that injects a command bus into compatible console shells and commands., (*10)
Set up your CommandRunner
as below:, (*11)
use App\Application;
use Cake\Console\CommandRunner;
use Cake\Console\CommandFactory;
use Tactician\Console\CommandFactory as TacticianCommandFactory;
$application = new Application(dirname(__DIR__) . '/config');
$cakeFactory = new CommandFactory(); // or any other custom factory (ie. CakePHP DI plugin DIC-compatible factory)
$factory = new TacticianCommandFactory($cakeFactory, $application);
$runner = new CommandRunner($application, 'cake', $factory);
exit($runner->run($argv));
Application hook (CakePHP 3.3+)
If your application supports middleware you can configure the command bus using an application hook., (*12)
use Cake\Http\BaseApplication;
use League\Tactician\CommandBus;
use Robotusers\Tactician\Core\BusApplicationInterface;
use Robotusers\Tactician\Core\BusMiddleware;
class Application extends BaseApplication implements BusApplicationInterface
{
public function commandBus()
{
$bus = new CommandBus([
// your middleware
]);
return $bus;
}
public function middleware($middleware)
{
// ...
$middleware->add(new BusMiddleware($this));
// ...
return $middleware;
}
}
You can use helper factory methods for building CommandBus
or CakePHP convention enabled CommandHandlerMiddleware
:, (*13)
use Robotusers\Tactician\Bus\Factory;
public function commandBus()
{
return Factory::createCommandBus([
// your middleware
Factory::createCommandHandlerMiddleware();
]);
}
The command bus configured here will be injected into controllers and models in Model.initialize
and Controller.initialize
event listener., (*14)
Bootstrap
If you're still on pre 3.3 stack you can set up the listener in your bootstrap.php
file., (*15)
You can use build in quick start class:, (*16)
// bootstrap.php
use Robotusers\Tactician\Event\QuickStart;
QuickStart::setUp($commandBus);
QuickStart
can load simple CakePHP convention enabled bus if it hasn't been provided:, (*17)
// bootstrap.php
use Robotusers\Tactician\Event\QuickStart;
QuickStart::setUp();
Conventions locator
CakePHP Conventions locator will look for command handlers based on a convention,
that commands should reside under App\Model\Command\
namespace and be suffixed with Command
string
and handlers should reside under App\Model\Handler\
namespace and be suffixed with Handler
string., (*18)
//CakePHP convention locator
$locator = new ConventionsLocator();
$extractor = new ClassNameExtractor();
$inflector = new HandleClassNameInflector();
$commandBus = new CommandBus(
[
new CommandHandlerMiddleware($extractor, $locator, $inflector)
]
);
In this example App\Model\Command\MakeOrderCommand
command will map to App\Model\Handler\MakeOrderHandler
handler., (*19)
You can change default namespace and suffix using configuration options:, (*20)
$locator = new ConventionsLocator([
'commandNamespace' => 'Command',
'commandSuffix' => '',
'handlerNamespace' => 'Handler',
'handlerSuffix' => '',
]);
In this example App\Command\MakeOrder
command will map to App\Handler\MakeOrder
handler. Note a different namespace and no suffix., (*21)
Transaction middleware
Transaction middleware is a wrapper for CakePHP ConnectionInterface::transactional()
.
You need to provide a list of supported commands., (*22)
A list supports FQCN or convention supported name (eq Plugin.Name
)., (*23)
This will wrap only Foo
and Bar
commands in a transaction:, (*24)
//default connection
$connection = ConnectionManager::get('default');
$commandBus = new CommandBus(
[
//CakePHP transaction middleware with a connection and a list of commands.
new TransactionMiddleware($connection, [
FooCommand::class,
'My/Plugin.Bar',
]),
$commandHandlerMiddleware
]
);
You can include all commands by setting the $commands
argument to true
and exclude only some commands., (*25)
This will wrap all commands in a transaction with an exception for Foo
and Bar
commands:, (*26)
$middleware = new TransactionMiddleware($connection, true, [
FooCommand::class,
'My/Plugin.Bar',
]),