I’m developing a custom Symfony bundle that provides a state machine system. I am using Symfony 6.2.7.
The bundle user must configure it like this:
# config/packages/state_machine.yaml
state_machine:
definition:
directory: '%kernel.project_dir%/smdef'
commands:
namespace: 'App\StateMachine\Commands\'
directory: '%kernel.project_dir%/src/StateMachine/Commands'
In my bundle extension, I try to automatically register all command classes located in this directory and tag them:
final class StateMachineBundle extends AbstractBundle {
...
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void {
$services = $container->services();
$services->defaults()->autowire()->autoconfigure()->private();
$cmdDir = $builder->getParameterBag()->resolveValue($config['commands']['directory']);
if (!is_dir($cmdDir)) {
throw new \RuntimeException(sprintf('Directory not found: %s', $cmdDir));
}
$services
->load($config['commands']['namespace'], "$cmdDir/*")
->tag('state_machine.command');
...
}
...
}
The goal is to have one service per command class (all implementing CommandInterface) so they can later be fetched dynamically by FQCN.
I inject them using a tagged locator:
// services.php
...
$services
->set('state_machine.command_executor', CommandExecutor::class)
->args([
tagged_locator('state_machine.command', 'class'),
])
->alias(CommandExecutor::class, 'state_machine.command_executor');
...
The executor looks like this:
final class CommandExecutor implements CommandExecutorInterface
{
public function __construct(private readonly ContainerInterface $commandLocator) {}
public function execute(\Iterator $listOfCommands, Stateful $stateful): ResultInterface
{
try {
foreach ($listOfCommands as $commandFqcn) {
if (!$this->commandLocator->has($commandFqcn)) {
throw new CommandNotRegisteredException($commandFqcn);
}
$command = $this->commandLocator->get($commandFqcn);
$command->execute($stateful);
}
} ...
}
}
My problem: CommandNotRegisteredException is always thrown.
This is confirmed when I run:
bin/console debug:container --tag=state_machine.command
I get:
No tags found that match "state_machine.command"
Notes:
What could prevent services from being registered/tagged in this case? Is the load() resource pattern incorrect, or am I missing something about how Symfony resolves/keeps tagged services?
I think you're making this too complex. You shouldn't need the commands namespace/directory config at all. As long as users have the directory where their commands are located autowired/autoconfigured (this would be by default in a standard Symfony app). You can change loadExtension() to:
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
{
$builder
->registerForAutoconfiguration(CommandInterface::class)
->addTag('state_machine.command')
;
}
See this doc for more information.