Weave Code
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

Tactician Container Laravel Package

league/tactician-container

PSR-11 container plugin for League Tactician that lazily loads command handlers from your DI container. Install via Composer and integrate with Tactician to resolve handlers on demand for cleaner wiring and faster bootstrap.

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup
1. **Install the package**:
   ```bash
   composer require league/tactician-container
  1. Register the plugin in your Laravel service container (typically in AppServiceProvider):
    use League\Tactician\Container\ContainerPlugin;
    use League\Tactician\Container\ContainerLocator;
    
    public function register()
    {
        $this->app->bind(ContainerLocator::class, function () {
            return new ContainerLocator($this->app);
        });
    
        $this->app->bind(ContainerPlugin::class, function () {
            return new ContainerPlugin();
        });
    }
    
  2. Configure Tactician to use the container plugin (e.g., in config/tactician.php):
    'plugins' => [
        League\Tactician\Container\ContainerPlugin::class,
    ],
    

First Use Case: Lazy-Loading Handlers

Define a command handler as a service binding:

$this->app->bind(
    \App\Commands\ProcessOrderHandler::class,
    \App\Commands\ProcessOrderHandler::class
);

Tactician will now resolve it from the container when needed, avoiding eager loading.


Implementation Patterns

1. Handler Resolution Workflow

  • Bind handlers to the container as services (e.g., in AppServiceProvider):
    $this->app->bind(
        \App\Commands\SendEmailHandler::class,
        \App\Commands\SendEmailHandler::class
    );
    
  • Tag handlers (optional) for dynamic discovery:
    $this->app->tag([\App\Commands\ProcessOrderHandler::class]);
    
  • Use in middleware/processors:
    $commandBus = $this->app->make(\League\Tactician\CommandBus::class);
    $result = $commandBus->handle(new ProcessOrderCommand());
    

2. Middleware Integration

  • Bind middleware to the container (e.g., logging middleware):
    $this->app->bind(
        \App\Middleware\LogCommandMiddleware::class,
        \App\Middleware\LogCommandMiddleware::class
    );
    
  • Configure middleware in Tactician (via config/tactician.php):
    'middleware' => [
        \League\Tactician\Middleware\CommandHandlerMiddleware::class,
        \App\Middleware\LogCommandMiddleware::class,
    ],
    

3. Dynamic Handler Discovery

  • Use ContainerLocator to resolve handlers dynamically:
    $locator = $this->app->make(ContainerLocator::class);
    $handler = $locator->getHandler($command);
    
  • Tag handlers for bulk resolution (e.g., in register()):
    $this->app->tag([
        \App\Commands\HandlerA::class,
        \App\Commands\HandlerB::class,
    ]);
    

4. Dependency Injection

  • Inject dependencies into handlers via constructor binding:
    $this->app->bind(\App\Commands\ProcessOrderHandler::class, function ($app) {
        return new \App\Commands\ProcessOrderHandler(
            $app->make(\App\Services\OrderService::class)
        );
    });
    

5. Testing Patterns

  • Mock the container in tests:
    $container = $this->createMock(\Psr\Container\ContainerInterface::class);
    $container->method('get')
        ->with(\App\Commands\TestHandler::class)
        ->willReturn(new \App\Commands\TestHandler());
    
    $locator = new ContainerLocator($container);
    
  • Use TacticianTestCase (if available) for boilerplate reduction.

Gotchas and Tips

Pitfalls

  1. Circular Dependencies

    • Tactician resolves handlers lazily, but circular dependencies between handlers (e.g., HandlerA depends on HandlerB, which depends on HandlerA) will cause ContainerException.
    • Fix: Refactor to avoid circular dependencies or use method injection.
  2. Handler Not Found

    • If a handler is not bound to the container, Tactician throws HandlerNotFoundException.
    • Fix: Ensure all handlers are registered as services or use a fallback locator.
  3. Middleware Resolution Order

    • Middleware bound to the container must be listed in config/tactician.php under 'middleware' to take effect.
    • Fix: Explicitly declare middleware in the config.
  4. Container Interface Mismatch

    • The package expects PSR-11 (Psr\Container\ContainerInterface). Laravel’s container implements Illuminate\Contracts\Container\Container, which is compatible, but third-party containers may not be.
    • Fix: Use a PSR-11 adapter if needed (e.g., league/container).
  5. Singleton vs. Non-Singleton Handlers

    • By default, Laravel’s container resolves bindings as singletons. If a handler relies on non-shared state (e.g., a database connection), bind it as non-shared:
      $this->app->bind(
          \App\Commands\NonSharedHandler::class
      )->instantiate();
      

Debugging Tips

  1. Check Bindings

    • Verify handlers are bound:
      $this->app->bound(\App\Commands\ProcessOrderHandler::class); // true/false
      
    • List all bindings:
      $this->app->getBindings();
      
  2. Enable Debugging

    • Temporarily replace the container with a debug wrapper:
      $debugContainer = new class($this->app) implements \Psr\Container\ContainerInterface {
          private $container;
          public function __construct($container) { $this->container = $container; }
          public function get($id) {
              try {
                  return $this->container->get($id);
              } catch (\Exception $e) {
                  throw new \RuntimeException("Failed to resolve {$id}: " . $e->getMessage());
              }
          }
          // ... other methods
      };
      
  3. Log Handler Resolution

    • Wrap ContainerLocator to log resolutions:
      $locator = new class($this->app) extends ContainerLocator {
          public function getHandler($command) {
              \Log::debug("Resolving handler for " . get_class($command));
              return parent::getHandler($command);
          }
      };
      

Extension Points

  1. Custom Locator

    • Extend ContainerLocator to add logic (e.g., fallback to a static map):
      class CustomLocator extends ContainerLocator {
          public function getHandler($command) {
              if (!$this->container->has($handlerClass = static::getHandlerClass($command))) {
                  return $this->fallbackLocator->getHandler($command);
              }
              return $this->container->get($handlerClass);
          }
      }
      
  2. Dynamic Handler Naming

    • Override getHandlerClass() to use a custom naming convention:
      class CustomLocator extends ContainerLocator {
          protected static function getHandlerClass($command) {
              return 'App\\Handlers\\' . class_basename($command) . 'Handler';
          }
      }
      
  3. Middleware Binding

    • Dynamically bind middleware based on conditions:
      $this->app->when(\League\Tactician\CommandBus::class)
          ->needs(\App\Middleware\AuthMiddleware::class)
          ->give(function () {
              return new \App\Middleware\AuthMiddleware($this->app->make(\App\Services\AuthService::class));
          });
      
  4. Integration with Laravel Events

    • Listen for tactician.command.before/after events to modify behavior:
      \Event::listen('tactician.command.before', function ($command, $handler) {
          // Pre-process command or handler
      });
      

Performance Tips

  1. Avoid Over-Binding

    • Only bind handlers that are actually used to reduce container overhead.
  2. Use instantiate() for Non-Shared Handlers

    • For handlers that shouldn’t be singletons:
      $this->app->bind(\App\Commands\TempHandler::class)->instantiate();
      
  3. Cache Resolutions

    • For high-frequency commands, cache resolved handlers:
      $handlerCache = new \Symfony\Component\Cache\Simple\FilesystemCache();
      $locator = new class($this->app, $handlerCache) extends ContainerLocator {
          public function getHandler($command) {
              $key = spl_object_hash($command);
              if ($this->cache->has($key)) {
                  return $this->cache->get($key);
              }
              $handler = parent::getHandler($command);
              $this->cache->set($key, $handler, 3600); // Cache for 1 hour
      
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.
daikazu/eloquent-salesforce-objects
unseen-codes/chat
romalytar/yammi-jobs-monitoring-laravel
kisame76/filament-db-table-state
nqxcode/laravel-lucene-search
dpfx/laravel-livewire-wizards
workos/workos-php-laravel
sofa/laravel-global-scope
nawasara/auth-primitives
adhocrat-io/arkhe-main
make-dev/orca-harpoon
itsemon245/lamet
baks-dev/dashboard
amoifr/pickle-panther-bundle
make-dev/orca
dmstr/symfony-system-resources-bundle
dmstr/symfony-job-queue-bundle
dmstr/openapi-json-schema-bundle
dmstr/keycloak-security-bundle
dmstr/doctrine-audit-log-bundle