PHPackages                             brammm/tactishun - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. brammm/tactishun

ActiveLibrary

brammm/tactishun
================

A simple PHP 8 command bus implementation

v0.3.0(9mo ago)121[3 PRs](https://github.com/Brammm/tactishun/pulls)LGPL-3.0-or-laterPHPPHP ^8.3CI passing

Since Jul 22Pushed 5mo ago1 watchersCompare

[ Source](https://github.com/Brammm/tactishun)[ Packagist](https://packagist.org/packages/brammm/tactishun)[ RSS](/packages/brammm-tactishun/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (3)Dependencies (5)Versions (7)Used By (0)

Tactishun
=========

[](#tactishun)

[![PHP Version](https://camo.githubusercontent.com/4a48974bf8596f592c126bdf1772e9eba70eb49972e572e51de922d2b3ca253c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f6272616d6d6d2f74616374697368756e2f7068702e7376673f636f6c6f72423d253233383839324246267374796c653d666c61742d737175617265)](https://php.net)[![Latest Version on Packagist](https://camo.githubusercontent.com/60fc62560615701e9948b6886b5f2a175420888911b77f366be424008d9f0e2b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6272616d6d6d2f74616374697368756e2e7376673f7374796c653d666c61742d737175617265266c6162656c3d72656c65617365)](https://packagist.org/packages/brammm/tactishun)[![Software License](https://camo.githubusercontent.com/bf1c19b4a07c841715e713542bd6e9c2aaf75ffa2ee2aa3987814d99311f799b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4c47504c76332d677265656e2e7376673f7374796c653d666c61742d737175617265)](LICENSE.md)[![Build status](https://camo.githubusercontent.com/b199db2b8a3cf54d08e40257da6cb990de3b954ffedd89bea675dd6f7c28c14d/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6272616d6d6d2f74616374697368756e2f636f6e74696e756f75732d696e746567726174696f6e2e796d6c3f6272616e63683d6d61696e267374796c653d666c61742d737175617265266c6f676f3d676974687562)](https://github.com/brammm/tactishun/actions/workflows/continuous-integration.yml)

A lightweight command bus implementation for PHP 8.3+ projects, designed to provide a straightforward approach to hexagonal architecture and CQRS patterns.

Features
--------

[](#features)

- Simple command bus pattern implementation
- PSR-11 container integration
- Extensible middleware system
- Zero configuration required to get started
- Full PHPStan coverage

Installation
------------

[](#installation)

Install via Composer:

```
composer require brammm/tactishun
```

Quickstart
----------

[](#quickstart)

```
use Brammm\Tactishun\CommandBus;
use Brammm\Tactishun\CommandHandler\CommandHandler;
use Brammm\Tactishun\HandledBy;

// 1. Create your command
#[HandledBy(SendWelcomeMailHandler::class)]
final readonly class SendWelcomeMail
{
    public function __construct(
        public UserId $userId,
    ) {}
}

// 2. Implement the handler
/** @implements CommandHandler */
final class SendWelcomeMailHandler implements CommandHandler
{
    public function handle(object $command): void
    {
        // Fetch user from $command->userId and send welcome email
    }
}

// 3. Set up the command bus
$container = new Container(); // Any PSR-11 compatible container
$commandBus = new CommandBus($container);

// 4. Dispatch commands
$commandBus->handle(new SendWelcomeMail($user->id));
```

Usage
-----

[](#usage)

### Handling multiple commands with a single command handler

[](#handling-multiple-commands-with-a-single-command-handler)

It's possible to have multiple commands handled by a single command handler. Simply add the `HandledBy` attribute with the same handler to multiple command classes.

For convenience, you can extend from the MultipleCommandsHandler (instead of using the CommandHandler interface). It forwards the command to separate `handleCommandName`methods.

```
use Brammm\Tactishun\CommandHandler\MultipleCommandsHandler;
use Brammm\Tactishun\HandledBy;

#[HandledBy(UserCommandHandler::class)]
final readonly class RegisterUser
{
}

#[HandledBy(UserCommandHandler::class)]
final readonly class DeactivateUser
{
}

/** @extends MultipleCommandsHandler */
final class UserCommandHandler extends MultipleCommandsHandler
{
    public function handleRegisterUser(RegisterUser $command): void
    {
    }

    public function handleDeactivateUser(DeactivateUser $command): void
    {
    }
}
```

### Extending functionality through middleware

[](#extending-functionality-through-middleware)

Extend functionality with middleware that wraps command execution:

```
final readonly class LoggingMiddleware implements Middleware
{
    public function __construct(
        private LoggerInterface $logger
    ) {}

    public function process(object $command, CommandHandler $commandHandler): void
    {
        $commandName = get_class($command);
        $this->logger->info("Executing command: {$commandName}");

        $startTime = microtime(true);
        $commandHandler->handle($command);
        $executionTime = microtime(true) - $startTime;

        $this->logger->info("Command {$commandName} completed in {$executionTime}ms");
    }
}

// Register middleware
$commandBus->add(new LoggingMiddleware($logger));
```

Middleware are added First In, First Out.

Through Middleware, it is possible to provide async queued functionality. No Middleware to facilitate this are shipped with this package at the moment.

### Custom `CommandHandlerResolver`s

[](#custom-commandhandlerresolvers)

By default, Tactishun uses the `AttributeCommandHandlerResolver` to find handlers via the `HandledBy` attribute. This allows the zero-configuration setup. If you'd rather not use that attribute or have a different solution in mind, you can provide a custom resolver as the second parameter:

```
class ClassMapResolver implements CommandHandlerResolver
{
    private array $handlerMap = [
        SendWelcomeMail::class => SendWelcomeMailHandler::class,
        ProcessPayment::class => ProcessPaymentHandler::class,
    ];

    public function resolve(object $command): string
    {
        $commandClass = get_class($command);
        return $this->handlerMap[$commandClass] ?? throw new HandlerNotFound($commandClass);
    }
}

$commandBus = new CommandBus($container, new ClassMapResolver());
```

Note that command handler resolvers must return a class-string for a command handler that the container implementation can then resolve to an instance of that handler.

Inspiration
-----------

[](#inspiration)

This library draws inspiration from:

- [Ross Tuck](https://github.com/rosstuck)'s work on [league/tactician](https://github.com/thephpleague/tactician)
- The [Slim Framework](https://github.com/slimphp/Slim) middleware dispatcher pattern

###  Health Score

33

—

LowBetter than 75% of packages

Maintenance66

Regular maintenance activity

Popularity5

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 92.9% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~10 days

Total

3

Last Release

278d ago

PHP version history (2 changes)v0.1.0PHP ^8.1

v0.2.0PHP ^8.3

### Community

Maintainers

![](https://www.gravatar.com/avatar/fb062679f23619abece6f72b11b7f1ea92748429affb766d38bdd3d4f3c6d401?d=identicon)[Brammm](/maintainers/Brammm)

---

Top Contributors

[![Brammm](https://avatars.githubusercontent.com/u/851445?v=4)](https://github.com/Brammm "Brammm (26 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")

---

Tags

command bus

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/brammm-tactishun/health.svg)

```
[![Health](https://phpackages.com/badges/brammm-tactishun/health.svg)](https://phpackages.com/packages/brammm-tactishun)
```

###  Alternatives

[league/tactician

A small, flexible command bus. Handy for building service layers.

86415.4M127](/packages/league-tactician)[simple-bus/message-bus

Generic classes and interfaces for messages and message buses

3455.7M30](/packages/simple-bus-message-bus)[api-platform/state

API Platform state interfaces

223.4M57](/packages/api-platform-state)[mikemix/tactician-module

Laminas/Mezzio Module to use the League of Extraordinary Packages' Tactician library - flexible command bus implementation

17156.2k](/packages/mikemix-tactician-module)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
