diff --git a/packages/cron-job/Command/QueueCronJobByNameCommand.php b/packages/cron-job/Command/QueueCronJobByNameCommand.php new file mode 100644 index 00000000..65343872 --- /dev/null +++ b/packages/cron-job/Command/QueueCronJobByNameCommand.php @@ -0,0 +1,56 @@ +setDescription('Queues cron job by name') + ->addArgument('name', InputArgument::REQUIRED, 'Cron job name'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $cronJob = $this->managerRegistry + ->getRepository(CronJob::class) + ->findOneBy(['name' => $input->getArgument('name')]); + + if (null === $cronJob) { + $io->error('Cron job could not be found.'); + + return Command::FAILURE; + } + + $io->section('Queueing cron job...'); + + $this->cronJobProcessor->queue($cronJob, true); + + $io->section('Cron job successfully queued.'); + + return Command::SUCCESS; + } +} diff --git a/packages/cron-job/Command/QueueDueCronJobsCommand.php b/packages/cron-job/Command/QueueDueCronJobsCommand.php new file mode 100644 index 00000000..46923246 --- /dev/null +++ b/packages/cron-job/Command/QueueDueCronJobsCommand.php @@ -0,0 +1,61 @@ +setDescription('Queues due cron jobs'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $io->section('Queueing cron jobs...'); + + $cronJobs = array_map( + static fn(CronJob $cronJob): bool => $cronJob->isDue(), + $this->managerRegistry + ->getRepository(CronJob::class) + ->findAll() + ); + + $progress = $io->createProgressBar(\count($cronJobs)); + $progress->setFormat(ProgressBar::FORMAT_DEBUG); + + foreach ($cronJobs as $cronJob) { + $this->cronJobProcessor->queue($cronJob); + + $progress->advance(); + } + + $progress->finish(); + + $io->newLine(2); + $io->success('Cron jobs successfully queued...'); + + return Command::SUCCESS; + } +} diff --git a/packages/cron-job/CronJobExecutionFactory.php b/packages/cron-job/CronJobExecutionFactory.php new file mode 100644 index 00000000..3bc2c7bf --- /dev/null +++ b/packages/cron-job/CronJobExecutionFactory.php @@ -0,0 +1,19 @@ +setCronJob($cronJob) + ->setRequestedAt(new \DateTimeImmutable()) + ->setForce($force); + } +} diff --git a/packages/cron-job/CronJobProcessor.php b/packages/cron-job/CronJobProcessor.php index 59db23e3..f16ce3fd 100644 --- a/packages/cron-job/CronJobProcessor.php +++ b/packages/cron-job/CronJobProcessor.php @@ -4,15 +4,52 @@ namespace Draw\Component\CronJob; +use Doctrine\Persistence\ManagerRegistry; use Draw\Component\CronJob\Entity\CronJob; +use Draw\Component\CronJob\Entity\CronJobExecution; +use Draw\Component\CronJob\Message\ExecuteCronJobMessage; +use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Process\Process; class CronJobProcessor { - public function __construct() + public function __construct( + private CronJobExecutionFactory $cronJobExecutionFactory, + private ManagerRegistry $managerRegistry, + private MessageBusInterface $messageBus, + ) { + } + + public function queue(CronJob $cronJob, bool $force = false): void { + $manager = $this->managerRegistry->getManagerForClass(CronJobExecution::class); + + $manager->persist($execution = $this->cronJobExecutionFactory->create($cronJob, $force)); + $manager->flush(); + + $this->messageBus->dispatch(new ExecuteCronJobMessage($execution)); } - public function process(CronJob $cronJob): void + public function process(CronJobExecution $execution): void { + $manager = $this->managerRegistry->getManagerForClass(CronJobExecution::class); + + $execution->start(); + $manager->flush(); + + $command = $execution->getCronJob()->getCommand(); + + $process = Process::fromShellCommandline($command) + ->setTimeout(1800); + + try { + $process->run(); + + $execution->end($process->getExitCode()); + } catch (\Throwable $error) { + $execution->fail($process->getExitCode(), ['TODO']); + } finally { + $manager->flush(); + } } } diff --git a/packages/cron-job/Entity/CronJob.php b/packages/cron-job/Entity/CronJob.php index 72ad4213..b3856297 100644 --- a/packages/cron-job/Entity/CronJob.php +++ b/packages/cron-job/Entity/CronJob.php @@ -162,6 +162,11 @@ public function removeExecution(CronJobExecution $execution): self return $this; } + public function isDue(): bool + { + return false; + } + public function __toString(): string { return $this->name; diff --git a/packages/cron-job/Entity/CronJobExecution.php b/packages/cron-job/Entity/CronJobExecution.php index 65e67cc2..2ae096f1 100644 --- a/packages/cron-job/Entity/CronJobExecution.php +++ b/packages/cron-job/Entity/CronJobExecution.php @@ -154,4 +154,26 @@ public function setCronJob(?CronJob $cronJob): self return $this; } + + public function start(): void + { + $this + ->setExecutionStartedAt(new \DateTimeImmutable()) + ->setExecutionEndedAt(null); + } + + public function end(?int $exitCode): void + { + $this + ->setExitCode($exitCode) + ->setExecutionEndedAt($executionEndedAt = new \DateTimeImmutable()) + ->setExecutionDelay($executionEndedAt->getTimestamp() - $this->getExecutionStartedAt()->getTimestamp()); + } + + public function fail(?int $exitCode, ?array $error): void + { + $this + ->setExitCode($exitCode) + ->setError($error); + } } diff --git a/packages/cron-job/MessageHandler/ExecuteCronJobMessageHandler.php b/packages/cron-job/MessageHandler/ExecuteCronJobMessageHandler.php index a83057b6..c4299011 100644 --- a/packages/cron-job/MessageHandler/ExecuteCronJobMessageHandler.php +++ b/packages/cron-job/MessageHandler/ExecuteCronJobMessageHandler.php @@ -4,6 +4,7 @@ namespace Draw\Component\CronJob\MessageHandler; +use Draw\Component\CronJob\CronJobProcessor; use Draw\Component\CronJob\Event\PostCronJobExecutionEvent; use Draw\Component\CronJob\Event\PreCronJobExecutionEvent; use Draw\Component\CronJob\Message\ExecuteCronJobMessage; @@ -14,6 +15,7 @@ class ExecuteCronJobMessageHandler { public function __construct( private EventDispatcherInterface $eventDispatcher, + private CronJobProcessor $cronJobProcessor, ) { } @@ -28,7 +30,7 @@ public function handleExecuteCronJobMessage(ExecuteCronJobMessage $message): voi return; } - // @TODO + $this->cronJobProcessor->process($execution); $this->eventDispatcher->dispatch(new PostCronJobExecutionEvent($execution)); } diff --git a/packages/cron-job/composer.json b/packages/cron-job/composer.json index 88ce4705..5e4ae596 100644 --- a/packages/cron-job/composer.json +++ b/packages/cron-job/composer.json @@ -12,11 +12,13 @@ ], "require": { "php": ">=8.1", - "doctrine/orm": "^3.1", + "doctrine/orm": "^2.11", "draw/core": "^0.11", "draw/messenger": "^0.11", + "symfony/console": "^6.4.0", "symfony/event-dispatcher": "^6.4.0", - "symfony/messenger": "^6.4.0" + "symfony/messenger": "^6.4.0", + "symfony/process": "^6.4.0" }, "require-dev": { "phpunit/phpunit": "^9.0 || ^10.0", diff --git a/packages/framework-extra-bundle/Tests/DependencyInjection/Integration/CronJobIntegrationTest.php b/packages/framework-extra-bundle/Tests/DependencyInjection/Integration/CronJobIntegrationTest.php new file mode 100644 index 00000000..3649761a --- /dev/null +++ b/packages/framework-extra-bundle/Tests/DependencyInjection/Integration/CronJobIntegrationTest.php @@ -0,0 +1,52 @@ +add('id') + ->add('name') + ->add('command') + ->add('active'); + + } + + protected function configureListFields(ListMapper $list): void + { + $list + ->addIdentifier('name') + ->add('command') + ->add('schedule') + ->add('active', null, ['editable' => true]) + ->add('timeToLive') + ->add('priority'); + } + + protected function configureFormFields(FormMapper $form): void + { + $form + ->add('name') + ->add( + 'command', + null, + [ + 'help' => 'Enter the full command to run excluding stdOut and stdErr directive (... 2>&1 | logger -t ...)
Parameters bag is available. Use like %kernel.project_dir%
', + ] + ) + ->add('schedule') + ->add('active') + ->add('timeToLive') + ->add('priority'); + } + + protected function configureShowFields(ShowMapper $show): void + { + $show + ->add('name') + ->add('command') + ->add('schedule') + ->add('active', null, ['editable' => true]) + ->add('timeToLive') + ->add('priority'); + } +} diff --git a/packages/sonata-integration-bundle/CronJob/Admin/CronJobExecutionAdmin.php b/packages/sonata-integration-bundle/CronJob/Admin/CronJobExecutionAdmin.php new file mode 100644 index 00000000..56ecc32f --- /dev/null +++ b/packages/sonata-integration-bundle/CronJob/Admin/CronJobExecutionAdmin.php @@ -0,0 +1,12 @@ +