mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-12 21:34:25 +00:00
[cron-job] allow a cronjob to pass data from one execution to another
When a cronjob is executed, it may return an array of data. This data will be passed as parameter on the next execution
This commit is contained in:
parent
e38b369149
commit
3f66e1a862
6
.changes/unreleased/DX-20230712-113603.yaml
Normal file
6
.changes/unreleased/DX-20230712-113603.yaml
Normal file
@ -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: ""
|
@ -19,5 +19,13 @@ interface CronJobInterface
|
|||||||
|
|
||||||
public function getKey(): string;
|
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;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ namespace Chill\MainBundle\Cron;
|
|||||||
use Chill\MainBundle\Entity\CronJobExecution;
|
use Chill\MainBundle\Entity\CronJobExecution;
|
||||||
use Chill\MainBundle\Repository\CronJobExecutionRepositoryInterface;
|
use Chill\MainBundle\Repository\CronJobExecutionRepositoryInterface;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Psr\Log\LoggerInterface;
|
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_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 CronJobExecutionRepositoryInterface $cronJobExecutionRepository;
|
||||||
|
|
||||||
private EntityManagerInterface $entityManager;
|
private EntityManagerInterface $entityManager;
|
||||||
@ -85,6 +88,9 @@ class CronManager implements CronManagerInterface
|
|||||||
foreach ($orderedJobs as $job) {
|
foreach ($orderedJobs as $job) {
|
||||||
if ($job->canRun($lasts[$job->getKey()] ?? null)) {
|
if ($job->canRun($lasts[$job->getKey()] ?? null)) {
|
||||||
if (array_key_exists($job->getKey(), $lasts)) {
|
if (array_key_exists($job->getKey(), $lasts)) {
|
||||||
|
|
||||||
|
$executionData = $lasts[$job->getKey()]->getLastExecutionData();
|
||||||
|
|
||||||
$this->entityManager
|
$this->entityManager
|
||||||
->createQuery(self::UPDATE_BEFORE_EXEC)
|
->createQuery(self::UPDATE_BEFORE_EXEC)
|
||||||
->setParameters([
|
->setParameters([
|
||||||
@ -96,12 +102,17 @@ class CronManager implements CronManagerInterface
|
|||||||
$execution = new CronJobExecution($job->getKey());
|
$execution = new CronJobExecution($job->getKey());
|
||||||
$this->entityManager->persist($execution);
|
$this->entityManager->persist($execution);
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$executionData = $execution->getLastExecutionData();
|
||||||
}
|
}
|
||||||
$this->entityManager->clear();
|
$this->entityManager->clear();
|
||||||
|
|
||||||
|
// note: at this step, the entity manager does not have any entity CronJobExecution
|
||||||
|
// into his internal memory
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->logger->info(sprintf('%sWill run job', self::LOG_PREFIX), ['job' => $job->getKey()]);
|
$this->logger->info(sprintf('%sWill run job', self::LOG_PREFIX), ['job' => $job->getKey()]);
|
||||||
$job->run();
|
$result = $job->run($executionData);
|
||||||
|
|
||||||
$this->entityManager
|
$this->entityManager
|
||||||
->createQuery(self::UPDATE_AFTER_EXEC)
|
->createQuery(self::UPDATE_AFTER_EXEC)
|
||||||
@ -112,6 +123,14 @@ class CronManager implements CronManagerInterface
|
|||||||
])
|
])
|
||||||
->execute();
|
->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()]);
|
$this->logger->info(sprintf('%sSuccessfully run job', self::LOG_PREFIX), ['job' => $job->getKey()]);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -133,7 +152,7 @@ class CronManager implements CronManagerInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<0: CronJobInterface[], 1: array<string, CronJobExecution>>
|
* @return array{0: array<CronJobInterface>, 1: array<string, CronJobExecution>}
|
||||||
*/
|
*/
|
||||||
private function getOrderedJobs(): array
|
private function getOrderedJobs(): array
|
||||||
{
|
{
|
||||||
@ -174,7 +193,7 @@ class CronManager implements CronManagerInterface
|
|||||||
{
|
{
|
||||||
foreach ($this->jobs as $job) {
|
foreach ($this->jobs as $job) {
|
||||||
if ($job->getKey() === $forceJob) {
|
if ($job->getKey() === $forceJob) {
|
||||||
$job->run();
|
$job->run([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,8 +49,10 @@ class RefreshAddressToGeographicalUnitMaterializedViewCronJob implements CronJob
|
|||||||
return 'refresh-materialized-view-address-to-geog-units';
|
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');
|
$this->connection->executeQuery('REFRESH MATERIALIZED VIEW view_chill_main_address_geographical_unit');
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Cron;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Cron\CronJobInterface;
|
||||||
|
use Chill\MainBundle\Cron\CronManager;
|
||||||
|
use Chill\MainBundle\Entity\CronJobExecution;
|
||||||
|
use Chill\MainBundle\Repository\CronJobExecutionRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Prophecy\Argument;
|
||||||
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
use Psr\Log\NullLogger;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class CronJobDatabaseInteractionTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
use ProphecyTrait;
|
||||||
|
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
|
private CronJobExecutionRepository $cronJobExecutionRepository;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
$this->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'];
|
||||||
|
}
|
||||||
|
}
|
@ -40,7 +40,7 @@ final class CronManagerTest extends TestCase
|
|||||||
$jobToExecute = $this->prophesize(CronJobInterface::class);
|
$jobToExecute = $this->prophesize(CronJobInterface::class);
|
||||||
$jobToExecute->getKey()->willReturn('to-exec');
|
$jobToExecute->getKey()->willReturn('to-exec');
|
||||||
$jobToExecute->canRun(Argument::type(CronJobExecution::class))->willReturn(true);
|
$jobToExecute->canRun(Argument::type(CronJobExecution::class))->willReturn(true);
|
||||||
$jobToExecute->run()->shouldBeCalled();
|
$jobToExecute->run([])->shouldBeCalled();
|
||||||
|
|
||||||
$executions = [
|
$executions = [
|
||||||
['key' => $jobOld1->getKey(), 'lastStart' => new DateTimeImmutable('yesterday'), 'lastEnd' => new DateTimeImmutable('1 hours ago'), 'lastStatus' => CronJobExecution::SUCCESS],
|
['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');
|
$jobAlreadyExecuted = new JobCanRun('k');
|
||||||
$jobNeverExecuted = $this->prophesize(CronJobInterface::class);
|
$jobNeverExecuted = $this->prophesize(CronJobInterface::class);
|
||||||
$jobNeverExecuted->getKey()->willReturn('never-executed');
|
$jobNeverExecuted->getKey()->willReturn('never-executed');
|
||||||
$jobNeverExecuted->run()->shouldBeCalled();
|
$jobNeverExecuted->run([])->shouldBeCalled();
|
||||||
$jobNeverExecuted->canRun(null)->willReturn(true);
|
$jobNeverExecuted->canRun(null)->willReturn(true);
|
||||||
|
|
||||||
$executions = [
|
$executions = [
|
||||||
@ -86,7 +86,7 @@ final class CronManagerTest extends TestCase
|
|||||||
$jobAlreadyExecuted = new JobCanRun('k');
|
$jobAlreadyExecuted = new JobCanRun('k');
|
||||||
$jobNeverExecuted = $this->prophesize(CronJobInterface::class);
|
$jobNeverExecuted = $this->prophesize(CronJobInterface::class);
|
||||||
$jobNeverExecuted->getKey()->willReturn('never-executed');
|
$jobNeverExecuted->getKey()->willReturn('never-executed');
|
||||||
$jobNeverExecuted->run()->shouldBeCalled();
|
$jobNeverExecuted->run([])->shouldBeCalled();
|
||||||
$jobNeverExecuted->canRun(null)->willReturn(true);
|
$jobNeverExecuted->canRun(null)->willReturn(true);
|
||||||
|
|
||||||
$executions = [
|
$executions = [
|
||||||
@ -178,8 +178,9 @@ class JobCanRun implements CronJobInterface
|
|||||||
return $this->key;
|
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';
|
return 'job-b';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function run(): void
|
public function run(array $lastExecutionData): null|array
|
||||||
{
|
{
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,10 @@ readonly class AccompanyingPeriodStepChangeCronjob implements CronJobInterface
|
|||||||
return 'accompanying-period-step-change';
|
return 'accompanying-period-step-change';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function run(): void
|
public function run(array $lastExecutionData): null|array
|
||||||
{
|
{
|
||||||
($this->requestor)();
|
($this->requestor)();
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user