Command bus package for PHP

Related tags

Miscellaneous Chief
Overview

#Chief

Build Status Code Coverage Scrutinizer Code Quality SensioLabsInsight

Chief is a powerful standalone command bus package for PHP 5.4+.

Contents

Command Bus?

The most common style of interface to a module is to use procedures, or object methods. So if you want a module to calculate a bunch of charges for a contract, you might have a BillingService class with a method for doing the calculation, calling it like this $billingService->calculateCharges($contract);. A command oriented interface would have a command class for each operation, and be called with something like this $cmd = new CalculateChargesCommand($contract); $cmd->execute();. Essentially you have one command class for each method that you would have in the method-oriented interface. A common variation is to have a separate command executor object that actually does the running of the command. $command = new CalculateChargesCommand($contract); $commandBus->execute($command);

-- From Martin Fowler's Blog (code samples haven ported to PHP):

That 'executor' Martin mentions is what we call the command bus. The pattern typically consists of 3 classes:

  1. Command: A tiny object containing some data (probably just some public properties or getters/setters)
  2. CommandHandler: Responsible for running the command through a handle($command) method
  3. CommandBus: All commands are passed to the bus execute($command) method, which is responsible for finding the right CommandHandler and calling the handle($command) method.

For every Command in your application, there should be a corresponding CommandHandler.

In the below example, we demonstrate how a command bus design could handle registering a new user in your system using Chief:

use Chief\Chief, Chief\Command;

class RegisterUserCommand implements Command {
	public $email;
	public $name;
}

class RegisterUserCommandHandler {
	public function handle(RegisterUserCommand $command) {
		Users::create([
			'email' => $command->email,
			'name' => $command->name
		]);
		Mailer::sendWelcomeEmail($command->email);
	}
}

$chief = new Chief;

$registerUserCommand = new RegisterUserCommand;
$registerUserCommand->email = '[email protected]';
$registerUserCommand->name = 'Adam Nicholson';

$chief->execute($registerUserCommand);

Installation

Install the latest version with composer require chief/chief, or see Packagist.

No further setup is required, however if you're using a framework and want to make sure that we play nicely (with DI Containers, Event handlers, etc), then use the bridges below.

Laravel

After installing via composer, add the below to the $providers array in your app/config/app.php:

'Chief\Bridge\Laravel\LaravelServiceProvider'

Usage

We'll use the below command/handler for the usage examples:

use Chief\Chief, Chief\Command;

class MyCommand implements Command {}
class MyCommandHandler {
    public function handle(MyCommand $command) { /* ... */ }
}

Automatic handler resolution

When you pass a Command to Chief::execute(), Chief will automatically search for the relevant CommandHandler and call the handle() method:

$chief = new Chief;
$chief->execute(new MyCommand);

By default, this will search for a CommandHandler with the same name as your Command, suffixed with 'Handler', in both the current namespace and in a nested Handlers namespace.

So Commands\FooCommand will automatically resolve to Commands\FooCommandHandler or Commands\Handlers\FooCommandHandler if either class exists.

Want to implement your own method of automatically resolving handlers from commands? Implement your own version of the Chief\CommandHandlerResolver interface to modify the automatic resolution behaviour.

Handlers bound by class name

If your handlers don't follow a particular naming convention, you can explicitly bind a command to a handler by its class name:

use Chief\Chief, Chief\NativeCommandHandlerResolver, Chief\Busses\SynchronousCommandBus;

$resolver = new NativeCommandHandlerResolver();
$bus = new SynchronousCommandBus($resolver);
$chief = new Chief($bus);

$resolver->bindHandler('MyCommand', 'MyCommandHandler');

$chief->execute(new MyCommand);

Handlers bound by object

Or, just pass your CommandHandler instance:

$resolver->bindHandler('MyCommand', new MyCommandHandler);

$chief->execute(new MyCommand);

Handlers as anonymous functions

Sometimes you might want to quickly write a handler for your Command without having to write a new class. With Chief you can do this by passing an anonymous function as your handler:

$resolver->bindHandler('MyCommand', function (Command $command) {
    /* ... */
});

$chief->execute(new MyCommand);

Self-handling commands

Alternatively, you may want to simply allow a Command object to execute itself. To do this, just ensure your Command class also implements CommandHandler:

class SelfHandlingCommand implements Command, CommandHandler {
    public function handle(Command $command) { /* ... */ }
}
$chief->execute(new SelfHandlingCommand);

