Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Tactician Laravel Package

tillkruss/laravel-tactician

Laravel integration for the Tactician command bus, letting you dispatch commands to handler classes through Laravel's container. Includes a service provider, facade, and config for mapping commands to handlers and middleware.

View on GitHub
Deep Wiki
Context7

Getting Started

Before installing, confirm the package can actually resolve against your app: tillkruss/laravel-tactician is a Laravel 5-era package, last released in 2016 and archived. It is a good fit for an existing Laravel 5.x codebase, not a new project. Check composer why-not tillkruss/laravel-tactician ^1.0 first; if Composer blocks it, stop and either fork/update the constraints or choose a maintained alternative.

Minimal setup:

composer require tillkruss/laravel-tactician:^1.0
php artisan vendor:publish --provider="TillKruss\LaravelTactician\TacticianServiceProvider"

On Laravel 5.5+ auto-discovery should register the provider. On 5.0–5.4, add the provider and facade/alias to config/app.php manually. Publish and inspect config/tactician.php first — that is where the bus definition and command-to-handler mappings live. Define at least one bus and map one command to one handler:

// config/tactician.php (shape depends on package version)
return [
    'default' => 'default',

    'buses' => [
        'default' => [
            'middleware' => [
                // logging, transactions, etc.
            ],
            'handlers' => [
                App\Commands\RegisterUser::class => App\Handlers\RegisterUserHandler::class,
            ],
        ],
    ],
];

First use case: extract one fat controller action into a command and handler. That gives you a feel for dispatch, container injection, and middleware without refactoring the whole app.

final class RegisterUser
{
    public $name;
    public $email;
    public $password;

    public function __construct($name, $email, $password)
    {
        $this->name = $name;
        $this->email = $email;
        $this->password = $password;
    }
}
class RegisterUserHandler
{
    private $users;

    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    public function handle(RegisterUser $command)
    {
        return $this->users->create(
            $command->name,
            $command->email,
            $command->password
        );
    }
}
public function store(RegisterUserRequest $request)
{
    Tactician::dispatch(new RegisterUser(
        $request->input('name'),
        $request->input('email'),
        $request->input('password')
    ));

    return redirect()->route('dashboard');
}

If the facade alias is not registered in your version, inject the bus or resolve it from the container (app('tactician')) instead. The important workflow is the same: create a command DTO, create a handler with a handle() method, register the mapping, dispatch.

Implementation Patterns

Thin controllers, one command per action. Treat commands as immutable request DTOs. Controllers validate with Form Requests, then dispatch. Do not pass Request objects or Eloquent models into commands; pass scalars or IDs so the command is serializable and testable.

Group buses by bounded context. If your version supports named buses, define separate buses for commands and queries, or per module (Billing, Accounts). Resolve the right bus explicitly rather than letting everything share one global pipeline. This is useful when queries need different middleware than writes.

Build a middleware pipeline deliberately. Middleware is where cross-cutting behavior belongs:

  • Authorization middleware that checks whether the current user may execute the command.
  • Logging middleware that records command class, duration, and failure.
  • Database transaction middleware wrapping the handler.
  • Validation middleware for command invariants that Form Requests cannot express.

Order matters. Put logging outermost, authorization before mutation, transaction directly around the handler. Keep middleware small and avoid dispatching other commands from inside middleware unless you understand the nesting.

Use handlers as the transaction boundary. A common pattern is a DatabaseTransactionMiddleware calling DB::transaction() around $next($command). If you handle domain events, dispatch them after the transaction commits; otherwise listeners can observe uncommitted state.

Integrate with Laravel jobs and console commands. The bus is synchronous. For async work, dispatch a Laravel job, then dispatch the command inside the job's handle(). This gives you queue retries and backoff without expecting the command bus to provide them.

Testing strategy. Unit-test handlers directly by constructing them with fakes or mocks. For integration tests, dispatch through the bus so middleware and mappings are exercised. If you use the facade, mock it to assert a command was dispatched:

Tactician::shouldReceive('dispatch')
    ->once()
    ->with(Mockery::type(RegisterUser::class));

Keep a small wrapper or interface around the bus in your app code. That isolates the archived dependency and makes a later replacement feasible.

Custom locators are the extension point. If you do not want to maintain a large handler map, implement a convention-based locator (for example, App\Handlers\{Command}Handler) or a container-aware locator. Keep the mapping explicit if the project is small; convention-based resolution becomes hard to debug as the handler count grows.

Gotchas and Tips

Archived means compatibility risk is real. The package targets Laravel 5-era APIs. On modern Laravel/PHP it may fail to install or may break due to container, facade, or package-discovery changes. Do not adopt it for greenfield work. If inherited, isolate it behind your own bus interface and keep the dependency pinned.

Handler keys must match exactly. The config map is keyed by the command's fully qualified class name. A typo, leading backslash, or namespace mismatch produces a "no handler found" error at dispatch time, not at boot time. Double-check with ::class constants rather than string literals.

Container injection requires concrete type hints. Handlers are resolved through Laravel's container. If a handler type-hints an interface, that interface must be bound. If it type-hints a concrete class, that class must be resolvable. Constructor injection works; method injection into handle() generally will not unless your locator supports it.

Config caching. After changing config/tactician.php, run php artisan config:clear (and re-cache in production). Do not put closures or runtime logic in the config; cached config cannot serialize closures.

Middleware order is behavior. Transaction middleware placed outside authorization can hold a transaction open while an authorization exception unwinds it. Logging middleware placed inside a transaction may log before rollback. Write a test that asserts the order or at least exercise failure paths.

Return values are handler returns. Tactician's command bus returns what the handler returns; middleware can short-circuit. Do not rely on the bus returning a standardized response object unless you enforce that in handlers. For queries, this is often convenient; for writes, prefer returning an ID or void.

Exceptions bubble. There is no built-in retry, dead-letter, or async behavior. Let exceptions propagate to Laravel's handler, or catch them in middleware for logging/translation. If a command should be retried, it belongs in a queued job, not in the synchronous bus.

Avoid stateful handlers. If the container resolves a handler as a singleton, request-specific state can leak between dispatches. Keep handlers stateless. Commands should be immutable; do not mutate command properties inside handlers.

Debugging checklist. Verify the service provider is loaded (php artisan about on newer Laravel, or provider list on old versions), confirm the config file is published, dump config('tactician'), and dispatch from php artisan tinker with a trivial command. Add a logging middleware early; it makes mapping and pipeline issues obvious.

Facade aliasing and conflicts. If Tactician is already aliased in your app, use constructor injection of the bus or app('tactician'). This also improves testability.

Plan the exit. Because the package is unmaintained, keep command/handler classes framework-agnostic and route dispatch through your own service. Replacing the bus later should be a matter of swapping one adapter, not rewriting business logic.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console