Tactician Documentation

repository·1.x·Indexed 21 days ago

https://github.com/thephpleague/tactician

A small, pluggable command bus for PHP that decouples the intent of an action (the command) from its execution logic (the handler). It features a middleware-based architecture with support for plugins such as logging, Doctrine transactions, and background queuing via Bernard. The library includes tools for handler location (InMemoryLocator, CallableLocator), method name inflection (ClassNameInflector), and a QuickStart utility for rapid prototyping.

Tokens
3.4K
Snippets
14
Records
17
Agent score
73%

What's inside Tactician

  1. Extend Tactician with Plugins

    1.x

    Tactician is a small core package designed to be extended via plugins. Common plugins include:

    • Logger: Adds PSR-3 logging for command reception, completion, or failure.
    • Container: Enables lazy loading of handlers from any container-interop compatible container.
    • Doctrine: Wraps commands in separate Doctrine ORM transactions.
    • Bernard: Allows background command queuing using the Bernard library.
    • Command Events: Fires events during major lifecycle moments of a command.
    • Locking: Ensures only one command is executed at a time.
  2. Rapidly set up a CommandBus with QuickStart

    1.x

    The QuickStart::create() method provides a pre-configured CommandBus instance to get started quickly. This setup is intended for prototyping and initial experimentation rather than long-term production use.

    By default, the QuickStart configuration includes:

    • Handler Storage: Handlers are stored in an InMemoryLocator.
    • Handler Discovery: Uses ClassNameExtractor to find handlers.
    • Method Naming: Uses HandleInflector (expects the handler method to be named handle).
    • Concurrency Control: Includes LockingMiddleware to ensure only one command is executed at a time.

    To use it, provide an associative array mapping command class names to their corresponding handler instances.

    use League\Tactician\Setup\QuickStart;
    
    $commandToHandlerMap = [
        MyCommand::class => new MyCommandHandler(),
    ];
    
    $commandBus = QuickStart::create($commandToHandlerMap);
    
    $commandBus->handle(new MyCommand());
  3. Use InMemoryLocator to map commands to handlers

    1.x

    The InMemoryLocator is a HandlerLocator implementation that stores a mapping of command class names to specific handler instances in memory. You can use it to manually wire your application by registering handlers for specific command classes.

    To use it, you can either pass a map of command classes to handler instances during construction or use the addHandler() method to register them individually.

    // Option 1: Initialize with a map
    $locator = new InMemoryLocator([
        AddTaskCommand::class => new AddTaskHandler($dependency),
        CompleteTaskCommand::class => new CompleteTaskHandler($dependency),
    ]);
    
    // Option 2: Add handlers individually
    $locator->addHandler(new TaskAddedHandler($dep1, $dep2), 'My\TaskAddedCommand');
    
    // Retrieve a handler
    $handler = $locator->getHandlerForCommand('My\TaskAddedCommand');
  4. Use LockingMiddleware to queue commands during execution

    1.x

    The LockingMiddleware ensures that only one command is executed at a time on the command bus. If a command is already being processed, any new incoming commands are added to an internal queue and will only execute once the current command completes.

    Note that if multiple commands are queued while one is running, only the return value of the first queued command is returned to the caller; subsequent return values from the queue are discarded.

    use League\Tactician\MiddlewareStack;
    use League\Tactician\Plugins\LockingMiddleware;
    
    $stack = new MiddlewareStack();
    $stack->add(new LockingMiddleware());
    
    // When you dispatch commands via the bus using this stack,
    // they will be queued if one is already executing.
  5. Use CallableLocator to integrate DI containers

    1.x

    The CallableLocator allows you to bridge Tactician with a Dependency Injection (DI) container without writing a custom adapter. It works by taking a callable that accepts a command name and returns the corresponding handler instance.

    If the callable returns null, a MissingHandlerException is thrown. This is particularly useful for containers that use a get method (like Symfony) or for using closures to add custom logic when resolving handlers.

    // Example: Using a container with a 'get' method
    $locator = new \League\Tactician\Handler\Locator\CallableLocator([$container, 'get']);
    
    // Example: Using a closure for custom logic
    $locator = new \League\Tactician\Handler\Locator\CallableLocator(function ($commandName) use ($container) {
        return $container->get($commandName);
    });
  6. Use CommandHandlerMiddleware to execute commands

    1.x

    The CommandHandlerMiddleware is the core component of the Tactician command bus. It is responsible for locating the appropriate handler for a given command and executing it.

    To function, it requires three dependencies injected via its constructor:

    1. CommandNameExtractor: To determine the command's name from the command object.
    2. HandlerLocator: To find the handler instance associated with that command name.
    3. MethodNameInflector: To determine which method on the handler should be called to process the command.

    If the middleware cannot find a valid callable method on the located handler, it throws a League\Tactician\Exception\CanNotInvokeHandlerException.

    use League\​Tactician\Handler\CommandHandlerMiddleware;
    use League\\Tactician\\Handler\CommandNameExtractor\CommandNameExtractor;
    use League\\Tactician\\Handler\Locator\HandlerLocator;
    use League\\Tactician\\Handler\MethodNameInflector\MethodNameInflector;
    
    // The middleware is typically instantiated and added to a middleware stack
    $middleware = new CommandHandlerMiddleware(
        $commandNameExtractor,
        $handlerLocator,
        $methodNameInflector
    );
  7. Dispatch commands using the CommandBus

    1.x

    The CommandBus is the primary entrypoint for sending commands through a chain of middleware. You initialize it by passing an array of Middleware instances to the constructor. To execute a command, call the handle() method with your command object. The handle() method will pass the command through the middleware chain and return the result of the execution (which can be mixed).

    Note: The command passed to handle() must be an object. If a non-object is provided, an InvalidCommandException is thrown.

    use League	actician\CommandBus;
    
    // Assuming $middleware is an array of League\Tactician\Middleware implementations
    $commandBus = new CommandBus($middleware);
    
    $result = $commandBus->handle(new MyCommand());
  8. Handle MissingHandlerException when no command handler is found

    1.x

    The MissingHandlerException is thrown by Tactician when the command bus is unable to locate a registered handler for a specific command. You can catch this exception to handle cases where a command was dispatched but no logic was mapped to it. You can retrieve the name of the command that caused the failure using the getCommandName() method.

    use League\Tactician\Exception\MissingHandlerException;
    
    try {
        $commandBus->handle($command);
    } catch (MissingHandlerException $e) {
        $commandName = $e->getCommandName();
        // Handle the missing handler scenario
    }
  9. Handle InvalidCommandException

    1.x

    The InvalidCommandException is thrown by the command bus when a value that is not an object is passed as a command. You can catch this exception to identify the specific invalid value that caused the failure using the getInvalidCommand() method.

    try {
        $commandBus->handle($nonObjectValue);
    } catch (\League\Tactician\Exception\InvalidCommandException $e) {
        $invalidValue = $e->getInvalidCommand();
        // Handle the error
    }
  10. Retrieve a handler with getHandlerForCommand()

    1.x

    The getHandlerForCommand() method looks up the handler instance associated with the provided command class name.

    • $commandName: The fully qualified class name (string) of the command.
    • Returns: The registered handler object.
    • Throws: League\Tactician\Exception\MissingHandlerException if no handler is bound to the provided command name.
    $handler = $inMemoryLocator->getHandlerForCommand('My\TaskAddedCommand');