mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-11-04 03:08: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:
		
							
								
								
									
										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 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\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<string, CronJobExecution>>
 | 
			
		||||
     * @return array{0: array<CronJobInterface>, 1: array<string, CronJobExecution>}
 | 
			
		||||
     */
 | 
			
		||||
    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([]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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->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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user