diff --git a/.changes/unreleased/DX-20230712-113603.yaml b/.changes/unreleased/DX-20230712-113603.yaml new file mode 100644 index 000000000..518ac3ca9 --- /dev/null +++ b/.changes/unreleased/DX-20230712-113603.yaml @@ -0,0 +1,6 @@ +kind: DX +body: '[cronjob] when a cronjob is executed, it may return an array of data that will + be passed as argument on the next execution' +time: 2023-07-12T11:36:03.813179067+02:00 +custom: + Issue: "" diff --git a/src/Bundle/ChillMainBundle/Cron/CronJobInterface.php b/src/Bundle/ChillMainBundle/Cron/CronJobInterface.php index 4e1ca9ff6..69edf8464 100644 --- a/src/Bundle/ChillMainBundle/Cron/CronJobInterface.php +++ b/src/Bundle/ChillMainBundle/Cron/CronJobInterface.php @@ -19,5 +19,13 @@ interface CronJobInterface public function getKey(): string; - public function run(): void; + /** + * Execute the cronjob + * + * If data is returned, this data is passed as argument on the next execution + * + * @param array $lastExecutionData the data which was returned from the previous execution + * @return array|null optionally return an array with the same data than the previous execution + */ + public function run(array $lastExecutionData): null|array; } diff --git a/src/Bundle/ChillMainBundle/Cron/CronManager.php b/src/Bundle/ChillMainBundle/Cron/CronManager.php index f69dcba76..a3e82a170 100644 --- a/src/Bundle/ChillMainBundle/Cron/CronManager.php +++ b/src/Bundle/ChillMainBundle/Cron/CronManager.php @@ -14,6 +14,7 @@ namespace Chill\MainBundle\Cron; use Chill\MainBundle\Entity\CronJobExecution; use Chill\MainBundle\Repository\CronJobExecutionRepositoryInterface; use DateTimeImmutable; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Exception; use Psr\Log\LoggerInterface; @@ -46,6 +47,8 @@ class CronManager implements CronManagerInterface private const UPDATE_BEFORE_EXEC = 'UPDATE ' . CronJobExecution::class . ' cr SET cr.lastStart = :now WHERE cr.key = :key'; + private const UPDATE_LAST_EXECUTION_DATA = 'UPDATE ' . CronJobExecution::class . ' cr SET cr.lastExecutionData = :data WHERE cr.key = :key'; + private CronJobExecutionRepositoryInterface $cronJobExecutionRepository; private EntityManagerInterface $entityManager; @@ -85,6 +88,9 @@ class CronManager implements CronManagerInterface foreach ($orderedJobs as $job) { if ($job->canRun($lasts[$job->getKey()] ?? null)) { if (array_key_exists($job->getKey(), $lasts)) { + + $executionData = $lasts[$job->getKey()]->getLastExecutionData(); + $this->entityManager ->createQuery(self::UPDATE_BEFORE_EXEC) ->setParameters([ @@ -96,12 +102,17 @@ class CronManager implements CronManagerInterface $execution = new CronJobExecution($job->getKey()); $this->entityManager->persist($execution); $this->entityManager->flush(); + + $executionData = $execution->getLastExecutionData(); } $this->entityManager->clear(); + // note: at this step, the entity manager does not have any entity CronJobExecution + // into his internal memory + try { $this->logger->info(sprintf('%sWill run job', self::LOG_PREFIX), ['job' => $job->getKey()]); - $job->run(); + $result = $job->run($executionData); $this->entityManager ->createQuery(self::UPDATE_AFTER_EXEC) @@ -112,6 +123,14 @@ class CronManager implements CronManagerInterface ]) ->execute(); + if (null !== $result) { + $this->entityManager + ->createQuery(self::UPDATE_LAST_EXECUTION_DATA) + ->setParameter('data', $result, Types::JSON) + ->setParameter('key', $job->getKey(), Types::STRING) + ->execute(); + } + $this->logger->info(sprintf('%sSuccessfully run job', self::LOG_PREFIX), ['job' => $job->getKey()]); return; @@ -133,7 +152,7 @@ class CronManager implements CronManagerInterface } /** - * @return array<0: CronJobInterface[], 1: array> + * @return array{0: array, 1: array} */ private function getOrderedJobs(): array { @@ -174,7 +193,7 @@ class CronManager implements CronManagerInterface { foreach ($this->jobs as $job) { if ($job->getKey() === $forceJob) { - $job->run(); + $job->run([]); } } } diff --git a/src/Bundle/ChillMainBundle/Service/AddressGeographicalUnit/RefreshAddressToGeographicalUnitMaterializedViewCronJob.php b/src/Bundle/ChillMainBundle/Service/AddressGeographicalUnit/RefreshAddressToGeographicalUnitMaterializedViewCronJob.php index 9dbb38a3f..3a6ff8fb9 100644 --- a/src/Bundle/ChillMainBundle/Service/AddressGeographicalUnit/RefreshAddressToGeographicalUnitMaterializedViewCronJob.php +++ b/src/Bundle/ChillMainBundle/Service/AddressGeographicalUnit/RefreshAddressToGeographicalUnitMaterializedViewCronJob.php @@ -49,8 +49,10 @@ class RefreshAddressToGeographicalUnitMaterializedViewCronJob implements CronJob return 'refresh-materialized-view-address-to-geog-units'; } - public function run(): void + public function run(array $lastExecutionData): null|array { $this->connection->executeQuery('REFRESH MATERIALIZED VIEW view_chill_main_address_geographical_unit'); + + return null; } } diff --git a/src/Bundle/ChillMainBundle/Tests/Cron/CronJobDatabaseInteractionTest.php b/src/Bundle/ChillMainBundle/Tests/Cron/CronJobDatabaseInteractionTest.php new file mode 100644 index 000000000..0b80730fe --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Cron/CronJobDatabaseInteractionTest.php @@ -0,0 +1,87 @@ +entityManager = self::$container->get(EntityManagerInterface::class); + $this->cronJobExecutionRepository = self::$container->get(CronJobExecutionRepository::class); + } + + public function testCompleteLifeCycle(): void + { + $cronjob = $this->prophesize(CronJobInterface::class); + $cronjob->canRun(null)->willReturn(true); + $cronjob->canRun(Argument::type(CronJobExecution::class))->willReturn(true); + $cronjob->getKey()->willReturn('test-with-data'); + $cronjob->run([])->willReturn(['test' => 'execution-0']); + $cronjob->run(['test' => 'execution-0'])->willReturn(['test' => 'execution-1']); + + $cronjob->run([])->shouldBeCalledOnce(); + $cronjob->run(['test' => 'execution-0'])->shouldBeCalledOnce(); + + $manager = new CronManager( + $this->cronJobExecutionRepository, + $this->entityManager, + [$cronjob->reveal()], + new NullLogger() + ); + + // run a first time + $manager->run(); + + // run a second time + $manager->run(); + } + +} + +class JobWithReturn implements CronJobInterface +{ + public function canRun(?CronJobExecution $cronJobExecution): bool + { + return true; + } + + public function getKey(): string + { + return 'with-data'; + } + + public function run(array $lastExecutionData): null|array + { + return ['data' => 'test']; + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Cron/CronManagerTest.php b/src/Bundle/ChillMainBundle/Tests/Cron/CronManagerTest.php index 4b812ce2b..47c929a52 100644 --- a/src/Bundle/ChillMainBundle/Tests/Cron/CronManagerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Cron/CronManagerTest.php @@ -40,7 +40,7 @@ final class CronManagerTest extends TestCase $jobToExecute = $this->prophesize(CronJobInterface::class); $jobToExecute->getKey()->willReturn('to-exec'); $jobToExecute->canRun(Argument::type(CronJobExecution::class))->willReturn(true); - $jobToExecute->run()->shouldBeCalled(); + $jobToExecute->run([])->shouldBeCalled(); $executions = [ ['key' => $jobOld1->getKey(), 'lastStart' => new DateTimeImmutable('yesterday'), 'lastEnd' => new DateTimeImmutable('1 hours ago'), 'lastStatus' => CronJobExecution::SUCCESS], @@ -64,7 +64,7 @@ final class CronManagerTest extends TestCase $jobAlreadyExecuted = new JobCanRun('k'); $jobNeverExecuted = $this->prophesize(CronJobInterface::class); $jobNeverExecuted->getKey()->willReturn('never-executed'); - $jobNeverExecuted->run()->shouldBeCalled(); + $jobNeverExecuted->run([])->shouldBeCalled(); $jobNeverExecuted->canRun(null)->willReturn(true); $executions = [ @@ -86,7 +86,7 @@ final class CronManagerTest extends TestCase $jobAlreadyExecuted = new JobCanRun('k'); $jobNeverExecuted = $this->prophesize(CronJobInterface::class); $jobNeverExecuted->getKey()->willReturn('never-executed'); - $jobNeverExecuted->run()->shouldBeCalled(); + $jobNeverExecuted->run([])->shouldBeCalled(); $jobNeverExecuted->canRun(null)->willReturn(true); $executions = [ @@ -178,8 +178,9 @@ class JobCanRun implements CronJobInterface return $this->key; } - public function run(): void + public function run(array $lastExecutionData): null|array { + return null; } } @@ -195,7 +196,8 @@ class JobCannotRun implements CronJobInterface return 'job-b'; } - public function run(): void + public function run(array $lastExecutionData): null|array { + return null; } } diff --git a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Lifecycle/AccompanyingPeriodStepChangeCronjob.php b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Lifecycle/AccompanyingPeriodStepChangeCronjob.php index f637e70b9..2ddf3415c 100644 --- a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Lifecycle/AccompanyingPeriodStepChangeCronjob.php +++ b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Lifecycle/AccompanyingPeriodStepChangeCronjob.php @@ -39,8 +39,10 @@ readonly class AccompanyingPeriodStepChangeCronjob implements CronJobInterface return 'accompanying-period-step-change'; } - public function run(): void + public function run(array $lastExecutionData): null|array { ($this->requestor)(); + + return null; } }