From ee5703f0ab94f844c7f9cb3f1d7a773126a38ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20J?= Date: Mon, 23 Feb 2026 16:55:22 +0100 Subject: [PATCH 1/7] Added cleanup commands --- CHANGELOG.md | 3 ++ README.md | 5 ++- src/Command/JobCleanupCommand.php | 32 +++++++++++++++++++ src/Command/SetCrashedCommand.php | 32 +++++++++++++++++++ .../CodeRhapsodieDataflowExtension.php | 2 ++ src/DependencyInjection/Configuration.php | 14 ++++++++ src/Entity/Job.php | 1 + src/Repository/JobRepository.php | 27 ++++++++++++++++ src/Resources/config/services.yaml | 14 +++++++- 9 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 src/Command/JobCleanupCommand.php create mode 100644 src/Command/SetCrashedCommand.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bc8f7c..152bfeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# Version 5.5.0 +* Added cleanup commands + # Version 5.4.1 * Fix File Exceptions integration diff --git a/README.md b/README.md index 84638b5..dab6151 100644 --- a/README.md +++ b/README.md @@ -243,7 +243,6 @@ implementing `DataflowTypeInterface`. Otherwise, manually add the tag `coderhapsodie.dataflow.type` in your dataflow type service configuration: -```yaml ```yaml CodeRhapsodie\DataflowExemple\DataflowType\MyFirstDataflowType: tags: @@ -598,6 +597,10 @@ the messenger component instead. `code-rhapsodie:dataflow:dump-schema` Generates schema create / update SQL queries +`code-rhapsodie:dataflow:set_crashed` Jobs that have been in the "running" status for too long will be set in the "crashed" status. + +`code-rhapsodie:dataflow:job_cleanup` Remove old completed or crashed jobs + ### Work with many databases All commands have a `--connection` option to define what Doctrine DBAL connection to use during execution. diff --git a/src/Command/JobCleanupCommand.php b/src/Command/JobCleanupCommand.php new file mode 100644 index 0000000..402106b --- /dev/null +++ b/src/Command/JobCleanupCommand.php @@ -0,0 +1,32 @@ +setHelp('Job retention can be configured with the "job_history.retention" configuration.'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->jobRepository->deleteOld($this->retention); + + return Command::SUCCESS; + } +} diff --git a/src/Command/SetCrashedCommand.php b/src/Command/SetCrashedCommand.php new file mode 100644 index 0000000..bd7925c --- /dev/null +++ b/src/Command/SetCrashedCommand.php @@ -0,0 +1,32 @@ +setHelp('How long jobs have to run before they are set as crashed can be configured with the "job_history.crashed_delay" configuration.'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->jobRepository->crashLongRunning($this->crashedDelay); + + return Command::SUCCESS; + } +} diff --git a/src/DependencyInjection/CodeRhapsodieDataflowExtension.php b/src/DependencyInjection/CodeRhapsodieDataflowExtension.php index ca4bb9c..b09e69c 100644 --- a/src/DependencyInjection/CodeRhapsodieDataflowExtension.php +++ b/src/DependencyInjection/CodeRhapsodieDataflowExtension.php @@ -30,6 +30,8 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('coderhapsodie.dataflow.dbal_default_connection', $config['dbal_default_connection']); $container->setParameter('coderhapsodie.dataflow.default_logger', $config['default_logger']); $container->setParameter('coderhapsodie.dataflow.exceptions_mode.type', $config['exceptions_mode']['type']); + $container->setParameter('coderhapsodie.dataflow.job_history.retention', $config['job_history']['retention']); + $container->setParameter('coderhapsodie.dataflow.job_history.crashed_delay', $config['job_history']['crashed_delay']); if ($config['exceptions_mode']['type'] === 'file') { $container->setParameter('coderhapsodie.dataflow.flysystem_service', $config['exceptions_mode']['flysystem_service']); diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 12a6df0..a4be510 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -52,6 +52,20 @@ public function getConfigTreeBuilder(): TreeBuilder ->thenInvalid('You need "league/flysystem" to use Dataflow file exception mode.') ->end() ->end() + ->arrayNode('job_history') + ->addDefaultsIfNotSet() + ->children() + ->integerNode('retention') + ->defaultValue(30) + ->min(0) + ->info('How many days completed and crashed jobs are kept when running the cleanup command.') + ->end() + ->integerNode('crashed_delay') + ->defaultValue(24) + ->min(24) + ->info('Jobs running for more than this many hours will be set as crashed when running the cleanup command.') + ->end() + ->end() ->end() ; diff --git a/src/Entity/Job.php b/src/Entity/Job.php index cd931a6..dd721b2 100644 --- a/src/Entity/Job.php +++ b/src/Entity/Job.php @@ -17,6 +17,7 @@ class Job public const STATUS_RUNNING = 1; public const STATUS_COMPLETED = 2; public const STATUS_QUEUED = 3; + public const STATUS_CRASHED = 4; private const KEYS = [ 'id', diff --git a/src/Repository/JobRepository.php b/src/Repository/JobRepository.php index 92d36a6..ae1a7e4 100644 --- a/src/Repository/JobRepository.php +++ b/src/Repository/JobRepository.php @@ -151,6 +151,33 @@ public function createQueryBuilder($alias = null): QueryBuilder return $qb; } + public function crashLongRunning(int $hours): void + { + $qb = $this->connection->createQueryBuilder(); + $qb->update(static::TABLE_NAME, 'j') + ->set('j.status', ':new_status') + ->set('j.end_time', ':now') + ->andWhere('j.status = :status') + ->andWhere('j.start_time < :date') + ->setParameter('status', Job::STATUS_RUNNING) + ->setParameter('date', new \DateTime("- {$hours} hours"), 'datetime') + ->setParameter('new_status', Job::STATUS_CRASHED) + ->setParameter('now', new \DateTime(), 'datetime') + ->executeStatement() + ; + } + + public function deleteOld(int $days): void + { + $qb = $this->connection->createQueryBuilder(); + $qb->delete(static::TABLE_NAME, 'j') + ->andWhere($qb->expr()->in('j.status', [Job::STATUS_COMPLETED, Job::STATUS_CRASHED])) + ->andWhere('j.end_time < :date') + ->setParameter('date', new \DateTime("- {$days} days"), 'datetime') + ->executeStatement() + ; + } + private function returnFirstOrNull(QueryBuilder $qb): ?Job { $stmt = $qb->executeQuery(); diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml index dce5b62..3f3bb9a 100644 --- a/src/Resources/config/services.yaml +++ b/src/Resources/config/services.yaml @@ -3,7 +3,7 @@ services: public: false CodeRhapsodie\DataflowBundle\Registry\DataflowTypeRegistryInterface: '@CodeRhapsodie\DataflowBundle\Registry\DataflowTypeRegistry' - CodeRhapsodie\DataflowBundle\Registry\DataflowTypeRegistry: + CodeRhapsodie\DataflowBundle\Registry\DataflowTypeRegistry: ~ CodeRhapsodie\DataflowBundle\Command\AddScheduledDataflowCommand: arguments: @@ -100,3 +100,15 @@ services: arguments: $repository: '@CodeRhapsodie\DataflowBundle\Repository\JobRepository' $exceptionHandler: '@CodeRhapsodie\DataflowBundle\ExceptionsHandler\ExceptionHandlerInterface' + + CodeRhapsodie\DataflowBundle\Command\JobCleanupCommand: + arguments: + $jobRepository: '@CodeRhapsodie\DataflowBundle\Repository\JobRepository' + $retention: '%coderhapsodie.dataflow.job_history.retention%' + tags: ['console.command'] + + CodeRhapsodie\DataflowBundle\Command\SetCrashedCommand: + arguments: + $jobRepository: '@CodeRhapsodie\DataflowBundle\Repository\JobRepository' + $crashedDelay: '%coderhapsodie.dataflow.job_history.crashed_delay%' + tags: ['console.command'] From 83f00e79d33565d347b1ef6f5f9afd1341d2ae95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20J?= Date: Mon, 23 Feb 2026 16:59:28 +0100 Subject: [PATCH 2/7] php-cs-fixer --- src/Command/JobShowCommand.php | 2 +- src/Command/SchemaCommand.php | 2 +- src/DataflowType/Dataflow/Dataflow.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Command/JobShowCommand.php b/src/Command/JobShowCommand.php index 2863b94..15da603 100644 --- a/src/Command/JobShowCommand.php +++ b/src/Command/JobShowCommand.php @@ -93,7 +93,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->table(['Field', 'Value'], $display); if ($input->getOption('details')) { $io->section('Exceptions'); - $exceptions = array_map(fn (string $exception) => substr($exception, 0, 900).'…', $job->getExceptions()); + $exceptions = array_map(static fn (string $exception) => substr($exception, 0, 900).'…', $job->getExceptions()); $io->write($exceptions); } diff --git a/src/Command/SchemaCommand.php b/src/Command/SchemaCommand.php index 237724c..f774a3e 100644 --- a/src/Command/SchemaCommand.php +++ b/src/Command/SchemaCommand.php @@ -38,7 +38,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int // add -- before each keys $options = array_combine( - array_map(fn ($key) => '--'.$key, array_keys($options)), + array_map(static fn ($key) => '--'.$key, array_keys($options)), array_values($options) ); diff --git a/src/DataflowType/Dataflow/Dataflow.php b/src/DataflowType/Dataflow/Dataflow.php index dbbb761..d2021e3 100644 --- a/src/DataflowType/Dataflow/Dataflow.php +++ b/src/DataflowType/Dataflow/Dataflow.php @@ -67,7 +67,7 @@ public function setCustomExceptionIndex(callable $callable): self */ public function setAfterItemProcessors(array $processors): self { - $this->afterItemProcessors = array_map(fn (callable $callable) => \Closure::fromCallable($callable), $processors); + $this->afterItemProcessors = array_map(static fn (callable $callable) => \Closure::fromCallable($callable), $processors); return $this; } From bc38448558703d5e2931bfb12248345868dec0a1 Mon Sep 17 00:00:00 2001 From: jeremycr <32451794+jeremycr@users.noreply.github.com> Date: Tue, 31 Mar 2026 13:58:35 +0200 Subject: [PATCH 3/7] Apply suggestions from code review Co-authored-by: jbcr <51637606+jbcr@users.noreply.github.com> --- README.md | 4 ++-- src/Command/JobCleanupCommand.php | 2 +- src/Command/SetCrashedCommand.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dab6151..c799c0b 100644 --- a/README.md +++ b/README.md @@ -597,9 +597,9 @@ the messenger component instead. `code-rhapsodie:dataflow:dump-schema` Generates schema create / update SQL queries -`code-rhapsodie:dataflow:set_crashed` Jobs that have been in the "running" status for too long will be set in the "crashed" status. +`code-rhapsodie:dataflow:job:set-crashed` Jobs that have been in the "running" status for too long will be set in the "crashed" status. -`code-rhapsodie:dataflow:job_cleanup` Remove old completed or crashed jobs +`code-rhapsodie:dataflow:job:cleanup` Remove old completed or crashed jobs ### Work with many databases diff --git a/src/Command/JobCleanupCommand.php b/src/Command/JobCleanupCommand.php index 402106b..b37bea8 100644 --- a/src/Command/JobCleanupCommand.php +++ b/src/Command/JobCleanupCommand.php @@ -10,7 +10,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'code-rhapsodie:job_cleanup', description: 'Cleanup job history.')] +#[AsCommand(name: 'code-rhapsodie:job:cleanup', description: 'Cleanup job history.')] class JobCleanupCommand extends Command { public function __construct(private JobRepository $jobRepository, private int $retention) diff --git a/src/Command/SetCrashedCommand.php b/src/Command/SetCrashedCommand.php index bd7925c..55cbd7e 100644 --- a/src/Command/SetCrashedCommand.php +++ b/src/Command/SetCrashedCommand.php @@ -10,7 +10,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'code-rhapsodie:set_crashed', description: 'Set long running jobs as crashed.')] +#[AsCommand(name: 'code-rhapsodie:job:set-crashed', description: 'Set long running jobs as crashed.')] class SetCrashedCommand extends Command { public function __construct(private JobRepository $jobRepository, private int $crashedDelay) From 976cc8212328a56ad3cea187fba4f858e81af02c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20J?= Date: Tue, 31 Mar 2026 14:33:28 +0200 Subject: [PATCH 4/7] Updated for PHP 8.2 --- CHANGELOG.md | 1 + .../DataflowType/AbstractDataflowTypeTest.php | 2 +- composer.json | 2 +- rector.php | 25 ++++++++----------- src/Command/AddScheduledDataflowCommand.php | 7 +++--- src/Command/ChangeScheduleStatusCommand.php | 7 +++--- src/Command/DatabaseSchemaCommand.php | 7 +++--- src/Command/ExecuteDataflowCommand.php | 15 +++++------ src/Command/JobCleanupCommand.php | 7 +++--- src/Command/JobShowCommand.php | 7 +++--- src/Command/RunPendingDataflowsCommand.php | 9 +++---- src/Command/ScheduleListCommand.php | 7 +++--- src/Command/SchemaCommand.php | 5 ++-- src/Command/SetCrashedCommand.php | 7 +++--- src/DataflowType/Dataflow/Dataflow.php | 2 +- src/DataflowType/Result.php | 6 ++--- src/DataflowType/Writer/CollectionWriter.php | 2 +- src/DataflowType/Writer/PortWriterAdapter.php | 2 +- src/Event/ProcessingEvent.php | 2 +- .../FilesystemExceptionHandler.php | 2 +- src/Factory/ConnectionFactory.php | 2 +- src/Gateway/JobGateway.php | 2 +- src/Manager/ScheduledDataflowManager.php | 2 +- src/MessengerMode/JobMessage.php | 2 +- src/MessengerMode/JobMessageHandler.php | 2 +- src/Repository/JobRepository.php | 4 +-- src/Runner/MessengerDataflowRunner.php | 2 +- src/Runner/PendingDataflowRunner.php | 2 +- 28 files changed, 71 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 152bfeb..7ce7ca1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Version 5.5.0 * Added cleanup commands +* Updated for PHP 8.2 # Version 5.4.1 * Fix File Exceptions integration diff --git a/Tests/DataflowType/AbstractDataflowTypeTest.php b/Tests/DataflowType/AbstractDataflowTypeTest.php index ee843ae..ca4a121 100644 --- a/Tests/DataflowType/AbstractDataflowTypeTest.php +++ b/Tests/DataflowType/AbstractDataflowTypeTest.php @@ -18,7 +18,7 @@ public function testProcess() $dataflowType = new class($label, $options, $values, $testCase) extends AbstractDataflowType { - public function __construct(private string $label, private array $options, private array $values, private TestCase $testCase) + public function __construct(private readonly string $label, private readonly array $options, private readonly array $values, private readonly TestCase $testCase) { } diff --git a/composer.json b/composer.json index 393f4b0..b70ace3 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,7 @@ } }, "require": { - "php": "^8.0", + "php": "^8.2", "ext-json": "*", "doctrine/dbal": "^3.0||^4.0", "doctrine/doctrine-bundle": "^2.0", diff --git a/rector.php b/rector.php index 92cd240..f7aadd9 100644 --- a/rector.php +++ b/rector.php @@ -7,19 +7,16 @@ use Rector\Set\ValueObject\LevelSetList; use Rector\Symfony\Set\SymfonySetList; -return static function (RectorConfig $rectorConfig): void { - $rectorConfig->paths([ - __DIR__ . '/src', - __DIR__ . '/Tests', - ]); - - // register a single rule - $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); - - $rectorConfig->sets([ - SymfonySetList::SYMFONY_70, +return RectorConfig::configure() + ->withPaths([ + __DIR__.'/src', + __DIR__.'/Tests', + ]) + ->withComposerBased(symfony: true) + ->withRules([InlineConstructorDefaultToPropertyRector::class]) + ->withSets([ SymfonySetList::SYMFONY_CODE_QUALITY, SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION, - LevelSetList::UP_TO_PHP_80, - ]); -}; + LevelSetList::UP_TO_PHP_82, + ]) +; diff --git a/src/Command/AddScheduledDataflowCommand.php b/src/Command/AddScheduledDataflowCommand.php index 7a895ca..99dc095 100644 --- a/src/Command/AddScheduledDataflowCommand.php +++ b/src/Command/AddScheduledDataflowCommand.php @@ -19,10 +19,12 @@ /** * @codeCoverageIgnore */ -#[AsCommand('code-rhapsodie:dataflow:schedule:add', 'Create a scheduled dataflow')] +#[AsCommand('code-rhapsodie:dataflow:schedule:add', 'Create a scheduled dataflow', help: <<<'TXT' +The %command.name% allows you to create a new scheduled dataflow. +TXT)] class AddScheduledDataflowCommand extends Command { - public function __construct(private DataflowTypeRegistryInterface $registry, private ScheduledDataflowRepository $scheduledDataflowRepository, private ValidatorInterface $validator, private ConnectionFactory $connectionFactory) + public function __construct(private readonly DataflowTypeRegistryInterface $registry, private readonly ScheduledDataflowRepository $scheduledDataflowRepository, private readonly ValidatorInterface $validator, private readonly ConnectionFactory $connectionFactory) { parent::__construct(); } @@ -30,7 +32,6 @@ public function __construct(private DataflowTypeRegistryInterface $registry, pri protected function configure(): void { $this - ->setHelp('The %command.name% allows you to create a new scheduled dataflow.') ->addOption('label', null, InputOption::VALUE_REQUIRED, 'Label of the scheduled dataflow') ->addOption('type', null, InputOption::VALUE_REQUIRED, 'Type of the scheduled dataflow (FQCN)') ->addOption( diff --git a/src/Command/ChangeScheduleStatusCommand.php b/src/Command/ChangeScheduleStatusCommand.php index 35735fc..635d17a 100644 --- a/src/Command/ChangeScheduleStatusCommand.php +++ b/src/Command/ChangeScheduleStatusCommand.php @@ -18,10 +18,12 @@ /** * @codeCoverageIgnore */ -#[AsCommand('code-rhapsodie:dataflow:schedule:change-status', 'Change schedule status')] +#[AsCommand('code-rhapsodie:dataflow:schedule:change-status', 'Change schedule status', help: <<<'TXT' +The %command.name% command able you to change schedule status. +TXT)] class ChangeScheduleStatusCommand extends Command { - public function __construct(private ScheduledDataflowRepository $scheduledDataflowRepository, private ConnectionFactory $connectionFactory) + public function __construct(private readonly ScheduledDataflowRepository $scheduledDataflowRepository, private readonly ConnectionFactory $connectionFactory) { parent::__construct(); } @@ -29,7 +31,6 @@ public function __construct(private ScheduledDataflowRepository $scheduledDatafl protected function configure(): void { $this - ->setHelp('The %command.name% command able you to change schedule status.') ->addArgument('schedule-id', InputArgument::REQUIRED, 'Id of the schedule') ->addOption('enable', null, InputOption::VALUE_NONE, 'Enable the schedule') ->addOption('disable', null, InputOption::VALUE_NONE, 'Disable the schedule') diff --git a/src/Command/DatabaseSchemaCommand.php b/src/Command/DatabaseSchemaCommand.php index 464df39..a5f974e 100644 --- a/src/Command/DatabaseSchemaCommand.php +++ b/src/Command/DatabaseSchemaCommand.php @@ -19,10 +19,12 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; -#[AsCommand(name: 'code-rhapsodie:dataflow:database-schema', description: 'Generates schema create / update SQL queries')] +#[AsCommand(name: 'code-rhapsodie:dataflow:database-schema', description: 'Generates schema create / update SQL queries', help: <<<'TXT' +The %command.name% help you to generate SQL Query to create or update your database schema for this bundle +TXT)] class DatabaseSchemaCommand extends Command { - public function __construct(private ConnectionFactory $connectionFactory) + public function __construct(private readonly ConnectionFactory $connectionFactory) { parent::__construct(); } @@ -30,7 +32,6 @@ public function __construct(private ConnectionFactory $connectionFactory) protected function configure(): void { $this - ->setHelp('The %command.name% help you to generate SQL Query to create or update your database schema for this bundle') ->addOption('dump-sql', null, InputOption::VALUE_NONE, 'Dump only the update SQL queries.') ->addOption('update', null, InputOption::VALUE_NONE, 'Dump/execute only the update SQL queries.') ->addOption('connection', null, InputOption::VALUE_REQUIRED, 'Define the DBAL connection to use'); diff --git a/src/Command/ExecuteDataflowCommand.php b/src/Command/ExecuteDataflowCommand.php index fa99cd5..4603f01 100644 --- a/src/Command/ExecuteDataflowCommand.php +++ b/src/Command/ExecuteDataflowCommand.php @@ -23,7 +23,11 @@ * * @codeCoverageIgnore */ -#[AsCommand('code-rhapsodie:dataflow:execute', 'Runs one dataflow type with provided options')] +#[AsCommand('code-rhapsodie:dataflow:execute', 'Runs one dataflow type with provided options', help: <<<'TXT' +The %command.name% command runs one dataflow with the provided options. + + php %command.full_name% App\Dataflow\MyDataflow '{"option1": "value1", "option2": "value2"}' +TXT)] class ExecuteDataflowCommand extends Command implements LoggerAwareInterface { use LoggerAwareTrait; @@ -36,13 +40,6 @@ public function __construct(private DataflowTypeRegistryInterface $registry, pri protected function configure(): void { $this - ->setHelp( - <<<'EOF' -The %command.name% command runs one dataflow with the provided options. - - php %command.full_name% App\Dataflow\MyDataflow '{"option1": "value1", "option2": "value2"}' -EOF - ) ->addArgument('fqcn', InputArgument::REQUIRED, 'FQCN or alias of the dataflow type') ->addArgument('options', InputArgument::OPTIONAL, 'Options for the dataflow type as a json string', '[]') ->addOption('connection', null, InputOption::VALUE_REQUIRED, 'Define the DBAL connection to use'); @@ -54,7 +51,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->connectionFactory->setConnectionName($input->getOption('connection')); } $fqcnOrAlias = $input->getArgument('fqcn'); - $options = json_decode($input->getArgument('options'), true, 512, \JSON_THROW_ON_ERROR); + $options = json_decode((string) $input->getArgument('options'), true, 512, \JSON_THROW_ON_ERROR); $io = new SymfonyStyle($input, $output); $dataflowType = $this->registry->getDataflowType($fqcnOrAlias); diff --git a/src/Command/JobCleanupCommand.php b/src/Command/JobCleanupCommand.php index b37bea8..483095b 100644 --- a/src/Command/JobCleanupCommand.php +++ b/src/Command/JobCleanupCommand.php @@ -10,17 +10,18 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'code-rhapsodie:job:cleanup', description: 'Cleanup job history.')] +#[AsCommand(name: 'code-rhapsodie:dataflow:job:cleanup', description: 'Cleanup job history.', help: <<<'TXT' +Job retention can be configured with the "job_history.retention" configuration. +TXT)] class JobCleanupCommand extends Command { - public function __construct(private JobRepository $jobRepository, private int $retention) + public function __construct(private readonly JobRepository $jobRepository, private readonly int $retention) { parent::__construct(); } protected function configure() { - $this->setHelp('Job retention can be configured with the "job_history.retention" configuration.'); } protected function execute(InputInterface $input, OutputInterface $output): int diff --git a/src/Command/JobShowCommand.php b/src/Command/JobShowCommand.php index 15da603..7d00c27 100644 --- a/src/Command/JobShowCommand.php +++ b/src/Command/JobShowCommand.php @@ -17,7 +17,9 @@ /** * @codeCoverageIgnore */ -#[AsCommand('code-rhapsodie:dataflow:job:show', 'Display job details for schedule or specific job')] +#[AsCommand('code-rhapsodie:dataflow:job:show', 'Display job details for schedule or specific job', help: <<<'TXT' +The %command.name% display job details for schedule or specific job. +TXT)] class JobShowCommand extends Command { private const STATUS_MAPPING = [ @@ -26,7 +28,7 @@ class JobShowCommand extends Command Job::STATUS_COMPLETED => 'Completed', ]; - public function __construct(private JobGateway $jobGateway, private ConnectionFactory $connectionFactory) + public function __construct(private readonly JobGateway $jobGateway, private readonly ConnectionFactory $connectionFactory) { parent::__construct(); } @@ -34,7 +36,6 @@ public function __construct(private JobGateway $jobGateway, private ConnectionFa protected function configure(): void { $this - ->setHelp('The %command.name% display job details for schedule or specific job.') ->addOption('job-id', null, InputOption::VALUE_REQUIRED, 'Id of the job to get details') ->addOption('schedule-id', null, InputOption::VALUE_REQUIRED, 'Id of schedule for last execution details') ->addOption('details', null, InputOption::VALUE_NONE, 'Display full details') diff --git a/src/Command/RunPendingDataflowsCommand.php b/src/Command/RunPendingDataflowsCommand.php index a08cae5..6e8d8d9 100644 --- a/src/Command/RunPendingDataflowsCommand.php +++ b/src/Command/RunPendingDataflowsCommand.php @@ -19,7 +19,9 @@ * * @codeCoverageIgnore */ -#[AsCommand('code-rhapsodie:dataflow:run-pending', 'Runs dataflows based on the scheduled defined in the UI.')] +#[AsCommand('code-rhapsodie:dataflow:run-pending', 'Runs dataflows based on the scheduled defined in the UI.', help: <<<'TXT' +The %command.name% command runs dataflows according to the schedule defined in the UI by the user. +TXT)] class RunPendingDataflowsCommand extends Command { use LockableTrait; @@ -32,11 +34,6 @@ public function __construct(private ScheduledDataflowManagerInterface $manager, protected function configure(): void { $this - ->setHelp( - <<<'EOF' -The %command.name% command runs dataflows according to the schedule defined in the UI by the user. -EOF - ) ->addOption('connection', null, InputOption::VALUE_REQUIRED, 'Define the DBAL connection to use'); } diff --git a/src/Command/ScheduleListCommand.php b/src/Command/ScheduleListCommand.php index d4a187c..fe820c7 100644 --- a/src/Command/ScheduleListCommand.php +++ b/src/Command/ScheduleListCommand.php @@ -16,10 +16,12 @@ /** * @codeCoverageIgnore */ -#[AsCommand('code-rhapsodie:dataflow:schedule:list', 'List scheduled dataflows')] +#[AsCommand('code-rhapsodie:dataflow:schedule:list', 'List scheduled dataflows', help: <<<'TXT' +The %command.name% lists all scheduled dataflows. +TXT)] class ScheduleListCommand extends Command { - public function __construct(private ScheduledDataflowRepository $scheduledDataflowRepository, private ConnectionFactory $connectionFactory) + public function __construct(private readonly ScheduledDataflowRepository $scheduledDataflowRepository, private readonly ConnectionFactory $connectionFactory) { parent::__construct(); } @@ -27,7 +29,6 @@ public function __construct(private ScheduledDataflowRepository $scheduledDatafl protected function configure(): void { $this - ->setHelp('The %command.name% lists all scheduled dataflows.') ->addOption('connection', null, InputOption::VALUE_REQUIRED, 'Define the DBAL connection to use'); } diff --git a/src/Command/SchemaCommand.php b/src/Command/SchemaCommand.php index f774a3e..cbe643d 100644 --- a/src/Command/SchemaCommand.php +++ b/src/Command/SchemaCommand.php @@ -17,13 +17,14 @@ * * @deprecated This command is deprecated and will be removed in 6.0, use this command "code-rhapsodie:dataflow:database-schema" instead. */ -#[AsCommand('code-rhapsodie:dataflow:dump-schema', 'Generates schema create / update SQL queries')] +#[AsCommand('code-rhapsodie:dataflow:dump-schema', 'Generates schema create / update SQL queries', help: <<<'TXT' +The %command.name% help you to generate SQL Query to create or update your database schema for this bundle +TXT)] class SchemaCommand extends Command { protected function configure(): void { $this - ->setHelp('The %command.name% help you to generate SQL Query to create or update your database schema for this bundle') ->addOption('update', null, InputOption::VALUE_NONE, 'Dump only the update SQL queries.') ->addOption('connection', null, InputOption::VALUE_REQUIRED, 'Define the DBAL connection to use') ; diff --git a/src/Command/SetCrashedCommand.php b/src/Command/SetCrashedCommand.php index 55cbd7e..ce508f7 100644 --- a/src/Command/SetCrashedCommand.php +++ b/src/Command/SetCrashedCommand.php @@ -10,17 +10,18 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'code-rhapsodie:job:set-crashed', description: 'Set long running jobs as crashed.')] +#[AsCommand(name: 'code-rhapsodie:dataflow:job:set-crashed', description: 'Set long running jobs as crashed.', help: <<<'TXT' +How long jobs have to run before they are set as crashed can be configured with the "job_history.crashed_delay" configuration. +TXT)] class SetCrashedCommand extends Command { - public function __construct(private JobRepository $jobRepository, private int $crashedDelay) + public function __construct(private readonly JobRepository $jobRepository, private readonly int $crashedDelay) { parent::__construct(); } protected function configure() { - $this->setHelp('How long jobs have to run before they are set as crashed can be configured with the "job_history.crashed_delay" configuration.'); } protected function execute(InputInterface $input, OutputInterface $output): int diff --git a/src/DataflowType/Dataflow/Dataflow.php b/src/DataflowType/Dataflow/Dataflow.php index d2021e3..2797062 100644 --- a/src/DataflowType/Dataflow/Dataflow.php +++ b/src/DataflowType/Dataflow/Dataflow.php @@ -67,7 +67,7 @@ public function setCustomExceptionIndex(callable $callable): self */ public function setAfterItemProcessors(array $processors): self { - $this->afterItemProcessors = array_map(static fn (callable $callable) => \Closure::fromCallable($callable), $processors); + $this->afterItemProcessors = array_map(\Closure::fromCallable(...), $processors); return $this; } diff --git a/src/DataflowType/Result.php b/src/DataflowType/Result.php index 5c8716b..f7cf5fe 100644 --- a/src/DataflowType/Result.php +++ b/src/DataflowType/Result.php @@ -9,15 +9,15 @@ */ class Result { - private \DateInterval $elapsed; + private readonly \DateInterval $elapsed; private int $errorCount = 0; private int $successCount = 0; - private array $exceptions; + private readonly array $exceptions; - public function __construct(private string $name, private \DateTimeInterface $startTime, private \DateTimeInterface $endTime, private int $totalProcessedCount, array $exceptions) + public function __construct(private readonly string $name, private readonly \DateTimeInterface $startTime, private readonly \DateTimeInterface $endTime, private readonly int $totalProcessedCount, array $exceptions) { $this->elapsed = $startTime->diff($endTime); $this->errorCount = \count($exceptions); diff --git a/src/DataflowType/Writer/CollectionWriter.php b/src/DataflowType/Writer/CollectionWriter.php index 704e675..7dee448 100644 --- a/src/DataflowType/Writer/CollectionWriter.php +++ b/src/DataflowType/Writer/CollectionWriter.php @@ -14,7 +14,7 @@ class CollectionWriter implements DelegateWriterInterface /** * CollectionWriter constructor. */ - public function __construct(private WriterInterface $writer) + public function __construct(private readonly WriterInterface $writer) { } diff --git a/src/DataflowType/Writer/PortWriterAdapter.php b/src/DataflowType/Writer/PortWriterAdapter.php index 7d624ed..32bfb58 100644 --- a/src/DataflowType/Writer/PortWriterAdapter.php +++ b/src/DataflowType/Writer/PortWriterAdapter.php @@ -6,7 +6,7 @@ class PortWriterAdapter implements WriterInterface { - public function __construct(private \Port\Writer $writer) + public function __construct(private readonly \Port\Writer $writer) { } diff --git a/src/Event/ProcessingEvent.php b/src/Event/ProcessingEvent.php index 4c0e4e7..fa56cdb 100644 --- a/src/Event/ProcessingEvent.php +++ b/src/Event/ProcessingEvent.php @@ -16,7 +16,7 @@ class ProcessingEvent extends CrEvent /** * ProcessingEvent constructor. */ - public function __construct(private Job $job) + public function __construct(private readonly Job $job) { } diff --git a/src/ExceptionsHandler/FilesystemExceptionHandler.php b/src/ExceptionsHandler/FilesystemExceptionHandler.php index 9176cf7..8107f0a 100644 --- a/src/ExceptionsHandler/FilesystemExceptionHandler.php +++ b/src/ExceptionsHandler/FilesystemExceptionHandler.php @@ -9,7 +9,7 @@ class FilesystemExceptionHandler implements ExceptionHandlerInterface { - public function __construct(private Filesystem $filesystem) + public function __construct(private readonly Filesystem $filesystem) { } diff --git a/src/Factory/ConnectionFactory.php b/src/Factory/ConnectionFactory.php index 04da339..6daae2b 100644 --- a/src/Factory/ConnectionFactory.php +++ b/src/Factory/ConnectionFactory.php @@ -13,7 +13,7 @@ */ class ConnectionFactory { - public function __construct(private Container $container, private string $connectionName) + public function __construct(private readonly Container $container, private string $connectionName) { } diff --git a/src/Gateway/JobGateway.php b/src/Gateway/JobGateway.php index fa24e41..55ba631 100644 --- a/src/Gateway/JobGateway.php +++ b/src/Gateway/JobGateway.php @@ -11,7 +11,7 @@ class JobGateway { - public function __construct(private JobRepository $repository, private ExceptionHandlerInterface $exceptionHandler) + public function __construct(private readonly JobRepository $repository, private readonly ExceptionHandlerInterface $exceptionHandler) { } diff --git a/src/Manager/ScheduledDataflowManager.php b/src/Manager/ScheduledDataflowManager.php index cbb0f23..3b5baa1 100644 --- a/src/Manager/ScheduledDataflowManager.php +++ b/src/Manager/ScheduledDataflowManager.php @@ -15,7 +15,7 @@ */ class ScheduledDataflowManager implements ScheduledDataflowManagerInterface { - public function __construct(private Connection $connection, private ScheduledDataflowRepository $scheduledDataflowRepository, private JobRepository $jobRepository) + public function __construct(private readonly Connection $connection, private readonly ScheduledDataflowRepository $scheduledDataflowRepository, private readonly JobRepository $jobRepository) { } diff --git a/src/MessengerMode/JobMessage.php b/src/MessengerMode/JobMessage.php index f4361f7..5bb51a3 100644 --- a/src/MessengerMode/JobMessage.php +++ b/src/MessengerMode/JobMessage.php @@ -6,7 +6,7 @@ class JobMessage { - public function __construct(private int $jobId) + public function __construct(private readonly int $jobId) { } diff --git a/src/MessengerMode/JobMessageHandler.php b/src/MessengerMode/JobMessageHandler.php index 401dbd7..44d2a2a 100644 --- a/src/MessengerMode/JobMessageHandler.php +++ b/src/MessengerMode/JobMessageHandler.php @@ -11,7 +11,7 @@ #[AsMessageHandler] class JobMessageHandler { - public function __construct(private JobRepository $repository, private JobProcessorInterface $processor) + public function __construct(private readonly JobRepository $repository, private readonly JobProcessorInterface $processor) { } diff --git a/src/Repository/JobRepository.php b/src/Repository/JobRepository.php index ae1a7e4..88772dc 100644 --- a/src/Repository/JobRepository.php +++ b/src/Repository/JobRepository.php @@ -154,7 +154,7 @@ public function createQueryBuilder($alias = null): QueryBuilder public function crashLongRunning(int $hours): void { $qb = $this->connection->createQueryBuilder(); - $qb->update(static::TABLE_NAME, 'j') + $qb->update(static::TABLE_NAME.' j') ->set('j.status', ':new_status') ->set('j.end_time', ':now') ->andWhere('j.status = :status') @@ -170,7 +170,7 @@ public function crashLongRunning(int $hours): void public function deleteOld(int $days): void { $qb = $this->connection->createQueryBuilder(); - $qb->delete(static::TABLE_NAME, 'j') + $qb->delete(static::TABLE_NAME.' j') ->andWhere($qb->expr()->in('j.status', [Job::STATUS_COMPLETED, Job::STATUS_CRASHED])) ->andWhere('j.end_time < :date') ->setParameter('date', new \DateTime("- {$days} days"), 'datetime') diff --git a/src/Runner/MessengerDataflowRunner.php b/src/Runner/MessengerDataflowRunner.php index 8f01f66..86c859b 100644 --- a/src/Runner/MessengerDataflowRunner.php +++ b/src/Runner/MessengerDataflowRunner.php @@ -11,7 +11,7 @@ class MessengerDataflowRunner implements PendingDataflowRunnerInterface { - public function __construct(private JobRepository $repository, private MessageBusInterface $bus) + public function __construct(private readonly JobRepository $repository, private readonly MessageBusInterface $bus) { } diff --git a/src/Runner/PendingDataflowRunner.php b/src/Runner/PendingDataflowRunner.php index 36ff37e..0c4a8e0 100644 --- a/src/Runner/PendingDataflowRunner.php +++ b/src/Runner/PendingDataflowRunner.php @@ -9,7 +9,7 @@ class PendingDataflowRunner implements PendingDataflowRunnerInterface { - public function __construct(private JobRepository $repository, private JobProcessorInterface $processor) + public function __construct(private readonly JobRepository $repository, private readonly JobProcessorInterface $processor) { } From 327e0cc29af5129189b93c3d0f96c39f9a9e67ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20J?= Date: Tue, 31 Mar 2026 15:30:30 +0200 Subject: [PATCH 5/7] WIP --- composer.json | 23 ++++---- rector.php | 5 +- src/Command/AddScheduledDataflowCommand.php | 52 ++++++------------- src/Command/ChangeScheduleStatusCommand.php | 48 ++++++++--------- src/Command/DatabaseSchemaCommand.php | 37 +++++-------- src/Command/JobCleanupCommand.php | 24 ++++----- .../ExceptionHandlerInterface.php | 2 + .../FilesystemExceptionHandler.php | 5 ++ .../NullExceptionHandler.php | 6 +++ src/Repository/JobRepository.php | 17 +++++- src/Resources/config/services.yaml | 3 +- 11 files changed, 108 insertions(+), 114 deletions(-) diff --git a/composer.json b/composer.json index b70ace3..5f5da4d 100644 --- a/composer.json +++ b/composer.json @@ -47,16 +47,16 @@ "doctrine/doctrine-bundle": "^2.0", "monolog/monolog": "^2.0||^3.0", "psr/log": "^1.1||^2.0||^3.0", - "symfony/config": "^7.0", - "symfony/console": "^7.0", - "symfony/dependency-injection": "^7.0", - "symfony/event-dispatcher": "^7.0", - "symfony/http-kernel": "^7.0", - "symfony/lock": "^7.0", - "symfony/monolog-bridge": "^7.0", - "symfony/options-resolver": "^7.0", - "symfony/validator": "^7.0", - "symfony/yaml": "^7.0" + "symfony/config": "^7.3", + "symfony/console": "^7.3", + "symfony/dependency-injection": "^7.3", + "symfony/event-dispatcher": "^7.3", + "symfony/http-kernel": "^7.3", + "symfony/lock": "^7.3", + "symfony/monolog-bridge": "^7.3", + "symfony/options-resolver": "^7.3", + "symfony/validator": "^7.3", + "symfony/yaml": "^7.3" }, "require-dev": { "amphp/amp": "^2.5", @@ -64,10 +64,11 @@ "phpunit/phpunit": "^11", "portphp/portphp": "^1.9", "rector/rector": "^2.0", - "symfony/messenger": "^7.0" + "symfony/messenger": "^7.3" }, "suggest": { "amphp/amp": "Provide asynchronous steps for your dataflows", + "league/flysystem": "Allows Dataflow file exception mode, i.e. saving job exceptions outside the database", "portphp/portphp": "Provides generic readers, steps and writers for your dataflows.", "symfony/messenger": "Allows messenger mode, i.e. letting workers run jobs" }, diff --git a/rector.php b/rector.php index f7aadd9..5beb0f7 100644 --- a/rector.php +++ b/rector.php @@ -2,7 +2,6 @@ declare(strict_types=1); -use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector; use Rector\Config\RectorConfig; use Rector\Set\ValueObject\LevelSetList; use Rector\Symfony\Set\SymfonySetList; @@ -12,11 +11,11 @@ __DIR__.'/src', __DIR__.'/Tests', ]) - ->withComposerBased(symfony: true) - ->withRules([InlineConstructorDefaultToPropertyRector::class]) + ->withComposerBased(doctrine: true, symfony: true) ->withSets([ SymfonySetList::SYMFONY_CODE_QUALITY, SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION, + SymfonySetList::SYMFONY_73, LevelSetList::UP_TO_PHP_82, ]) ; diff --git a/src/Command/AddScheduledDataflowCommand.php b/src/Command/AddScheduledDataflowCommand.php index 99dc095..fe20282 100644 --- a/src/Command/AddScheduledDataflowCommand.php +++ b/src/Command/AddScheduledDataflowCommand.php @@ -9,10 +9,7 @@ use CodeRhapsodie\DataflowBundle\Registry\DataflowTypeRegistryInterface; use CodeRhapsodie\DataflowBundle\Repository\ScheduledDataflowRepository; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Attribute\Option; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -22,34 +19,24 @@ #[AsCommand('code-rhapsodie:dataflow:schedule:add', 'Create a scheduled dataflow', help: <<<'TXT' The %command.name% allows you to create a new scheduled dataflow. TXT)] -class AddScheduledDataflowCommand extends Command +final readonly class AddScheduledDataflowCommand { - public function __construct(private readonly DataflowTypeRegistryInterface $registry, private readonly ScheduledDataflowRepository $scheduledDataflowRepository, private readonly ValidatorInterface $validator, private readonly ConnectionFactory $connectionFactory) + public function __construct(private DataflowTypeRegistryInterface $registry, private ScheduledDataflowRepository $scheduledDataflowRepository, private ValidatorInterface $validator, private ConnectionFactory $connectionFactory) { - parent::__construct(); } - protected function configure(): void - { - $this - ->addOption('label', null, InputOption::VALUE_REQUIRED, 'Label of the scheduled dataflow') - ->addOption('type', null, InputOption::VALUE_REQUIRED, 'Type of the scheduled dataflow (FQCN)') - ->addOption( - 'options', - null, - InputOption::VALUE_OPTIONAL, - 'Options of the scheduled dataflow (ex: {"option1": "value1", "option2": "value2"})' - ) - ->addOption('frequency', null, InputOption::VALUE_REQUIRED, 'Frequency of the scheduled dataflow') - ->addOption('first_run', null, InputOption::VALUE_REQUIRED, 'Date for the first run of the scheduled dataflow (Y-m-d H:i:s)') - ->addOption('enabled', null, InputOption::VALUE_REQUIRED, 'State of the scheduled dataflow') - ->addOption('connection', null, InputOption::VALUE_REQUIRED, 'Define the DBAL connection to use'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - if ($input->getOption('connection') !== null) { - $this->connectionFactory->setConnectionName($input->getOption('connection')); + public function __invoke( + SymfonyStyle $io, + #[Option('Label of the scheduled dataflow')] ?string $label = null, + #[Option('Type of the scheduled dataflow (FQCN)')] ?string $type = null, + #[Option('Options of the scheduled dataflow (ex: {"option1": "value1", "option2": "value2"})')] ?string $options = null, + #[Option('Frequency of the scheduled dataflow')] ?string $frequency = null, + #[Option('Date for the first run of the scheduled dataflow (Y-m-d H:i:s)')] ?string $firstRun = null, + #[Option('State of the scheduled dataflow')] ?bool $enabled = null, + #[Option('Define the DBAL connection to use')] ?string $connection = null, + ): int { + if ($connection !== null) { + $this->connectionFactory->setConnectionName($connection); } $choices = []; $typeMapping = []; @@ -58,36 +45,29 @@ protected function execute(InputInterface $input, OutputInterface $output): int $typeMapping[$dataflowType->getLabel()] = $fqcn; } - $io = new SymfonyStyle($input, $output); - $label = $input->getOption('label'); if (!$label) { $label = $io->ask('What is the scheduled dataflow label?'); } - $type = $input->getOption('type'); if (!$type) { $selectedType = $io->choice('What is the scheduled dataflow type?', $choices); $type = $typeMapping[$selectedType]; } - $options = $input->getOption('options'); if (!$options) { $options = $io->ask( 'What are the launch options for the scheduled dataflow? (ex: {"option1": "value1", "option2": "value2"})', json_encode([]) ); } - $frequency = $input->getOption('frequency'); if (!$frequency) { $frequency = $io->choice( 'What is the frequency for the scheduled dataflow?', ScheduledDataflow::AVAILABLE_FREQUENCIES ); } - $firstRun = $input->getOption('first_run'); if (!$firstRun) { $firstRun = $io->ask('When is the first execution of the scheduled dataflow (format: Y-m-d H:i:s)?'); } - $enabled = $input->getOption('enabled'); - if (!$enabled) { + if ($enabled === null) { $enabled = $io->confirm('Enable the scheduled dataflow?'); } diff --git a/src/Command/ChangeScheduleStatusCommand.php b/src/Command/ChangeScheduleStatusCommand.php index 635d17a..2e665b4 100644 --- a/src/Command/ChangeScheduleStatusCommand.php +++ b/src/Command/ChangeScheduleStatusCommand.php @@ -7,12 +7,9 @@ use CodeRhapsodie\DataflowBundle\Entity\ScheduledDataflow; use CodeRhapsodie\DataflowBundle\Factory\ConnectionFactory; use CodeRhapsodie\DataflowBundle\Repository\ScheduledDataflowRepository; +use Symfony\Component\Console\Attribute\Argument; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Attribute\Option; use Symfony\Component\Console\Style\SymfonyStyle; /** @@ -21,50 +18,47 @@ #[AsCommand('code-rhapsodie:dataflow:schedule:change-status', 'Change schedule status', help: <<<'TXT' The %command.name% command able you to change schedule status. TXT)] -class ChangeScheduleStatusCommand extends Command +final readonly class ChangeScheduleStatusCommand { - public function __construct(private readonly ScheduledDataflowRepository $scheduledDataflowRepository, private readonly ConnectionFactory $connectionFactory) + public function __construct(private ScheduledDataflowRepository $scheduledDataflowRepository, private ConnectionFactory $connectionFactory) { - parent::__construct(); } - protected function configure(): void - { - $this - ->addArgument('schedule-id', InputArgument::REQUIRED, 'Id of the schedule') - ->addOption('enable', null, InputOption::VALUE_NONE, 'Enable the schedule') - ->addOption('disable', null, InputOption::VALUE_NONE, 'Disable the schedule') - ->addOption('connection', null, InputOption::VALUE_REQUIRED, 'Define the DBAL connection to use'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - if ($input->getOption('connection') !== null) { - $this->connectionFactory->setConnectionName($input->getOption('connection')); + public function __invoke( + SymfonyStyle $io, + #[Argument('Id of the schedule')] int $scheduleId, + #[Option('Enable the schedule')] ?bool $enable = null, + #[Option('Disable the schedule')] ?bool $disable = null, + #[Option('Define the DBAL connection to use')] ?string $connection = null, + ): int { + if ($connection !== null) { + $this->connectionFactory->setConnectionName($connection); } - $io = new SymfonyStyle($input, $output); + /** @var ScheduledDataflow|null $schedule */ - $schedule = $this->scheduledDataflowRepository->find((int) $input->getArgument('schedule-id')); + $schedule = $this->scheduledDataflowRepository->find($scheduleId); if (!$schedule) { - $io->error(\sprintf('Cannot find scheduled dataflow with id "%d".', $input->getArgument('schedule-id'))); + $io->error(\sprintf('Cannot find scheduled dataflow with id "%d".', $scheduleId)); return 1; } - if ($input->getOption('enable') && $input->getOption('disable')) { + if ($enable !== null && $disable !== null) { $io->error('You cannot pass enable and disable options in the same time.'); return 2; } - if (!$input->getOption('enable') && !$input->getOption('disable')) { + if ($enable === null && $disable === null) { $io->error('You must pass enable or disable option.'); return 3; } + $enable = $enable ?? !$disable; + try { - $schedule->setEnabled($input->getOption('enable')); + $schedule->setEnabled($enable); $this->scheduledDataflowRepository->save($schedule); $io->success(\sprintf('Schedule with id "%s" has been successfully updated.', $schedule->getId())); } catch (\Exception $e) { diff --git a/src/Command/DatabaseSchemaCommand.php b/src/Command/DatabaseSchemaCommand.php index a5f974e..1721596 100644 --- a/src/Command/DatabaseSchemaCommand.php +++ b/src/Command/DatabaseSchemaCommand.php @@ -12,37 +12,28 @@ use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Table; use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Attribute\Option; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; #[AsCommand(name: 'code-rhapsodie:dataflow:database-schema', description: 'Generates schema create / update SQL queries', help: <<<'TXT' The %command.name% help you to generate SQL Query to create or update your database schema for this bundle TXT)] -class DatabaseSchemaCommand extends Command +final readonly class DatabaseSchemaCommand { - public function __construct(private readonly ConnectionFactory $connectionFactory) + public function __construct(private ConnectionFactory $connectionFactory) { - parent::__construct(); } - protected function configure(): void - { - $this - ->addOption('dump-sql', null, InputOption::VALUE_NONE, 'Dump only the update SQL queries.') - ->addOption('update', null, InputOption::VALUE_NONE, 'Dump/execute only the update SQL queries.') - ->addOption('connection', null, InputOption::VALUE_REQUIRED, 'Define the DBAL connection to use'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $io = new SymfonyStyle($input, $output); - - if ($input->getOption('connection') !== null) { - $this->connectionFactory->setConnectionName($input->getOption('connection')); + public function __invoke( + SymfonyStyle $io, + #[Option('Dump only the update SQL queries.')] bool $dump = false, + #[Option('Dump/execute only the update SQL queries.')] bool $update = false, + #[Option('Define the DBAL connection to use')] ?string $connection = null, + ): int { + if ($connection !== null) { + $this->connectionFactory->setConnectionName($connection); } $connection = $this->connectionFactory->getConnection(); @@ -52,7 +43,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $sqls = $schema->toSql($connection->getDatabasePlatform()); - if ($input->getOption('update')) { + if ($update) { $sm = $connection->createSchemaManager(); $tableArray = [JobRepository::TABLE_NAME, ScheduledDataflowRepository::TABLE_NAME]; @@ -85,7 +76,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } - if ($input->getOption('dump-sql')) { + if ($dump) { $io->text('Execute these SQL Queries on your database:'); foreach ($sqls as $sql) { $io->text($sql.';'); @@ -106,6 +97,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io->success(\sprintf('%d queries executed.', \count($sqls))); - return parent::SUCCESS; + return Command::SUCCESS; } } diff --git a/src/Command/JobCleanupCommand.php b/src/Command/JobCleanupCommand.php index 483095b..c90187f 100644 --- a/src/Command/JobCleanupCommand.php +++ b/src/Command/JobCleanupCommand.php @@ -4,29 +4,29 @@ namespace CodeRhapsodie\DataflowBundle\Command; +use CodeRhapsodie\DataflowBundle\ExceptionsHandler\ExceptionHandlerInterface; use CodeRhapsodie\DataflowBundle\Repository\JobRepository; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; #[AsCommand(name: 'code-rhapsodie:dataflow:job:cleanup', description: 'Cleanup job history.', help: <<<'TXT' Job retention can be configured with the "job_history.retention" configuration. TXT)] -class JobCleanupCommand extends Command +final readonly class JobCleanupCommand { - public function __construct(private readonly JobRepository $jobRepository, private readonly int $retention) - { - parent::__construct(); - } - - protected function configure() - { + public function __construct( + private JobRepository $jobRepository, + private ExceptionHandlerInterface $exceptionHandler, + private int $retention, + ) { } - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(): int { - $this->jobRepository->deleteOld($this->retention); + $removedIds = $this->jobRepository->deleteOld($this->retention); + foreach ($removedIds as $jobId) { + $this->exceptionHandler->delete($jobId); + } return Command::SUCCESS; } diff --git a/src/ExceptionsHandler/ExceptionHandlerInterface.php b/src/ExceptionsHandler/ExceptionHandlerInterface.php index a950c85..bb1bfcc 100644 --- a/src/ExceptionsHandler/ExceptionHandlerInterface.php +++ b/src/ExceptionsHandler/ExceptionHandlerInterface.php @@ -9,4 +9,6 @@ interface ExceptionHandlerInterface public function save(?int $jobId, ?array $exceptions): void; public function find(int $jobId): ?array; + + public function delete(int $jobId): void; } diff --git a/src/ExceptionsHandler/FilesystemExceptionHandler.php b/src/ExceptionsHandler/FilesystemExceptionHandler.php index 8107f0a..20ec200 100644 --- a/src/ExceptionsHandler/FilesystemExceptionHandler.php +++ b/src/ExceptionsHandler/FilesystemExceptionHandler.php @@ -34,4 +34,9 @@ public function find(int $jobId): ?array return []; } } + + public function delete(int $jobId): void + { + $this->filesystem->delete(\sprintf('dataflow-job-%s.log', $jobId)); + } } diff --git a/src/ExceptionsHandler/NullExceptionHandler.php b/src/ExceptionsHandler/NullExceptionHandler.php index c3a0d2d..bc398e5 100644 --- a/src/ExceptionsHandler/NullExceptionHandler.php +++ b/src/ExceptionsHandler/NullExceptionHandler.php @@ -8,10 +8,16 @@ class NullExceptionHandler implements ExceptionHandlerInterface { public function save(?int $jobId, ?array $exceptions): void { + // Nothing to do } public function find(int $jobId): ?array { return null; } + + public function delete(int $jobId): void + { + // Nothing to do + } } diff --git a/src/Repository/JobRepository.php b/src/Repository/JobRepository.php index 88772dc..1683243 100644 --- a/src/Repository/JobRepository.php +++ b/src/Repository/JobRepository.php @@ -167,8 +167,21 @@ public function crashLongRunning(int $hours): void ; } - public function deleteOld(int $days): void + /** + * @return int[] Removed job ids + */ + public function deleteOld(int $days): array { + $qb = $this->connection->createQueryBuilder(); + $ids = $qb->select('j.id') + ->from(static::TABLE_NAME, 'j') + ->andWhere($qb->expr()->in('j.status', [Job::STATUS_COMPLETED, Job::STATUS_CRASHED])) + ->andWhere('j.end_time < :date') + ->setParameter('date', new \DateTime("- {$days} days"), 'datetime') + ->executeQuery() + ->fetchFirstColumn() + ; + $qb = $this->connection->createQueryBuilder(); $qb->delete(static::TABLE_NAME.' j') ->andWhere($qb->expr()->in('j.status', [Job::STATUS_COMPLETED, Job::STATUS_CRASHED])) @@ -176,6 +189,8 @@ public function deleteOld(int $days): void ->setParameter('date', new \DateTime("- {$days} days"), 'datetime') ->executeStatement() ; + + return $ids; } private function returnFirstOrNull(QueryBuilder $qb): ?Job diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml index 3f3bb9a..83af479 100644 --- a/src/Resources/config/services.yaml +++ b/src/Resources/config/services.yaml @@ -95,7 +95,7 @@ services: $dispatcher: '@event_dispatcher' $jobGateway: '@CodeRhapsodie\DataflowBundle\Gateway\JobGateway' CodeRhapsodie\DataflowBundle\ExceptionsHandler\ExceptionHandlerInterface: '@CodeRhapsodie\DataflowBundle\ExceptionsHandler\NullExceptionHandler' - CodeRhapsodie\DataflowBundle\ExceptionsHandler\NullExceptionHandler: + CodeRhapsodie\DataflowBundle\ExceptionsHandler\NullExceptionHandler: ~ CodeRhapsodie\DataflowBundle\Gateway\JobGateway: arguments: $repository: '@CodeRhapsodie\DataflowBundle\Repository\JobRepository' @@ -104,6 +104,7 @@ services: CodeRhapsodie\DataflowBundle\Command\JobCleanupCommand: arguments: $jobRepository: '@CodeRhapsodie\DataflowBundle\Repository\JobRepository' + $exceptionHandler: '@CodeRhapsodie\DataflowBundle\ExceptionsHandler\ExceptionHandlerInterface' $retention: '%coderhapsodie.dataflow.job_history.retention%' tags: ['console.command'] From 4713f31f98c8d4160f8fd7c094c98e4f429dcb15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20J?= Date: Tue, 31 Mar 2026 16:27:47 +0200 Subject: [PATCH 6/7] PHPStan --- composer.json | 1 + phpstan.neon.dist | 6 +++ src/Command/ExecuteDataflowCommand.php | 39 +++++++---------- src/Command/JobShowCommand.php | 42 +++++++------------ src/Command/RunPendingDataflowsCommand.php | 29 +++++-------- src/Command/ScheduleListCommand.php | 28 +++++-------- src/Command/SetCrashedCommand.php | 13 ++---- .../Dataflow/AMPAsyncDataflow.php | 2 +- src/DataflowType/Dataflow/Dataflow.php | 2 - src/Entity/Job.php | 4 +- src/Entity/ScheduledDataflow.php | 2 +- 11 files changed, 66 insertions(+), 102 deletions(-) create mode 100644 phpstan.neon.dist diff --git a/composer.json b/composer.json index 5f5da4d..e60f0e0 100644 --- a/composer.json +++ b/composer.json @@ -60,6 +60,7 @@ }, "require-dev": { "amphp/amp": "^2.5", + "ekino/phpstan-banned-code": "^3.2", "friendsofphp/php-cs-fixer": "^3.75", "phpunit/phpunit": "^11", "portphp/portphp": "^1.9", diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..abcd60b --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,6 @@ +includes: + - vendor/ekino/phpstan-banned-code/extension.neon +parameters: + level: 5 + paths: + - src/ diff --git a/src/Command/ExecuteDataflowCommand.php b/src/Command/ExecuteDataflowCommand.php index 4603f01..3a9c38c 100644 --- a/src/Command/ExecuteDataflowCommand.php +++ b/src/Command/ExecuteDataflowCommand.php @@ -10,12 +10,9 @@ use CodeRhapsodie\DataflowBundle\Repository\JobRepository; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; +use Symfony\Component\Console\Attribute\Argument; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Attribute\Option; use Symfony\Component\Console\Style\SymfonyStyle; /** @@ -28,33 +25,27 @@ php %command.full_name% App\Dataflow\MyDataflow '{"option1": "value1", "option2": "value2"}' TXT)] -class ExecuteDataflowCommand extends Command implements LoggerAwareInterface +final class ExecuteDataflowCommand implements LoggerAwareInterface { use LoggerAwareTrait; - public function __construct(private DataflowTypeRegistryInterface $registry, private ConnectionFactory $connectionFactory, private JobRepository $jobRepository) + public function __construct(private readonly DataflowTypeRegistryInterface $registry, private readonly ConnectionFactory $connectionFactory, private readonly JobRepository $jobRepository) { - parent::__construct(); } - protected function configure(): void - { - $this - ->addArgument('fqcn', InputArgument::REQUIRED, 'FQCN or alias of the dataflow type') - ->addArgument('options', InputArgument::OPTIONAL, 'Options for the dataflow type as a json string', '[]') - ->addOption('connection', null, InputOption::VALUE_REQUIRED, 'Define the DBAL connection to use'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - if ($input->getOption('connection') !== null) { - $this->connectionFactory->setConnectionName($input->getOption('connection')); + public function __invoke( + SymfonyStyle $io, + #[Argument('FQCN or alias of the dataflow type')] string $fqcn, + #[Argument('Options for the dataflow type as a json string')] string $options = '[]', + #[Option('Define the DBAL connection to use')] ?string $connection = null, + ): int { + if ($connection !== null) { + $this->connectionFactory->setConnectionName($connection); } - $fqcnOrAlias = $input->getArgument('fqcn'); - $options = json_decode((string) $input->getArgument('options'), true, 512, \JSON_THROW_ON_ERROR); - $io = new SymfonyStyle($input, $output); - $dataflowType = $this->registry->getDataflowType($fqcnOrAlias); + $options = json_decode($options, true, 512, \JSON_THROW_ON_ERROR); + + $dataflowType = $this->registry->getDataflowType($fqcn); if ($dataflowType instanceof AutoUpdateCountInterface) { $dataflowType->setRepository($this->jobRepository); } diff --git a/src/Command/JobShowCommand.php b/src/Command/JobShowCommand.php index 7d00c27..6d83201 100644 --- a/src/Command/JobShowCommand.php +++ b/src/Command/JobShowCommand.php @@ -8,10 +8,7 @@ use CodeRhapsodie\DataflowBundle\Factory\ConnectionFactory; use CodeRhapsodie\DataflowBundle\Gateway\JobGateway; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Attribute\Option; use Symfony\Component\Console\Style\SymfonyStyle; /** @@ -20,38 +17,31 @@ #[AsCommand('code-rhapsodie:dataflow:job:show', 'Display job details for schedule or specific job', help: <<<'TXT' The %command.name% display job details for schedule or specific job. TXT)] -class JobShowCommand extends Command +final readonly class JobShowCommand { private const STATUS_MAPPING = [ Job::STATUS_PENDING => 'Pending', Job::STATUS_RUNNING => 'Running', Job::STATUS_COMPLETED => 'Completed', + Job::STATUS_QUEUED => 'Queued', + Job::STATUS_CRASHED => 'Crashed', ]; - public function __construct(private readonly JobGateway $jobGateway, private readonly ConnectionFactory $connectionFactory) + public function __construct(private JobGateway $jobGateway, private ConnectionFactory $connectionFactory) { - parent::__construct(); } - protected function configure(): void - { - $this - ->addOption('job-id', null, InputOption::VALUE_REQUIRED, 'Id of the job to get details') - ->addOption('schedule-id', null, InputOption::VALUE_REQUIRED, 'Id of schedule for last execution details') - ->addOption('details', null, InputOption::VALUE_NONE, 'Display full details') - ->addOption('connection', null, InputOption::VALUE_REQUIRED, 'Define the DBAL connection to use'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - if ($input->getOption('connection') !== null) { - $this->connectionFactory->setConnectionName($input->getOption('connection')); + public function __invoke( + SymfonyStyle $io, + #[Option('Id of the job to get details')] ?int $jobId = null, + #[Option('Id of schedule for last execution details')] ?int $scheduleId = null, + #[Option('Display full details')] bool $details = false, + #[Option('Define the DBAL connection to use')] ?string $connection = null, + ): int { + if ($connection !== null) { + $this->connectionFactory->setConnectionName($connection); } - $io = new SymfonyStyle($input, $output); - - $jobId = (int) $input->getOption('job-id'); - $scheduleId = (int) $input->getOption('schedule-id'); if ($jobId && $scheduleId) { $io->error('You must use `job-id` OR `schedule-id` option, not the 2 in the same time.'); @@ -85,14 +75,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int ['Errors', \count((array) $job->getExceptions())], ['Status', $this->translateStatus($job->getStatus())], ]; - if ($input->getOption('details')) { + if ($details) { $display[] = ['Type', $job->getDataflowType()]; $display[] = ['Options', json_encode($job->getOptions(), \JSON_THROW_ON_ERROR)]; $io->section('Summary'); } $io->table(['Field', 'Value'], $display); - if ($input->getOption('details')) { + if ($details) { $io->section('Exceptions'); $exceptions = array_map(static fn (string $exception) => substr($exception, 0, 900).'…', $job->getExceptions()); diff --git a/src/Command/RunPendingDataflowsCommand.php b/src/Command/RunPendingDataflowsCommand.php index 6e8d8d9..0d35ced 100644 --- a/src/Command/RunPendingDataflowsCommand.php +++ b/src/Command/RunPendingDataflowsCommand.php @@ -8,11 +8,9 @@ use CodeRhapsodie\DataflowBundle\Manager\ScheduledDataflowManagerInterface; use CodeRhapsodie\DataflowBundle\Runner\PendingDataflowRunnerInterface; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Attribute\Option; use Symfony\Component\Console\Command\LockableTrait; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; /** * Runs dataflows according to user-defined schedule. @@ -22,31 +20,26 @@ #[AsCommand('code-rhapsodie:dataflow:run-pending', 'Runs dataflows based on the scheduled defined in the UI.', help: <<<'TXT' The %command.name% command runs dataflows according to the schedule defined in the UI by the user. TXT)] -class RunPendingDataflowsCommand extends Command +final class RunPendingDataflowsCommand { use LockableTrait; - public function __construct(private ScheduledDataflowManagerInterface $manager, private PendingDataflowRunnerInterface $runner, private ConnectionFactory $connectionFactory) + public function __construct(private readonly ScheduledDataflowManagerInterface $manager, private readonly PendingDataflowRunnerInterface $runner, private readonly ConnectionFactory $connectionFactory) { - parent::__construct(); } - protected function configure(): void - { - $this - ->addOption('connection', null, InputOption::VALUE_REQUIRED, 'Define the DBAL connection to use'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { + public function __invoke( + SymfonyStyle $io, + #[Option('Define the DBAL connection to use')] ?string $connection = null, + ): int { if (!$this->lock()) { - $output->writeln('The command is already running in another process.'); + $io->writeln('The command is already running in another process.'); return 0; } - if ($input->getOption('connection') !== null) { - $this->connectionFactory->setConnectionName($input->getOption('connection')); + if ($connection !== null) { + $this->connectionFactory->setConnectionName($connection); } $this->manager->createJobsFromScheduledDataflows(); diff --git a/src/Command/ScheduleListCommand.php b/src/Command/ScheduleListCommand.php index fe820c7..6f7b2b0 100644 --- a/src/Command/ScheduleListCommand.php +++ b/src/Command/ScheduleListCommand.php @@ -7,10 +7,7 @@ use CodeRhapsodie\DataflowBundle\Factory\ConnectionFactory; use CodeRhapsodie\DataflowBundle\Repository\ScheduledDataflowRepository; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Attribute\Option; use Symfony\Component\Console\Style\SymfonyStyle; /** @@ -19,25 +16,20 @@ #[AsCommand('code-rhapsodie:dataflow:schedule:list', 'List scheduled dataflows', help: <<<'TXT' The %command.name% lists all scheduled dataflows. TXT)] -class ScheduleListCommand extends Command +final readonly class ScheduleListCommand { - public function __construct(private readonly ScheduledDataflowRepository $scheduledDataflowRepository, private readonly ConnectionFactory $connectionFactory) + public function __construct(private ScheduledDataflowRepository $scheduledDataflowRepository, private ConnectionFactory $connectionFactory) { - parent::__construct(); } - protected function configure(): void - { - $this - ->addOption('connection', null, InputOption::VALUE_REQUIRED, 'Define the DBAL connection to use'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - if ($input->getOption('connection') !== null) { - $this->connectionFactory->setConnectionName($input->getOption('connection')); + public function __invoke( + SymfonyStyle $io, + #[Option('Define the DBAL connection to use')] ?string $connection = null, + ): int { + if ($connection !== null) { + $this->connectionFactory->setConnectionName($connection); } - $io = new SymfonyStyle($input, $output); + $display = []; $schedules = $this->scheduledDataflowRepository->listAllOrderedByLabel(); foreach ($schedules as $schedule) { diff --git a/src/Command/SetCrashedCommand.php b/src/Command/SetCrashedCommand.php index ce508f7..6493292 100644 --- a/src/Command/SetCrashedCommand.php +++ b/src/Command/SetCrashedCommand.php @@ -7,24 +7,17 @@ use CodeRhapsodie\DataflowBundle\Repository\JobRepository; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; #[AsCommand(name: 'code-rhapsodie:dataflow:job:set-crashed', description: 'Set long running jobs as crashed.', help: <<<'TXT' How long jobs have to run before they are set as crashed can be configured with the "job_history.crashed_delay" configuration. TXT)] -class SetCrashedCommand extends Command +final readonly class SetCrashedCommand { - public function __construct(private readonly JobRepository $jobRepository, private readonly int $crashedDelay) + public function __construct(private JobRepository $jobRepository, private int $crashedDelay) { - parent::__construct(); } - protected function configure() - { - } - - protected function execute(InputInterface $input, OutputInterface $output): int + public function __invoke(): int { $this->jobRepository->crashLongRunning($this->crashedDelay); diff --git a/src/DataflowType/Dataflow/AMPAsyncDataflow.php b/src/DataflowType/Dataflow/AMPAsyncDataflow.php index 1ee099b..8d64929 100644 --- a/src/DataflowType/Dataflow/AMPAsyncDataflow.php +++ b/src/DataflowType/Dataflow/AMPAsyncDataflow.php @@ -23,7 +23,7 @@ class AMPAsyncDataflow implements DataflowInterface, LoggerAwareInterface { use LoggerAwareTrait; - /** @var callable[] */ + /** @var array> */ private array $steps = []; /** @var WriterInterface[] */ diff --git a/src/DataflowType/Dataflow/Dataflow.php b/src/DataflowType/Dataflow/Dataflow.php index 2797062..81b2d99 100644 --- a/src/DataflowType/Dataflow/Dataflow.php +++ b/src/DataflowType/Dataflow/Dataflow.php @@ -21,8 +21,6 @@ class Dataflow implements DataflowInterface, LoggerAwareInterface private ?\Closure $customExceptionIndex = null; - private ?\DateTimeInterface $dateTime = null; - /** * @var \Closure[] */ diff --git a/src/Entity/Job.php b/src/Entity/Job.php index dd721b2..020f839 100644 --- a/src/Entity/Job.php +++ b/src/Entity/Job.php @@ -64,7 +64,7 @@ class Job public static function createFromScheduledDataflow(ScheduledDataflow $scheduled): self { - return (new static()) + return (new self()) ->setStatus(static::STATUS_PENDING) ->setDataflowType($scheduled->getDataflowType()) ->setOptions($scheduled->getOptions()) @@ -75,7 +75,7 @@ public static function createFromScheduledDataflow(ScheduledDataflow $scheduled) public static function createFromArray(array $datas) { - $lost = array_diff(static::KEYS, array_keys($datas)); + $lost = array_diff(self::KEYS, array_keys($datas)); if (\count($lost) > 0) { throw new \LogicException('The first argument of '.__METHOD__.' must be contains: "'.implode(', ', $lost).'"'); } diff --git a/src/Entity/ScheduledDataflow.php b/src/Entity/ScheduledDataflow.php index 3f138cd..161c806 100644 --- a/src/Entity/ScheduledDataflow.php +++ b/src/Entity/ScheduledDataflow.php @@ -49,7 +49,7 @@ class ScheduledDataflow public static function createFromArray(array $datas) { - $lost = array_diff(static::KEYS, array_keys($datas)); + $lost = array_diff(self::KEYS, array_keys($datas)); if (\count($lost) > 0) { throw new \LogicException('The first argument of '.__METHOD__.' must be contains: "'.implode(', ', $lost).'"'); } From 8f00e024aa947d23a50575817007b4facb2a487b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20J?= Date: Tue, 31 Mar 2026 16:29:37 +0200 Subject: [PATCH 7/7] README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c799c0b..5c251a7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ providing an easy way to create import / export dataflow. | Dataflow | Symfony | Support | |----------|--------------------------|---------| -| 5.x | 7.x | yes | +| 5.x | ^7.3 | yes | | 4.x | 3.4 \| 4.x \| 5.x \| 6.x | yes | | 3.x | 3.4 \| 4.x \| 5.x | no | | 2.x | 3.4 \| 4.x | no |