vendor/contao/core-bundle/src/Cron/Cron.php line 136

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4.  * This file is part of Contao.
  5.  *
  6.  * (c) Leo Feyer
  7.  *
  8.  * @license LGPL-3.0-or-later
  9.  */
  10. namespace Contao\CoreBundle\Cron;
  11. use Contao\CoreBundle\Entity\CronJob as CronJobEntity;
  12. use Contao\CoreBundle\Exception\CronExecutionSkippedException;
  13. use Contao\CoreBundle\Repository\CronJobRepository;
  14. use Cron\CronExpression;
  15. use Doctrine\DBAL\Exception\LockWaitTimeoutException;
  16. use Doctrine\ORM\EntityManagerInterface;
  17. use Psr\Log\LoggerInterface;
  18. class Cron
  19. {
  20.     public const SCOPE_WEB 'web';
  21.     public const SCOPE_CLI 'cli';
  22.     /**
  23.      * @var \Closure():CronJobRepository
  24.      */
  25.     private \Closure $repository;
  26.     /**
  27.      * @var \Closure():EntityManagerInterface
  28.      */
  29.     private \Closure $entityManager;
  30.     private ?LoggerInterface $logger;
  31.     /**
  32.      * @var array<CronJob>
  33.      */
  34.     private array $cronJobs = [];
  35.     /**
  36.      * @param \Closure():CronJobRepository      $repository
  37.      * @param \Closure():EntityManagerInterface $entityManager
  38.      */
  39.     public function __construct(\Closure $repository\Closure $entityManager, ?LoggerInterface $logger null)
  40.     {
  41.         $this->repository $repository;
  42.         $this->entityManager $entityManager;
  43.         $this->logger $logger;
  44.     }
  45.     public function addCronJob(CronJob $cronjob): void
  46.     {
  47.         $this->cronJobs[] = $cronjob;
  48.     }
  49.     /**
  50.      * Run all the registered Contao cron jobs.
  51.      */
  52.     public function run(string $scope): void
  53.     {
  54.         // Validate scope
  55.         if (self::SCOPE_WEB !== $scope && self::SCOPE_CLI !== $scope) {
  56.             throw new \InvalidArgumentException('Invalid scope "'.$scope.'"');
  57.         }
  58.         /** @var CronJobRepository $repository */
  59.         $repository = ($this->repository)();
  60.         /** @var EntityManagerInterface $entityManager */
  61.         $entityManager = ($this->entityManager)();
  62.         /** @var array<CronJob> $cronJobsToBeRun */
  63.         $cronJobsToBeRun = [];
  64.         $now = new \DateTimeImmutable();
  65.         // Return if another cron process is already running
  66.         try {
  67.             $repository->lockTable();
  68.         } catch (LockWaitTimeoutException $e) {
  69.             return;
  70.         }
  71.         try {
  72.             // Go through each cron job
  73.             foreach ($this->cronJobs as $cron) {
  74.                 $interval $cron->getInterval();
  75.                 $name $cron->getName();
  76.                 // Determine the last run date
  77.                 $lastRunDate null;
  78.                 $lastRunEntity $repository->findOneByName($name);
  79.                 if (null !== $lastRunEntity) {
  80.                     $lastRunDate $lastRunEntity->getLastRun();
  81.                 } else {
  82.                     $lastRunEntity = new CronJobEntity($name);
  83.                     $entityManager->persist($lastRunEntity);
  84.                 }
  85.                 // Check if the cron should be run
  86.                 $expression CronExpression::factory($interval);
  87.                 if (null !== $lastRunDate && $now $expression->getNextRunDate($lastRunDate)) {
  88.                     continue;
  89.                 }
  90.                 // Store the previous run in case the cronjob skips itself
  91.                 $cron->setPreviousRun($lastRunEntity->getLastRun());
  92.                 // Update the cron entry
  93.                 $lastRunEntity->setLastRun($now);
  94.                 // Add job to the cron jobs to be run
  95.                 $cronJobsToBeRun[] = $cron;
  96.             }
  97.             $entityManager->flush();
  98.         } finally {
  99.             $repository->unlockTable();
  100.         }
  101.         $exception null;
  102.         // Execute all cron jobs to be run
  103.         foreach ($cronJobsToBeRun as $cron) {
  104.             try {
  105.                 if (null !== $this->logger) {
  106.                     $this->logger->debug(sprintf('Executing cron job "%s"'$cron->getName()));
  107.                 }
  108.                 $cron($scope);
  109.             } catch (CronExecutionSkippedException $e) {
  110.                 // Restore previous run date in case cronjob skips itself
  111.                 $lastRunEntity $repository->findOneByName($cron->getName());
  112.                 $lastRunEntity->setLastRun($cron->getPreviousRun());
  113.                 $entityManager->flush();
  114.             } catch (\Throwable $e) {
  115.                 // Catch any exceptions so that other cronjobs are still executed
  116.                 if (null !== $this->logger) {
  117.                     $this->logger->error((string) $e);
  118.                 }
  119.                 if (null === $exception) {
  120.                     $exception $e;
  121.                 }
  122.             }
  123.         }
  124.         // Throw the first exception
  125.         if (null !== $exception) {
  126.             throw $exception;
  127.         }
  128.     }
  129. }