Decorators

Imagine you want to log every command execution. You could do this by adding a call to your logger in every CommandHandler, however a much more elegant solution is to use decorators.

Registering a decorator:

$chief = new Chief(new SynchronousCommandBus, [new LoggingDecorator($logger)]);

Now, whenever Chief::execute() is called, the command will be passed to LoggingDecorator::execute(), which will perform some log action, and then pass the command to the relevant CommandHandler.

Chief provides you with two decorators out-the-box:

  • LoggingDecorator: Log before and after all executions to a Psr\Log\LoggerInterface
  • EventDispatchingDecorator: Dispatch an event to a Chief\Decorators\EventDispatcher after every command execution.
  • CommandQueueingDecorator: Put the command into a Queue for later execution, if it implements Chief\QueueableCommand. (Read more under "Queued Commands")
  • TransactionalCommandLockingDecorator: Lock the command bus when a command implementing Chief\TransactionalCommand is being executed. (Read more under "Transactional Commands")

Registering multiple decorators:

// Attach decorators when you instantiate
$chief = new Chief(new SynchronousCommandBus, [
    new LoggingDecorator($logger),
    new EventDispatchingDecorator($eventDispatcher)
]);

// Or attach decorators later
$chief = new Chief();
$chief->pushDecorator(new LoggingDecorator($logger));
$chief->pushDecorator(new EventDispatchingDecorator($eventDispatcher));

// Or manually stack decorators
$chief = new Chief(
    new EventDispatchingtDecorator($eventDispatcher,
        new LoggingDecorator($logger, $context, 
            new CommandQueueingDecorator($queuer, 
                new TransactionalCommandLockingDecorator(
                    new CommandQueueingDecorator($queuer, 
                        new SynchronousCommandBus()
                    )
                )
            )
        )
    )
);

Queued Commands

Commands are often used for 'actions' on your domain (eg. send an email, create a user, log an event, etc). For these type of commands where you don't need an immediate response you may wish to queue them to be executed later. This is where the CommandQueueingDecorator comes in to play.

Firstly, to use the CommandQueueingDecorator, you must first implement the CommandQueuer interface with your desired queue package:

interface CommandQueuer {
    /**
     * Queue a Command for executing
     *
     * @param Command $command
     */
    public function queue(Command $command);
}

An implementation of CommandQueuer for illuminate/queue is included.

Next, attach the CommandQueueingDecorator decorator:

$chief = new Chief();
$queuer = MyCommandBusQueuer();
$chief->pushDecorator(new CommandQueueingDecorator($queuer));

Then, implement QueueableCommand in any command which can be queued:

MyQueueableCommand implements Chief\QueueableCommand {}

Then use Chief as normal:

$command = new MyQueueableCommand();
$chief->execute($command);

If you pass Chief any command which implements QueueableCommand it will be added to the queue. Any commands which do not implement QueueableCommand will be executed immediately as normal.

If your commands implement QueueableCommand but you are not using the CommandQueueingDecorator, then they will be executed immediately as normal. For this reason, it is good practice to implement QueueableCommand for any commands which may be queued in the future, even if you aren't using the queueing decorator yet.

Cached Command Execution

The CachingDecorator can be used to store the execution return value for a given command.

For example, you may have a FetchUerReportCommand, and an associated handler which takes a significant time to generate the "UserReport". Rather than re-generating the report every time, simply make FetchUserReport implement CacheableCommand, and the return value will be cached.

Data is cached to a psr/cache (PSR-6) compatible cache library.

Chief does not supply a cache library. You must require this yourself and pass it in as a consturctor argument to the CachingDecorator.

Example:

use Chief\CommandBus,
    Chief\CacheableCommand,
    Chief\Decorators\CachingDecorator;

$chief = new Chief();
$chief->pushDecorator(new CachingDecorator(
	$cache, // Your library of preference implementing PSR-6 CacheItemPoolInterface.
	3600 // Time in seconds that values should be cached for. 3600 = 1 hour.
));


    
class FetchUserReportCommand implements CacheableCommand { }

class FetchUserReportCommahdHandler {
	public function handle(FetchUserReportCommand $command) {
		return 'foobar';
	}
}

