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. }