diff --git a/config/services.php b/config/services.php
index 8636055..2b831fd 100644
--- a/config/services.php
+++ b/config/services.php
@@ -4,7 +4,9 @@
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Kraz\DoctrineContextBundle\Command\Doctrine\Database\CreateDatabaseCommand;
+use Kraz\DoctrineContextBundle\Command\Doctrine\Strategy\ConnectionStrategy;
use Kraz\DoctrineContextBundle\Configuration\Configuration as DoctrineContextConfiguration;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -13,11 +15,19 @@
->set('doctrine.doctrine_context.configuration', DoctrineContextConfiguration::class)
->public()
+ ->set('doctrine.doctrine_context.strategy.connection', ConnectionStrategy::class)
+
+ ->set('doctrine.doctrine_context.context_runner', ContextRunner::class)
+ ->public()
+ ->args([
+ service('doctrine.doctrine_context.configuration'),
+ ])
+
->set('doctrine.database_create_command.with_context', CreateDatabaseCommand::class)
->decorate('doctrine.database_create_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:database:create']);
};
diff --git a/config/services_migrations.php b/config/services_migrations.php
index a6bf983..56ae854 100644
--- a/config/services_migrations.php
+++ b/config/services_migrations.php
@@ -19,6 +19,7 @@
use Kraz\DoctrineContextBundle\Command\Doctrine\Migrations\SyncMetadataCommand;
use Kraz\DoctrineContextBundle\Command\Doctrine\Migrations\UpToDateCommand;
use Kraz\DoctrineContextBundle\Command\Doctrine\Migrations\VersionCommand;
+use Kraz\DoctrineContextBundle\Command\Doctrine\Strategy\MigrationStrategy;
use Symfony\Component\DependencyInjection\ContainerInterface;
return static function (ContainerConfigurator $container): void {
@@ -29,11 +30,13 @@
->set('doctrine.doctrine_context.dependency_factory', DependencyFactory::class)
->abstract()
+ ->set('doctrine.doctrine_context.strategy.migration', MigrationStrategy::class)
+
->set('doctrine_migrations.current_command.with_context', CurrentCommand::class)
->decorate('doctrine_migrations.current_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:migrations:current'])
@@ -41,7 +44,7 @@
->decorate('doctrine_migrations.diff_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:migrations:diff'])
@@ -49,7 +52,7 @@
->decorate('doctrine_migrations.dump_schema_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:migrations:dump-schema'])
@@ -57,7 +60,7 @@
->decorate('doctrine_migrations.execute_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:migrations:execute'])
@@ -65,7 +68,7 @@
->decorate('doctrine_migrations.generate_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:migrations:generate'])
@@ -73,7 +76,7 @@
->decorate('doctrine_migrations.latest_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:migrations:latest'])
@@ -81,7 +84,7 @@
->decorate('doctrine_migrations.versions_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:migrations:list'])
@@ -89,7 +92,7 @@
->decorate('doctrine_migrations.migrate_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:migrations:migrate'])
@@ -97,7 +100,7 @@
->decorate('doctrine_migrations.rollup_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:migrations:rollup'])
@@ -105,7 +108,7 @@
->decorate('doctrine_migrations.status_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:migrations:status'])
@@ -113,7 +116,7 @@
->decorate('doctrine_migrations.sync_metadata_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:migrations:sync-metadata-storage'])
@@ -121,7 +124,7 @@
->decorate('doctrine_migrations.up_to_date_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:migrations:up-to-date'])
@@ -129,7 +132,7 @@
->decorate('doctrine_migrations.version_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:migrations:version']);
};
diff --git a/config/services_orm.php b/config/services_orm.php
index dd221b0..973754d 100644
--- a/config/services_orm.php
+++ b/config/services_orm.php
@@ -7,15 +7,18 @@
use Kraz\DoctrineContextBundle\Command\Doctrine\Mapping\InfoCommand;
use Kraz\DoctrineContextBundle\Command\Doctrine\Schema\CreateSchemaCommand;
use Kraz\DoctrineContextBundle\Command\Doctrine\Schema\ValidateSchemaCommand;
+use Kraz\DoctrineContextBundle\Command\Doctrine\Strategy\EntityManagerStrategy;
use Symfony\Component\DependencyInjection\ContainerInterface;
return static function (ContainerConfigurator $container): void {
$container->services()
+ ->set('doctrine.doctrine_context.strategy.entity_manager', EntityManagerStrategy::class)
+
->set('doctrine.mapping_info_command.with_context', InfoCommand::class)
->decorate('doctrine.mapping_info_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:mapping:info'])
@@ -23,7 +26,7 @@
->decorate('doctrine.schema_create_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:schema:create'])
@@ -31,7 +34,7 @@
->decorate('doctrine.schema_validate_command', null, 0, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)
->args([
service('.inner'),
- service('doctrine.doctrine_context.configuration'),
+ service('doctrine.doctrine_context.context_runner'),
])
->tag('console.command', ['command' => 'doctrine:schema:validate']);
};
diff --git a/psalm.xml.dist b/psalm.xml.dist
index 9f95f42..6f7e884 100644
--- a/psalm.xml.dist
+++ b/psalm.xml.dist
@@ -37,12 +37,5 @@
-
-
-
-
-
-
-
diff --git a/src/Command/Doctrine/ContextRunner.php b/src/Command/Doctrine/ContextRunner.php
new file mode 100644
index 0000000..f316b6e
--- /dev/null
+++ b/src/Command/Doctrine/ContextRunner.php
@@ -0,0 +1,242 @@
+ */
+ private array $strategies = [];
+
+ public function __construct(private readonly Configuration $configuration)
+ {
+ }
+
+ public function addStrategy(CommandExecutionStrategy $strategy): void
+ {
+ $this->strategies[] = $strategy;
+ }
+
+ public function getConfiguration(): Configuration
+ {
+ return $this->configuration;
+ }
+
+ public function configure(Command $wrapper, Command $innerCommand): void
+ {
+ $name = $wrapper->getName() ?? $innerCommand->getName();
+ if ($name !== null) {
+ $wrapper->setName($name);
+ }
+
+ $wrapper->setDescription(($wrapper->getDescription() ?: $innerCommand->getDescription()) . ' [Doctrine Context]');
+ $wrapper->setAliases($wrapper->getAliases() ?: $innerCommand->getAliases());
+ $wrapper->setHelp($wrapper->getHelp() ?: $innerCommand->getHelp());
+ $nativeDefinition = $innerCommand->getNativeDefinition();
+ $wrapper->setDefinition($nativeDefinition);
+ $wrapper->addOption('ctx-isolation', null, InputOption::VALUE_NONE, 'Continue with the next context, if the current one fails.');
+ $wrapper->addOption('ctx-all', null, InputOption::VALUE_NONE, 'Run the command over all registered contexts (when explicit_context: true).');
+
+ foreach ($this->strategies as $strategy) {
+ if ($strategy->supports($innerCommand)) {
+ $strategy->configure($wrapper, $innerCommand);
+ break;
+ }
+ }
+ }
+
+ public function run(Command $wrapper, Command $innerCommand, InputInterface $input, OutputInterface $output): int
+ {
+ foreach ($this->strategies as $strategy) {
+ if ($strategy->supports($innerCommand)) {
+ return $strategy->execute($wrapper, $innerCommand, $this, $this->configuration, $input, $output);
+ }
+ }
+
+ throw new InvalidArgumentException(sprintf('Unsupported CLI command "%s"', $innerCommand::class));
+ }
+
+ /** @param array $list */
+ public function walkDoctrineContexts(callable $callback, array $list, InputInterface $input, OutputInterface $output): int
+ {
+ $result = Command::SUCCESS;
+ $ui = new SymfonyStyle($input, $output)->getErrorStyle();
+ $contextIsolation = $input->getOption('ctx-isolation');
+ $total = count($list);
+ while (count($list) > 0) {
+ $contextName = array_key_first($list);
+ $dependencyFactory = array_shift($list);
+ if ($total > 1) {
+ $ui->section(sprintf('%s: %s', $this->configuration->isEntityManager($contextName) ? 'Entity Manager' : 'Connection', $contextName));
+ }
+
+ try {
+ $cmdResult = $callback($input, $output, $contextName, $dependencyFactory);
+ } catch (Throwable $e) {
+ $ui->error($e->getMessage());
+ $cmdResult = Command::FAILURE;
+ }
+
+ if ($total > 1) {
+ $ui->newLine();
+ }
+
+ if ($cmdResult !== Command::SUCCESS) {
+ $result = $cmdResult;
+ if ($input->isInteractive() && count($list) > 0 && $ui->confirm('Do you want to proceed with the rest of the doctrine contexts?')) {
+ continue;
+ }
+
+ if (! $contextIsolation) {
+ break;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param (callable(DependencyFactory|string): bool)|null $filter
+ * @param string[] $targetEntityManagers
+ * @param string[] $targetConnectionNames
+ *
+ * @return array
+ */
+ public function filterDoctrineContexts(callable|null $filter = null, array $targetEntityManagers = [], array $targetConnectionNames = []): array
+ {
+ $targetEntityManagers = array_values(array_filter(array_map('trim', $targetEntityManagers)));
+ $targetConnectionNames = array_values(array_filter(array_map('trim', $targetConnectionNames)));
+
+ if (count($targetEntityManagers) > 0 && count($targetConnectionNames) > 0) {
+ throw new InvalidArgumentException('You can specify only one of the --em/--ems and --conn/--conns options.');
+ }
+
+ $targetNames = count($targetEntityManagers) > 0 ? $targetEntityManagers : $targetConnectionNames;
+ $isEntityManagerMode = count($targetEntityManagers) > 0;
+
+ // Build the full context pool (dependency factories + context-only entries)
+ $allContexts = $this->configuration->getDependencyFactories();
+ foreach ($this->configuration->getContextNames() as $name) {
+ if (! isset($allContexts[$name])) {
+ $allContexts[$name] = null;
+ }
+ }
+
+ // Select the requested subset or take all
+ if (count($targetNames) > 0) {
+ $list = [];
+ foreach ($targetNames as $name) {
+ if (array_key_exists($name, $allContexts)) {
+ $list[$name] = $allContexts[$name];
+ }
+ }
+ } else {
+ $list = $allContexts;
+ }
+
+ // Apply callable filter
+ if ($filter !== null) {
+ $filteredList = [];
+ foreach ($list as $contextName => $dependencyFactory) {
+ if (! call_user_func($filter, $dependencyFactory ?? $contextName)) {
+ continue;
+ }
+
+ $filteredList[$contextName] = $dependencyFactory;
+ }
+
+ $list = $filteredList;
+ }
+
+ // Throw for invalid/not-found targets
+ if (count($list) === 0 && count($targetNames) > 0) {
+ if ($isEntityManagerMode) {
+ throw new InvalidArgumentException(count($targetNames) === 1
+ ? sprintf('Unknown doctrine entity manager "%s" or it\'s not registered as doctrine context.', $targetNames[0])
+ : sprintf('Unknown doctrine entity managers "%s" or they are not registered as doctrine contexts.', implode('", "', $targetNames)));
+ }
+
+ throw new InvalidArgumentException(count($targetNames) === 1
+ ? sprintf('Unknown doctrine connection "%s" or it\'s not registered as doctrine context.', $targetNames[0])
+ : sprintf('Unknown doctrine connections "%s" or they are not registered as doctrine contexts.', implode('", "', $targetNames)));
+ }
+
+ ksort($list);
+
+ return $list;
+ }
+
+ /** @return string[] */
+ public function resolveArrayOption(InputInterface $input, string $name): array
+ {
+ if (! $input->hasOption($name)) {
+ return [];
+ }
+
+ $values = [];
+ foreach ((array) $input->getOption($name) as $value) {
+ foreach (explode(',', $value) as $part) {
+ $part = trim($part);
+ if ($part !== '') {
+ $values[] = $part;
+ }
+ }
+ }
+
+ return array_values(array_unique($values));
+ }
+
+ /** @param array $override */
+ public function createNewInput(Command $command, InputInterface $input, array $override): InputInterface
+ {
+ $definition = $command->getNativeDefinition();
+ $parameters = [];
+ foreach ($definition->getArguments() as $argument) {
+ if ($input->hasArgument($argument->getName())) {
+ $parameters[$argument->getName()] = $input->getArgument($argument->getName());
+ }
+ }
+
+ foreach ($definition->getOptions() as $option) {
+ if ($input->hasParameterOption('--' . $option->getName())) {
+ $parameters['--' . $option->getName()] = $input->getOption($option->getName());
+ }
+ }
+
+ $parameters = array_replace($parameters, $override);
+ $newInput = new ArrayInput($parameters);
+ $newInput->setInteractive($input->isInteractive());
+
+ return $newInput;
+ }
+}
diff --git a/src/Command/Doctrine/Database/CreateDatabaseCommand.php b/src/Command/Doctrine/Database/CreateDatabaseCommand.php
index 440e65d..432f8ec 100644
--- a/src/Command/Doctrine/Database/CreateDatabaseCommand.php
+++ b/src/Command/Doctrine/Database/CreateDatabaseCommand.php
@@ -5,37 +5,30 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Database;
use Doctrine\Bundle\DoctrineBundle\Command\CreateDatabaseDoctrineCommand;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class CreateDatabaseCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly CreateDatabaseDoctrineCommand $command,
- Configuration $configuration,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
- $this->addOption('conn', null, InputOption::VALUE_OPTIONAL, 'The name of the connection to use (alias for --connection).');
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/DoctrineContextTrait.php b/src/Command/Doctrine/DoctrineContextTrait.php
deleted file mode 100644
index b47909b..0000000
--- a/src/Command/Doctrine/DoctrineContextTrait.php
+++ /dev/null
@@ -1,334 +0,0 @@
-getName() ?? $command->getName();
- if ($name !== null) {
- $this->setName($name);
- }
-
- $this->setDescription(($this->getDescription() ?: $command->getDescription()) . ' [Doctrine Context]');
- $this->setAliases($this->getAliases() ?: $command->getAliases());
- $this->setHelp($this->getHelp() ?: $command->getHelp());
- $nativeDefinition = $command->getNativeDefinition();
- $this->setDefinition($nativeDefinition);
- $this->addOption('ctx-isolation', null, InputOption::VALUE_NONE, 'Continue with the next context, if the current one fails.');
- $this->addOption('ctx-all', null, InputOption::VALUE_NONE, 'Run the command over all registered contexts (when explicit_context: true).');
-
- if ($nativeDefinition->hasOption('em')) {
- $this->addOption('ems', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'The names of the entity managers to use (multi-value alias for --em).');
- }
-
- if ($nativeDefinition->hasOption('conn')) {
- $this->addOption('conns', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'The names of the connections to use (multi-value alias for --conn).');
- }
-
- if ($nativeDefinition->hasOption('connection')) {
- $this->addOption('connections', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'The names of the connections to use (multi-value alias for --connection).');
- $this->addOption('conns', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'The names of the connections to use (multi-value alias for --conn).');
- }
- }
-
- private function runAs(Command $command, InputInterface $input, OutputInterface $output): int
- {
- assert($this instanceof Command);
-
- $ctxAll = (bool) $input->getOption('ctx-all');
-
- if ($command instanceof AbstractEntityManagerCommand) {
- $emOption = trim((string) $input->getOption('em'));
- $emsOption = $this->resolveArrayOption($input, 'ems');
-
- if ($emOption !== '' && count($emsOption) > 0) {
- throw new InvalidArgumentException('You can specify only one of the --em and --ems options.');
- }
-
- $targetEntityManagers = $emOption !== '' ? [$emOption] : $emsOption;
-
- if ($this->configuration->isExplicitContext() && count($targetEntityManagers) === 0 && ! $ctxAll) {
- throw new InvalidArgumentException('Explicit context is required. Specify a context via --em, --ems, or use --ctx-all to run over all contexts.');
- }
-
- $list = $this->filterDoctrineContexts(fn (DependencyFactory|string $ctx): bool => is_string($ctx) ? $this->configuration->isEntityManager($ctx) : $ctx->hasEntityManager(), $targetEntityManagers);
-
- return $this->walkDoctrineContexts(function (InputInterface $input, OutputInterface $output, string $em) use ($command) {
- $command->setDefinition($this->getNativeDefinition());
- $command->setApplication($this->getApplication());
- $newInput = $this->createNewInput($command, $input, ['--em' => $em]);
-
- return $command->run($newInput, $output);
- }, $list, $input, $output);
- }
-
- if ($command instanceof AbstractDoctrineMigrationCommand) {
- $emOption = trim((string) $input->getOption('em'));
- $connOption = trim((string) $input->getOption('conn'));
- $emsOption = $this->resolveArrayOption($input, 'ems');
- $connsOption = $this->resolveArrayOption($input, 'conns');
-
- if ($emOption !== '' && count($emsOption) > 0) {
- throw new InvalidArgumentException('You can specify only one of the --em and --ems options.');
- }
-
- if ($connOption !== '' && count($connsOption) > 0) {
- throw new InvalidArgumentException('You can specify only one of the --conn and --conns options.');
- }
-
- $targetEntityManagers = $emOption !== '' ? [$emOption] : $emsOption;
- $targetConnectionNames = $connOption !== '' ? [$connOption] : $connsOption;
-
- if ($this->configuration->isExplicitContext() && count($targetEntityManagers) === 0 && count($targetConnectionNames) === 0 && ! $ctxAll) {
- throw new InvalidArgumentException('Explicit context is required. Specify a context via --em, --ems, --conn, --conns, or use --ctx-all to run over all contexts.');
- }
-
- $list = $this->filterDoctrineContexts(null, $targetEntityManagers, $targetConnectionNames);
-
- return $this->walkDoctrineContexts(function (InputInterface $input, OutputInterface $output, string $contextName, DependencyFactory $dependencyFactory) use ($command) {
- $command = new ReflectionClass($command)->newInstance($dependencyFactory);
- $command->setDefinition($this->getNativeDefinition());
- $command->setApplication($this->getApplication());
-
- return $command->run($input, $output);
- }, $list, $input, $output);
- }
-
- if ($command->getNativeDefinition()->hasOption('connection')) {
- $connectionOption = trim((string) $input->getOption('connection'));
- $connOption = trim((string) $input->getOption('conn'));
- $connectionsOption = $this->resolveArrayOption($input, 'connections');
- $connsOption = $this->resolveArrayOption($input, 'conns');
-
- if ($connectionOption !== '' && $connOption !== '') {
- throw new InvalidArgumentException('You can specify only one of the --connection and --conn options.');
- }
-
- if (count($connectionsOption) > 0 && count($connsOption) > 0) {
- throw new InvalidArgumentException('You can specify only one of the --connections and --conns options.');
- }
-
- $singleTarget = $connectionOption ?: ($connOption ?: null);
- $arrayTargets = count($connectionsOption) > 0 ? $connectionsOption : $connsOption;
-
- if ($singleTarget !== null && count($arrayTargets) > 0) {
- throw new InvalidArgumentException('You can specify only one of the --connection/--conn and --connections/--conns options.');
- }
-
- $targetConnectionNames = $singleTarget !== null ? [$singleTarget] : $arrayTargets;
-
- if ($this->configuration->isExplicitContext() && count($targetConnectionNames) === 0 && ! $ctxAll) {
- throw new InvalidArgumentException('Explicit context is required. Specify a context via --connection, --connections, --conn, --conns, or use --ctx-all to run over all contexts.');
- }
-
- $list = $this->filterDoctrineContexts(null, [], $targetConnectionNames);
-
- return $this->walkDoctrineContexts(function (InputInterface $input, OutputInterface $output, string $contextName) use ($command) {
- $command->setDefinition($this->getNativeDefinition());
- $command->setApplication($this->getApplication());
- $newInput = $this->createNewInput($command, $input, ['--connection' => $contextName]);
-
- return $command->run($newInput, $output);
- }, $list, $input, $output);
- }
-
- throw new InvalidArgumentException(sprintf('Unsupported CLI command "%s"', $command::class));
- }
-
- /** @param array $list */
- private function walkDoctrineContexts(callable $callback, array $list, InputInterface $input, OutputInterface $output): int
- {
- $result = Command::SUCCESS;
- $ui = new SymfonyStyle($input, $output)->getErrorStyle();
- $contextIsolation = $input->getOption('ctx-isolation');
- $total = count($list);
- while (count($list) > 0) {
- $contextName = array_key_first($list);
- $dependencyFactory = array_shift($list);
- if ($total > 1) {
- $ui->section(sprintf('%s: %s', $this->configuration->isEntityManager($contextName) ? 'Entity Manager' : 'Connection', $contextName));
- }
-
- try {
- $cmdResult = $callback($input, $output, $contextName, $dependencyFactory);
- } catch (Throwable $e) {
- $ui->error($e->getMessage());
- $cmdResult = Command::FAILURE;
- }
-
- if ($total > 1) {
- $ui->newLine();
- }
-
- if ($cmdResult !== Command::SUCCESS) {
- $result = $cmdResult;
- if ($input->isInteractive() && count($list) > 0 && $ui->confirm('Do you want to proceed with the rest of the doctrine contexts?')) {
- continue;
- }
-
- if (! $contextIsolation) {
- break;
- }
- }
- }
-
- return $result;
- }
-
- /**
- * @param callable(DependencyFactory|string): bool|null $filter
- * @param string[] $targetEntityManagers
- * @param string[] $targetConnectionNames
- *
- * @return array
- */
- private function filterDoctrineContexts(callable|null $filter = null, array $targetEntityManagers = [], array $targetConnectionNames = []): array
- {
- $targetEntityManagers = array_values(array_filter(array_map('trim', $targetEntityManagers)));
- $targetConnectionNames = array_values(array_filter(array_map('trim', $targetConnectionNames)));
-
- if (count($targetEntityManagers) > 0 && count($targetConnectionNames) > 0) {
- throw new InvalidArgumentException('You can specify only one of the --em/--ems and --conn/--conns options.');
- }
-
- $targetNames = count($targetEntityManagers) > 0 ? $targetEntityManagers : $targetConnectionNames;
- $isEntityManagerMode = count($targetEntityManagers) > 0;
-
- // Build the full context pool (dependency factories + context-only entries)
- $allContexts = $this->configuration->getDependencyFactories();
- foreach ($this->configuration->getContextNames() as $name) {
- if (! isset($allContexts[$name])) {
- $allContexts[$name] = null;
- }
- }
-
- // Select the requested subset or take all
- if (count($targetNames) > 0) {
- $list = [];
- foreach ($targetNames as $name) {
- if (array_key_exists($name, $allContexts)) {
- $list[$name] = $allContexts[$name];
- }
- }
- } else {
- $list = $allContexts;
- }
-
- // Apply callable filter
- if ($filter !== null) {
- $filteredList = [];
- foreach ($list as $contextName => $dependencyFactory) {
- if (! call_user_func($filter, $dependencyFactory ?? $contextName)) {
- continue;
- }
-
- $filteredList[$contextName] = $dependencyFactory;
- }
-
- $list = $filteredList;
- }
-
- // Throw for invalid/not-found targets
- if (count($list) === 0 && count($targetNames) > 0) {
- if ($isEntityManagerMode) {
- throw new InvalidArgumentException(count($targetNames) === 1
- ? sprintf('Unknown doctrine entity manager "%s" or it\'s not registered as doctrine context.', $targetNames[0])
- : sprintf('Unknown doctrine entity managers "%s" or they are not registered as doctrine contexts.', implode('", "', $targetNames)));
- }
-
- throw new InvalidArgumentException(count($targetNames) === 1
- ? sprintf('Unknown doctrine connection "%s" or it\'s not registered as doctrine context.', $targetNames[0])
- : sprintf('Unknown doctrine connections "%s" or they are not registered as doctrine contexts.', implode('", "', $targetNames)));
- }
-
- ksort($list);
-
- return $list;
- }
-
- /** @return string[] */
- private function resolveArrayOption(InputInterface $input, string $name): array
- {
- if (! $input->hasOption($name)) {
- return [];
- }
-
- $values = [];
- foreach ((array) $input->getOption($name) as $value) {
- foreach (explode(',', $value) as $part) {
- $part = trim($part);
- if ($part !== '') {
- $values[] = $part;
- }
- }
- }
-
- return array_values(array_unique($values));
- }
-
- /** @param array $override */
- private function createNewInput(Command $command, InputInterface $input, array $override): InputInterface
- {
- $definition = $command->getNativeDefinition();
- $parameters = [];
- foreach ($definition->getArguments() as $argument) {
- if ($input->hasArgument($argument->getName())) {
- $parameters[$argument->getName()] = $input->getArgument($argument->getName());
- }
- }
-
- foreach ($definition->getOptions() as $option) {
- if ($input->hasParameterOption('--' . $option->getName())) {
- $parameters['--' . $option->getName()] = $input->getOption($option->getName());
- }
- }
-
- $parameters = array_replace($parameters, $override);
- $newInput = new ArrayInput($parameters);
- $newInput->setInteractive($input->isInteractive());
-
- return $newInput;
- }
-}
diff --git a/src/Command/Doctrine/Mapping/InfoCommand.php b/src/Command/Doctrine/Mapping/InfoCommand.php
index ed9a9ca..d3842c2 100644
--- a/src/Command/Doctrine/Mapping/InfoCommand.php
+++ b/src/Command/Doctrine/Mapping/InfoCommand.php
@@ -4,9 +4,7 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Mapping;
-use Doctrine\ORM\Tools\Console\EntityManagerProvider;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -14,28 +12,22 @@
class InfoCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly \Doctrine\ORM\Tools\Console\Command\InfoCommand $command,
- Configuration $configuration,
- EntityManagerProvider|null $entityManagerProvider = null,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
- $this->entityManagerProvider = $entityManagerProvider;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/Migrations/CurrentCommand.php b/src/Command/Doctrine/Migrations/CurrentCommand.php
index 5c87b84..a00be76 100644
--- a/src/Command/Doctrine/Migrations/CurrentCommand.php
+++ b/src/Command/Doctrine/Migrations/CurrentCommand.php
@@ -4,8 +4,7 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Migrations;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -13,26 +12,22 @@
class CurrentCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly \Doctrine\Migrations\Tools\Console\Command\CurrentCommand $command,
- Configuration $configuration,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/Migrations/DiffCommand.php b/src/Command/Doctrine/Migrations/DiffCommand.php
index 863b0b1..3f0f8ac 100644
--- a/src/Command/Doctrine/Migrations/DiffCommand.php
+++ b/src/Command/Doctrine/Migrations/DiffCommand.php
@@ -4,8 +4,7 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Migrations;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -13,26 +12,22 @@
class DiffCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly \Doctrine\Migrations\Tools\Console\Command\DiffCommand $command,
- Configuration $configuration,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/Migrations/DumpSchemaCommand.php b/src/Command/Doctrine/Migrations/DumpSchemaCommand.php
index c8d7ed6..aaddc83 100644
--- a/src/Command/Doctrine/Migrations/DumpSchemaCommand.php
+++ b/src/Command/Doctrine/Migrations/DumpSchemaCommand.php
@@ -4,8 +4,7 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Migrations;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -13,26 +12,22 @@
class DumpSchemaCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly \Doctrine\Migrations\Tools\Console\Command\DumpSchemaCommand $command,
- Configuration $configuration,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/Migrations/ExecuteCommand.php b/src/Command/Doctrine/Migrations/ExecuteCommand.php
index 9299503..3a2a57e 100644
--- a/src/Command/Doctrine/Migrations/ExecuteCommand.php
+++ b/src/Command/Doctrine/Migrations/ExecuteCommand.php
@@ -4,8 +4,7 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Migrations;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -13,26 +12,22 @@
class ExecuteCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly \Doctrine\Migrations\Tools\Console\Command\ExecuteCommand $command,
- Configuration $configuration,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/Migrations/GenerateCommand.php b/src/Command/Doctrine/Migrations/GenerateCommand.php
index 09a7ec8..a68fc9a 100644
--- a/src/Command/Doctrine/Migrations/GenerateCommand.php
+++ b/src/Command/Doctrine/Migrations/GenerateCommand.php
@@ -4,8 +4,7 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Migrations;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -13,26 +12,22 @@
class GenerateCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly \Doctrine\Migrations\Tools\Console\Command\GenerateCommand $command,
- Configuration $configuration,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/Migrations/LatestCommand.php b/src/Command/Doctrine/Migrations/LatestCommand.php
index 24cdfff..6c11c2e 100644
--- a/src/Command/Doctrine/Migrations/LatestCommand.php
+++ b/src/Command/Doctrine/Migrations/LatestCommand.php
@@ -4,8 +4,7 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Migrations;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -13,26 +12,22 @@
class LatestCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly \Doctrine\Migrations\Tools\Console\Command\LatestCommand $command,
- Configuration $configuration,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/Migrations/ListCommand.php b/src/Command/Doctrine/Migrations/ListCommand.php
index 60be8cd..1a1936e 100644
--- a/src/Command/Doctrine/Migrations/ListCommand.php
+++ b/src/Command/Doctrine/Migrations/ListCommand.php
@@ -4,8 +4,7 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Migrations;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -13,26 +12,22 @@
class ListCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly \Doctrine\Migrations\Tools\Console\Command\ListCommand $command,
- Configuration $configuration,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/Migrations/MigrateCommand.php b/src/Command/Doctrine/Migrations/MigrateCommand.php
index 571bc65..5858fbf 100644
--- a/src/Command/Doctrine/Migrations/MigrateCommand.php
+++ b/src/Command/Doctrine/Migrations/MigrateCommand.php
@@ -4,8 +4,7 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Migrations;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -13,26 +12,22 @@
class MigrateCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly \Doctrine\Migrations\Tools\Console\Command\MigrateCommand $command,
- Configuration $configuration,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/Migrations/RollupCommand.php b/src/Command/Doctrine/Migrations/RollupCommand.php
index 03d6626..82dfe82 100644
--- a/src/Command/Doctrine/Migrations/RollupCommand.php
+++ b/src/Command/Doctrine/Migrations/RollupCommand.php
@@ -4,8 +4,7 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Migrations;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -13,26 +12,22 @@
class RollupCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly \Doctrine\Migrations\Tools\Console\Command\RollupCommand $command,
- Configuration $configuration,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/Migrations/StatusCommand.php b/src/Command/Doctrine/Migrations/StatusCommand.php
index 83ad182..c4e99e8 100644
--- a/src/Command/Doctrine/Migrations/StatusCommand.php
+++ b/src/Command/Doctrine/Migrations/StatusCommand.php
@@ -4,8 +4,7 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Migrations;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -13,26 +12,22 @@
class StatusCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly \Doctrine\Migrations\Tools\Console\Command\StatusCommand $command,
- Configuration $configuration,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/Migrations/SyncMetadataCommand.php b/src/Command/Doctrine/Migrations/SyncMetadataCommand.php
index 4cdad9b..107d6c6 100644
--- a/src/Command/Doctrine/Migrations/SyncMetadataCommand.php
+++ b/src/Command/Doctrine/Migrations/SyncMetadataCommand.php
@@ -4,8 +4,7 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Migrations;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -13,26 +12,22 @@
class SyncMetadataCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly \Doctrine\Migrations\Tools\Console\Command\SyncMetadataCommand $command,
- Configuration $configuration,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/Migrations/UpToDateCommand.php b/src/Command/Doctrine/Migrations/UpToDateCommand.php
index 57537e4..6fa3e5d 100644
--- a/src/Command/Doctrine/Migrations/UpToDateCommand.php
+++ b/src/Command/Doctrine/Migrations/UpToDateCommand.php
@@ -4,8 +4,7 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Migrations;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -13,26 +12,22 @@
class UpToDateCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly \Doctrine\Migrations\Tools\Console\Command\UpToDateCommand $command,
- Configuration $configuration,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/Migrations/VersionCommand.php b/src/Command/Doctrine/Migrations/VersionCommand.php
index f4dfd21..c76d59b 100644
--- a/src/Command/Doctrine/Migrations/VersionCommand.php
+++ b/src/Command/Doctrine/Migrations/VersionCommand.php
@@ -4,8 +4,7 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Migrations;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -13,26 +12,22 @@
class VersionCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly \Doctrine\Migrations\Tools\Console\Command\VersionCommand $command,
- Configuration $configuration,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/Schema/CreateSchemaCommand.php b/src/Command/Doctrine/Schema/CreateSchemaCommand.php
index 0d38bf3..76750d4 100644
--- a/src/Command/Doctrine/Schema/CreateSchemaCommand.php
+++ b/src/Command/Doctrine/Schema/CreateSchemaCommand.php
@@ -5,9 +5,7 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Schema;
use Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand;
-use Doctrine\ORM\Tools\Console\EntityManagerProvider;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -15,28 +13,22 @@
class CreateSchemaCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly CreateCommand $command,
- Configuration $configuration,
- EntityManagerProvider|null $entityManagerProvider = null,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
- $this->entityManagerProvider = $entityManagerProvider;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/Schema/ValidateSchemaCommand.php b/src/Command/Doctrine/Schema/ValidateSchemaCommand.php
index d2e546c..43aa5da 100644
--- a/src/Command/Doctrine/Schema/ValidateSchemaCommand.php
+++ b/src/Command/Doctrine/Schema/ValidateSchemaCommand.php
@@ -4,9 +4,7 @@
namespace Kraz\DoctrineContextBundle\Command\Doctrine\Schema;
-use Doctrine\ORM\Tools\Console\EntityManagerProvider;
-use Kraz\DoctrineContextBundle\Command\Doctrine\DoctrineContextTrait;
-use Kraz\DoctrineContextBundle\Configuration\Configuration;
+use Kraz\DoctrineContextBundle\Command\Doctrine\ContextRunner;
use Override;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -14,28 +12,22 @@
class ValidateSchemaCommand extends Command
{
- use DoctrineContextTrait;
-
public function __construct(
private readonly \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand $command,
- Configuration $configuration,
- EntityManagerProvider|null $entityManagerProvider = null,
+ private readonly ContextRunner $contextRunner,
) {
- $this->configuration = $configuration;
- $this->entityManagerProvider = $entityManagerProvider;
-
parent::__construct($this->command->getName(), $this->command->getCode());
}
#[Override]
protected function configure(): void
{
- $this->configureAs($this->command);
+ $this->contextRunner->configure($this, $this->command);
}
#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
- return $this->runAs($this->command, $input, $output);
+ return $this->contextRunner->run($this, $this->command, $input, $output);
}
}
diff --git a/src/Command/Doctrine/Strategy/CommandExecutionStrategy.php b/src/Command/Doctrine/Strategy/CommandExecutionStrategy.php
new file mode 100644
index 0000000..d7035a8
--- /dev/null
+++ b/src/Command/Doctrine/Strategy/CommandExecutionStrategy.php
@@ -0,0 +1,20 @@
+getNativeDefinition()->hasOption('connection');
+ }
+
+ #[Override]
+ public function configure(Command $wrapper, Command $innerCommand): void
+ {
+ $wrapper->addOption('conn', null, InputOption::VALUE_OPTIONAL, 'The name of the connection to use (alias for --connection).');
+ $wrapper->addOption('connections', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'The names of the connections to use (multi-value alias for --connection).');
+ $wrapper->addOption('conns', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'The names of the connections to use (multi-value alias for --conn).');
+ }
+
+ #[Override]
+ public function execute(Command $wrapper, Command $innerCommand, ContextRunner $contextRunner, Configuration $configuration, InputInterface $input, OutputInterface $output): int
+ {
+ $connectionOption = trim((string) $input->getOption('connection'));
+ $connOption = trim((string) $input->getOption('conn'));
+ $connectionsOption = $contextRunner->resolveArrayOption($input, 'connections');
+ $connsOption = $contextRunner->resolveArrayOption($input, 'conns');
+
+ if ($connectionOption !== '' && $connOption !== '') {
+ throw new InvalidArgumentException('You can specify only one of the --connection and --conn options.');
+ }
+
+ if (count($connectionsOption) > 0 && count($connsOption) > 0) {
+ throw new InvalidArgumentException('You can specify only one of the --connections and --conns options.');
+ }
+
+ $singleTarget = $connectionOption ?: ($connOption ?: null);
+ $arrayTargets = count($connectionsOption) > 0 ? $connectionsOption : $connsOption;
+
+ if ($singleTarget !== null && count($arrayTargets) > 0) {
+ throw new InvalidArgumentException('You can specify only one of the --connection/--conn and --connections/--conns options.');
+ }
+
+ $targetConnectionNames = $singleTarget !== null ? [$singleTarget] : $arrayTargets;
+
+ if ($configuration->isExplicitContext() && count($targetConnectionNames) === 0 && ! $input->getOption('ctx-all')) {
+ throw new InvalidArgumentException('Explicit context is required. Specify a context via --connection, --connections, --conn, --conns, or use --ctx-all to run over all contexts.');
+ }
+
+ $list = $contextRunner->filterDoctrineContexts(null, [], $targetConnectionNames);
+
+ return $contextRunner->walkDoctrineContexts(static function (InputInterface $input, OutputInterface $output, string $contextName) use ($wrapper, $innerCommand, $contextRunner) {
+ $innerCommand->setDefinition($wrapper->getNativeDefinition());
+ $innerCommand->setApplication($wrapper->getApplication());
+ $newInput = $contextRunner->createNewInput($innerCommand, $input, ['--connection' => $contextName]);
+
+ return $innerCommand->run($newInput, $output);
+ }, $list, $input, $output);
+ }
+}
diff --git a/src/Command/Doctrine/Strategy/EntityManagerStrategy.php b/src/Command/Doctrine/Strategy/EntityManagerStrategy.php
new file mode 100644
index 0000000..01638c7
--- /dev/null
+++ b/src/Command/Doctrine/Strategy/EntityManagerStrategy.php
@@ -0,0 +1,62 @@
+addOption('ems', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'The names of the entity managers to use (multi-value alias for --em).');
+ }
+
+ #[Override]
+ public function execute(Command $wrapper, Command $innerCommand, ContextRunner $contextRunner, Configuration $configuration, InputInterface $input, OutputInterface $output): int
+ {
+ $emOption = trim((string) $input->getOption('em'));
+ $emsOption = $contextRunner->resolveArrayOption($input, 'ems');
+
+ if ($emOption !== '' && count($emsOption) > 0) {
+ throw new InvalidArgumentException('You can specify only one of the --em and --ems options.');
+ }
+
+ $targetEntityManagers = $emOption !== '' ? [$emOption] : $emsOption;
+
+ if ($configuration->isExplicitContext() && count($targetEntityManagers) === 0 && ! $input->getOption('ctx-all')) {
+ throw new InvalidArgumentException('Explicit context is required. Specify a context via --em, --ems, or use --ctx-all to run over all contexts.');
+ }
+
+ $list = $contextRunner->filterDoctrineContexts(static fn (DependencyFactory|string $ctx): bool => is_string($ctx) ? $configuration->isEntityManager($ctx) : $ctx->hasEntityManager(), $targetEntityManagers);
+
+ return $contextRunner->walkDoctrineContexts(static function (InputInterface $input, OutputInterface $output, string $em) use ($wrapper, $innerCommand, $contextRunner) {
+ $innerCommand->setDefinition($wrapper->getNativeDefinition());
+ $innerCommand->setApplication($wrapper->getApplication());
+ $newInput = $contextRunner->createNewInput($innerCommand, $input, ['--em' => $em]);
+
+ return $innerCommand->run($newInput, $output);
+ }, $list, $input, $output);
+ }
+}
diff --git a/src/Command/Doctrine/Strategy/MigrationStrategy.php b/src/Command/Doctrine/Strategy/MigrationStrategy.php
new file mode 100644
index 0000000..285a8ef
--- /dev/null
+++ b/src/Command/Doctrine/Strategy/MigrationStrategy.php
@@ -0,0 +1,77 @@
+getNativeDefinition();
+
+ if ($nativeDefinition->hasOption('em')) {
+ $wrapper->addOption('ems', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'The names of the entity managers to use (multi-value alias for --em).');
+ }
+
+ if ($nativeDefinition->hasOption('conn')) {
+ $wrapper->addOption('conns', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'The names of the connections to use (multi-value alias for --conn).');
+ }
+ }
+
+ #[Override]
+ public function execute(Command $wrapper, Command $innerCommand, ContextRunner $contextRunner, Configuration $configuration, InputInterface $input, OutputInterface $output): int
+ {
+ $emOption = trim((string) $input->getOption('em'));
+ $connOption = trim((string) $input->getOption('conn'));
+ $emsOption = $contextRunner->resolveArrayOption($input, 'ems');
+ $connsOption = $contextRunner->resolveArrayOption($input, 'conns');
+
+ if ($emOption !== '' && count($emsOption) > 0) {
+ throw new InvalidArgumentException('You can specify only one of the --em and --ems options.');
+ }
+
+ if ($connOption !== '' && count($connsOption) > 0) {
+ throw new InvalidArgumentException('You can specify only one of the --conn and --conns options.');
+ }
+
+ $targetEntityManagers = $emOption !== '' ? [$emOption] : $emsOption;
+ $targetConnectionNames = $connOption !== '' ? [$connOption] : $connsOption;
+
+ if ($configuration->isExplicitContext() && count($targetEntityManagers) === 0 && count($targetConnectionNames) === 0 && ! $input->getOption('ctx-all')) {
+ throw new InvalidArgumentException('Explicit context is required. Specify a context via --em, --ems, --conn, --conns, or use --ctx-all to run over all contexts.');
+ }
+
+ $list = $contextRunner->filterDoctrineContexts(null, $targetEntityManagers, $targetConnectionNames);
+
+ return $contextRunner->walkDoctrineContexts(static function (InputInterface $input, OutputInterface $output, string $contextName, DependencyFactory $dependencyFactory) use ($wrapper, $innerCommand) {
+ $command = new ReflectionClass($innerCommand)->newInstance($dependencyFactory);
+ $command->setDefinition($wrapper->getNativeDefinition());
+ $command->setApplication($wrapper->getApplication());
+
+ return $command->run($input, $output);
+ }, $list, $input, $output);
+ }
+}
diff --git a/src/DoctrineContextBundle.php b/src/DoctrineContextBundle.php
index 8fb8020..6a6a2dc 100644
--- a/src/DoctrineContextBundle.php
+++ b/src/DoctrineContextBundle.php
@@ -192,6 +192,8 @@ public function loadExtension(array $config, ContainerConfigurator $container, C
$container->import('../config/services_orm.php');
}
+ $this->wireStrategies($builder);
+
$builder
->getDefinition('doctrine.doctrine_context.configuration')
->addMethodCall('setExplicitContext', [$config['explicit_context']]);
@@ -213,6 +215,21 @@ public function loadExtension(array $config, ContainerConfigurator $container, C
}
}
+ private function wireStrategies(ContainerBuilder $builder): void
+ {
+ $contextRunner = $builder->getDefinition('doctrine.doctrine_context.context_runner');
+
+ if ($builder->hasDefinition('doctrine.doctrine_context.strategy.entity_manager')) {
+ $contextRunner->addMethodCall('addStrategy', [new Reference('doctrine.doctrine_context.strategy.entity_manager')]);
+ }
+
+ if ($builder->hasDefinition('doctrine.doctrine_context.strategy.migration')) {
+ $contextRunner->addMethodCall('addStrategy', [new Reference('doctrine.doctrine_context.strategy.migration')]);
+ }
+
+ $contextRunner->addMethodCall('addStrategy', [new Reference('doctrine.doctrine_context.strategy.connection')]);
+ }
+
/** @param array $config */
private function loadContextConfiguration(string $name, array $config, ContainerBuilder $builder, bool $isEntityManager): void
{
diff --git a/tests/Unit/CommandExecutionStrategyTest.php b/tests/Unit/CommandExecutionStrategyTest.php
new file mode 100644
index 0000000..b1ed15e
--- /dev/null
+++ b/tests/Unit/CommandExecutionStrategyTest.php
@@ -0,0 +1,127 @@
+markTestSkipped('doctrine/orm is not installed');
+ }
+
+ $strategy = new EntityManagerStrategy();
+ $command = new DoctrineValidateSchemaCommand($this->createStub(EntityManagerProvider::class));
+
+ self::assertTrue($strategy->supports($command));
+ }
+
+ public function testEntityManagerStrategyRejectsMigrationCommand(): void
+ {
+ if (! class_exists(DoctrineMigrateCommand::class)) {
+ $this->markTestSkipped('doctrine/migrations is not installed');
+ }
+
+ $strategy = new EntityManagerStrategy();
+ $command = new DoctrineMigrateCommand($this->createStub(DependencyFactory::class));
+
+ self::assertFalse($strategy->supports($command));
+ }
+
+ public function testEntityManagerStrategyRejectsPlainCommand(): void
+ {
+ $strategy = new EntityManagerStrategy();
+ $command = new Command('test:plain');
+
+ self::assertFalse($strategy->supports($command));
+ }
+
+ public function testMigrationStrategySupportsDoctrineCommand(): void
+ {
+ if (! class_exists(DoctrineMigrateCommand::class)) {
+ $this->markTestSkipped('doctrine/migrations is not installed');
+ }
+
+ $strategy = new MigrationStrategy();
+ $command = new DoctrineMigrateCommand($this->createStub(DependencyFactory::class));
+
+ self::assertTrue($strategy->supports($command));
+ }
+
+ public function testMigrationStrategyRejectsEntityManagerCommand(): void
+ {
+ if (! interface_exists(EntityManagerProvider::class)) {
+ $this->markTestSkipped('doctrine/orm is not installed');
+ }
+
+ $strategy = new MigrationStrategy();
+ $command = new DoctrineValidateSchemaCommand($this->createStub(EntityManagerProvider::class));
+
+ self::assertFalse($strategy->supports($command));
+ }
+
+ public function testMigrationStrategyRejectsPlainCommand(): void
+ {
+ $strategy = new MigrationStrategy();
+ $command = new Command('test:plain');
+
+ self::assertFalse($strategy->supports($command));
+ }
+
+ public function testConnectionStrategySupportsCommandWithConnectionOption(): void
+ {
+ $strategy = new ConnectionStrategy();
+ $command = new Command('test:with-connection');
+ $command->addOption('connection', null, InputOption::VALUE_OPTIONAL);
+
+ self::assertTrue($strategy->supports($command));
+ }
+
+ public function testConnectionStrategyRejectsCommandWithoutConnectionOption(): void
+ {
+ $strategy = new ConnectionStrategy();
+ $command = new Command('test:no-connection');
+
+ self::assertFalse($strategy->supports($command));
+ }
+
+ public function testConnectionStrategyRejectsEntityManagerCommand(): void
+ {
+ if (! interface_exists(EntityManagerProvider::class)) {
+ $this->markTestSkipped('doctrine/orm is not installed');
+ }
+
+ $strategy = new ConnectionStrategy();
+ $command = new DoctrineValidateSchemaCommand($this->createStub(EntityManagerProvider::class));
+
+ self::assertFalse($strategy->supports($command));
+ }
+
+ public function testConnectionStrategyRejectsMigrationCommand(): void
+ {
+ if (! class_exists(DoctrineMigrateCommand::class)) {
+ $this->markTestSkipped('doctrine/migrations is not installed');
+ }
+
+ $strategy = new ConnectionStrategy();
+ $command = new DoctrineMigrateCommand($this->createStub(DependencyFactory::class));
+
+ self::assertFalse($strategy->supports($command));
+ }
+}
diff --git a/tests/Unit/ContextRunnerTest.php b/tests/Unit/ContextRunnerTest.php
new file mode 100644
index 0000000..d033fd2
--- /dev/null
+++ b/tests/Unit/ContextRunnerTest.php
@@ -0,0 +1,121 @@
+expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Unsupported CLI command');
+
+ $runner->run($command, $command, new ArrayInput([]), new NullOutput());
+ }
+
+ public function testRunAsThrowsWhenNoStrategiesRegistered(): void
+ {
+ $runner = new ContextRunner(new Configuration());
+ $command = new Command('test:any');
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Unsupported CLI command');
+
+ $runner->run($command, $command, new ArrayInput([]), new NullOutput());
+ }
+
+ public function testRunAsDelegatesToFirstMatchingStrategy(): void
+ {
+ $runner = new ContextRunner(new Configuration());
+
+ $nonMatching = $this->createMock(CommandExecutionStrategy::class);
+ $nonMatching->method('supports')->willReturn(false);
+ $nonMatching->expects($this->never())->method('execute');
+
+ $matching = $this->createMock(CommandExecutionStrategy::class);
+ $matching->method('supports')->willReturn(true);
+ $matching->expects($this->once())->method('execute')->willReturn(Command::SUCCESS);
+
+ $runner->addStrategy($nonMatching);
+ $runner->addStrategy($matching);
+
+ $command = new Command('test:matched');
+ $result = $runner->run($command, $command, new ArrayInput([]), new NullOutput());
+
+ self::assertSame(Command::SUCCESS, $result);
+ }
+
+ public function testFirstMatchingStrategyWinsWhenMultipleMatch(): void
+ {
+ $runner = new ContextRunner(new Configuration());
+
+ $first = $this->createMock(CommandExecutionStrategy::class);
+ $first->method('supports')->willReturn(true);
+ $first->expects($this->once())->method('execute')->willReturn(Command::SUCCESS);
+
+ $second = $this->createMock(CommandExecutionStrategy::class);
+ $second->method('supports')->willReturn(true);
+ $second->expects($this->never())->method('execute');
+
+ $runner->addStrategy($first);
+ $runner->addStrategy($second);
+
+ $command = new Command('test:both-match');
+ $result = $runner->run($command, $command, new ArrayInput([]), new NullOutput());
+
+ self::assertSame(Command::SUCCESS, $result);
+ }
+
+ public function testConfigureAsDelegatesToMatchingStrategy(): void
+ {
+ $runner = new ContextRunner(new Configuration());
+
+ $nonMatching = $this->createMock(CommandExecutionStrategy::class);
+ $nonMatching->method('supports')->willReturn(false);
+ $nonMatching->expects($this->never())->method('configure');
+
+ $matching = $this->createMock(CommandExecutionStrategy::class);
+ $matching->method('supports')->willReturn(true);
+ $matching->expects($this->once())->method('configure');
+
+ $runner->addStrategy($nonMatching);
+ $runner->addStrategy($matching);
+
+ $wrapper = new Command('test:wrapper');
+ $inner = new Command('test:inner');
+ $inner->setDescription('Test description');
+
+ $runner->configure($wrapper, $inner);
+ }
+
+ public function testConfigureAsAddsContextOptionsRegardlessOfStrategy(): void
+ {
+ $runner = new ContextRunner(new Configuration());
+
+ $strategy = $this->createStub(CommandExecutionStrategy::class);
+ $strategy->method('supports')->willReturn(true);
+ $runner->addStrategy($strategy);
+
+ $wrapper = new Command('test:wrapper');
+ $inner = new Command('test:inner');
+ $inner->setDescription('Test description');
+
+ $runner->configure($wrapper, $inner);
+
+ self::assertTrue($wrapper->getDefinition()->hasOption('ctx-isolation'));
+ self::assertTrue($wrapper->getDefinition()->hasOption('ctx-all'));
+ }
+}