$report = $chief->execute(new FetchUserReportCommand); // (string) "foo" handle() is called
$report = $chief->execute(new FetchUserReportCommand); // (string) "foo" Value taken from cache
$report = $chief->execute(new FetchUserReportCommand); // (string) "foo" Value taken from cache

Transactional Commands

Using the TransactionalCommandLockingDecorator can help to prevent more than 1 command being executed at any time. In practice, this means that you if you nest a command execution inside a command handler, the nested command will not be executed until the first command has completed.

Here's an example:

use Chief\CommandBus;
use Chief\Command;
use Chief\Decorators\TransactionalCommandLockingDecorator;

class RegisterUserCommandHandler {
	public function __construct(CommandBus $bus, Users $users) {
		$this->bus = $bus;
	}
	
	public function handle(RegisterUserCommand $command) {
		$this->bus->execute(new RecordUserActivity('this-will-never-be-executed'));
		Users::create([
			'email' => $command->email,
			'name' => $command->name
		]);
		throw new Exception('Something unexpected; could not create user');
	}
}

$chief = new Chief();
$chief->pushDecorator(new TransactionalCommandLockingDecorator());

$command = new RegisterUserCommand;
$command->email = '[email protected]';
$command->password = 'password123';

$chief->execute($command);

So what's happening here? When $chief->execute(new RecordUserActivity('registered-user')) is called, that command is actually dropped into an in-memory queue, which will not execute until RegisterCommandHandler::handle() has finished. In this example, because we're showing that an Exception is thrown before the method completes, the RecordUserActivity command is never actually executed.

Dependency Injection Container Integration

Chief uses a CommandHandlerResolver class which is responsible for finding and instantiating the relevant CommandHandler for a given Command.

If you want to use your own Dependency Injection Container to control the actual instantiation, just create your own class which implements Chief\Container and pass it to the CommandHandlerResolver which is consumed by SynchronousCommandBus.

For example, if you're using Laravel:

use Chief\Resolvers\NativeCommandHandlerResolver,
    Chief\Chief,
    Chief\Busses\SynchronousCommandBus,
    Chief\Container;

class IlluminateContainer implements Container {
    public function make($class) {
        return \App::make($class);
    }
}

$resolver = new NativeCommandHandlerResolver(new IlluminateContainer);
$chief = new Chief(new SynchronousCommandBus($resolver));
$chief->execute(new MyCommand);

Containers have already been provided for :

Illuminate\Container:

$container = new \Illuminate\Container\Container;
$resolver = new NativeCommandHandlerResolver(new \Chief\Bridge\Laravel\IlluminateContainer($container));
$chief = new Chief(new \Chief\Busses\SynchronousCommandBus($resolver));

League\Container:

$container = new \League\Container\Container;
$resolver = new NativeCommandHandlerResolver(new \Chief\Bridge\Laravel\IlluminateContainer($container));
$chief = new Chief(new \Chief\Busses\SynchronousCommandBus($resolver));

Contributing

We welcome any contributions to Chief. They can be made via GitHub issues or pull requests.

License

Chief is licensed under the MIT License - see the LICENSE.txt file for details

Author

Adam Nicholson - [email protected]

You might also like...
m4b-tool is a command line utility to merge, split and chapterize audiobook files such as mp3, ogg, flac, m4a or m4b
m4b-tool is a command line utility to merge, split and chapterize audiobook files such as mp3, ogg, flac, m4a or m4b

m4b-tool m4b-tool is a is a wrapper for ffmpeg and mp4v2 to merge, split or and manipulate audiobook files with chapters. Although m4b-tool is designe

Make a Laravel app respond to a slash command from Slack
Make a Laravel app respond to a slash command from Slack

Make a Laravel app respond to a slash command from Slack This package makes it easy to make your Laravel app respond to Slack's Slash commands. Once y

Your performance & security consultant, an artisan command away.
Your performance & security consultant, an artisan command away.

Enlightn A Laravel Tool To Boost Your App's Performance & Security Introduction Think of Enlightn as your performance and security consultant. Enlight

bin/magento command to display configured preferences for classes or interfaces

bin/magento command to display configured preferences for classes or interfaces A bin/magento command that will show you the configured preferences fo

Tango is a command-line tool for analyzing access logs 💃
Tango is a command-line tool for analyzing access logs 💃

Tango Tool to get insights from the server access logs Tango is a dependency-free command-line tool for analyzing access logs 💃 Currently, work on th

