mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-22 23:53:50 +00:00
When a cronjob is executed, it may return an array of data. This data will be passed as parameter on the next execution
201 lines
7.0 KiB
PHP
201 lines
7.0 KiB
PHP
<?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 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;
|
|
use function array_key_exists;
|
|
|
|
/**
|
|
* Manage cronjob and execute them.
|
|
*
|
|
* If any :code:`job` argument is given, the :code:`CronManager` schedule job with those steps:
|
|
*
|
|
* - the tasks are ordered, with:
|
|
* - a priority is given for tasks that weren't never executed;
|
|
* - then, the tasks are ordered, the last executed are the first in the list
|
|
*
|
|
* Then, for each tasks, and in the given order, the first task where :code:`canRun` return :code:`TRUE` will be executed.
|
|
*
|
|
* The error inside job execution are catched (with the exception of _out of memory error_).
|
|
*
|
|
* The manager will mark the task as executed even if an error is catched. This will lead as failed job
|
|
* will not have priority any more on other tasks.
|
|
*
|
|
* If a tasks is "forced", there is no test about eligibility of the task (the `canRun` method is not called),
|
|
* and the last task execution is not recorded.
|
|
*/
|
|
class CronManager implements CronManagerInterface
|
|
{
|
|
private const LOG_PREFIX = '[cron manager] ';
|
|
|
|
private const UPDATE_AFTER_EXEC = 'UPDATE ' . CronJobExecution::class . ' cr SET cr.lastEnd = :now, cr.lastStatus = :status 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 EntityManagerInterface $entityManager;
|
|
|
|
/**
|
|
* @var iterable<CronJobInterface>
|
|
*/
|
|
private iterable $jobs;
|
|
|
|
private LoggerInterface $logger;
|
|
|
|
/**
|
|
* @param CronJobInterface[] $jobs
|
|
*/
|
|
public function __construct(
|
|
CronJobExecutionRepositoryInterface $cronJobExecutionRepository,
|
|
EntityManagerInterface $entityManager,
|
|
iterable $jobs,
|
|
LoggerInterface $logger
|
|
) {
|
|
$this->cronJobExecutionRepository = $cronJobExecutionRepository;
|
|
$this->entityManager = $entityManager;
|
|
$this->jobs = $jobs;
|
|
$this->logger = $logger;
|
|
}
|
|
|
|
public function run(?string $forceJob = null): void
|
|
{
|
|
if (null !== $forceJob) {
|
|
$this->runForce($forceJob);
|
|
|
|
return;
|
|
}
|
|
|
|
[$orderedJobs, $lasts] = $this->getOrderedJobs();
|
|
|
|
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([
|
|
'now' => new DateTimeImmutable('now'),
|
|
'key' => $job->getKey(),
|
|
])
|
|
->execute();
|
|
} else {
|
|
$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()]);
|
|
$result = $job->run($executionData);
|
|
|
|
$this->entityManager
|
|
->createQuery(self::UPDATE_AFTER_EXEC)
|
|
->setParameters([
|
|
'now' => new DateTimeImmutable('now'),
|
|
'status' => CronJobExecution::SUCCESS,
|
|
'key' => $job->getKey(),
|
|
])
|
|
->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;
|
|
} catch (Exception $e) {
|
|
$this->logger->error(sprintf('%sRunning job failed', self::LOG_PREFIX), ['job' => $job->getKey()]);
|
|
$this->entityManager
|
|
->createQuery(self::UPDATE_AFTER_EXEC)
|
|
->setParameters([
|
|
'now' => new DateTimeImmutable('now'),
|
|
'status' => CronJobExecution::FAILURE,
|
|
'key' => $job->getKey(),
|
|
])
|
|
->execute();
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array{0: array<CronJobInterface>, 1: array<string, CronJobExecution>}
|
|
*/
|
|
private function getOrderedJobs(): array
|
|
{
|
|
/** @var array<string, CronJobExecution> $lasts */
|
|
$lasts = [];
|
|
|
|
foreach ($this->cronJobExecutionRepository->findAll() as $execution) {
|
|
$lasts[$execution->getKey()] = $execution;
|
|
}
|
|
|
|
// order by last, NULL first
|
|
$orderedJobs = iterator_to_array($this->jobs);
|
|
usort(
|
|
$orderedJobs,
|
|
static function (CronJobInterface $a, CronJobInterface $b) use ($lasts): int {
|
|
if (
|
|
(!array_key_exists($a->getKey(), $lasts) && !array_key_exists($b->getKey(), $lasts))
|
|
) {
|
|
return 0;
|
|
}
|
|
|
|
if (!array_key_exists($a->getKey(), $lasts) && array_key_exists($b->getKey(), $lasts)) {
|
|
return -1;
|
|
}
|
|
|
|
if (!array_key_exists($b->getKey(), $lasts) && array_key_exists($a->getKey(), $lasts)) {
|
|
return 1;
|
|
}
|
|
|
|
return $lasts[$a->getKey()]->getLastStart() <=> $lasts[$b->getKey()]->getLastStart();
|
|
}
|
|
);
|
|
|
|
return [$orderedJobs, $lasts];
|
|
}
|
|
|
|
private function runForce(string $forceJob): void
|
|
{
|
|
foreach ($this->jobs as $job) {
|
|
if ($job->getKey() === $forceJob) {
|
|
$job->run([]);
|
|
}
|
|
}
|
|
}
|
|
}
|