Preload your sweet sweet code to opcache with a composer command, making your code faster to run.

Composer Preload Preload your sweet sweet code to opcache with a composer command, making your code run faster. Composer Preload is a composer plugin

Dispatcher is a Laravel artisan command scheduling tool used to schedule artisan commands within your project so you don't need to touch your crontab when deploying.
Dispatcher is a Laravel artisan command scheduling tool used to schedule artisan commands within your project so you don't need to touch your crontab when deploying.

Dispatcher Dispatcher allows you to schedule your artisan commands within your Laravel project, eliminating the need to touch the crontab when deployi

This module adds a command to easily generate "modules" in Laravel and install them using composer.

Laravel Module Create This module adds a command to easily generate "modules" in Laravel and install them using composer Installation Simply install t

Detection of execution by command (Terminal) or web environment.
Detection of execution by command (Terminal) or web environment.

WEB-CLI-Detector Detection of execution by command (Terminal) or web environment. Acronym: [WEB-CLI-Detector]. Name: WEB-CLI-Detector. Dependencies: S

Comments
  • Queue

    Queue

    Hey! Can someone give a clear explanation of this?? https://github.com/adamnicholson/Chief/blob/master/src/Bridge/Laravel/IlluminateQueuer.php#L18

    This is my CommandQueuer

    class CommandBusQueuer implements CommandQueuer
    {
        /**
         * Queue a command to be executed
         *
         * @param Command $command
         *
         * @return mixed
         */
        public function queue(Command $command)
        {
        }
    }
    

    What should i write inside the queue method??

    I have tried using the execute($command) of the chief like this App::getContainer()->get("chief")->execute( $command ) to run the command but am getting Allowed memory size of 134217728.... .

    This is how i have initialized chief

    $chief = new Chief();
    $queuer = new \MyProject\Events\CommandBusQueuer();
    $chief->pushDecorator(new CommandQueueingDecorator($queuer));
    
    opened by bmutinda 0
Owner
Adam Nicholson
Adam Nicholson
Ecotone Framework is Service Bus Implementation. It enables message driven architecture and DDD, CQRS, Event Sourcing PHP

This is Read Only Repository To contribute make use of Ecotone-Dev repository. Ecotone is Service Bus Implementation, which enables message driven arc

EcotoneFramework 308 Dec 29, 2022
A simple bus ticket system made by Larave 5.4

Bus Ticket System About Bus Ticket System A simple bus ticket system made by Larave 5.4. There are several features here in this project and those are

Eng Hasan Hajjar 3 Sep 30, 2022
A collection of command line scripts for Magento 2 code generation, and a PHP module system for organizing command line scripts.

What is Pestle? Pestle is A PHP Framework for creating and organizing command line programs An experiment in implementing python style module imports

Alan Storm 526 Dec 5, 2022
salah eddine bendyab 18 Aug 17, 2021
A Laravel package which helps you to flush sessions with an artisan command.

A simple laravel Package to flush sessions via artisan command. Sometimes we store data on sessions such as cart data or maybe any information regardi

Erfan Ahmed Siam 5 Jun 1, 2023
Artisan Command for FriendsOfPHP/PHP-CS_Fixer

Laravel PHP CS Fixer The PHP CS Fixer is maintained on GitHub at https://github.com/FriendsOfPHP/PHP-CS-Fixer bug reports and ideas about new features

Signature Tech Studio 139 Dec 15, 2022
From the team that brought you laravel-random-command comes another gem!

?? Why require one if you can require them all? From the team that brought you laravel-random-command comes another gem! Requiring all our packages se

Spatie 46 Oct 5, 2022
The swiss army knife for Magento developers, sysadmins and devops. The tool provides a huge set of well tested command line commands which save hours of work time. All commands are extendable by a module API.

netz98 magerun CLI tools for Magento 2 The n98 magerun cli tools provides some handy tools to work with Magento from command line. Build Status Latest

netz98 758 Dec 28, 2022
Heal and Feed Command For PocketMine-MP

Description Heal and Feed Command For PocketMine-MP. If you are have a question, please make a issues Features Simple Configuration With UI Can heal a

null 2 Feb 15, 2022
A PocketMine-MP Unregister-Command completely different from the others on Poggit.

This Unregister-Cmd plugin is completely different from the ones you see on Poggit while that much easier and constantly in active development. Featur

Arzxy Modz 1 Sep 12, 2022