setHelp('If no parameter is given, the scheduler executes any tasks that are overdue to run. Call it like this: typo3/sysext/core/bin/typo3 scheduler:run --task=13 -f') ->addOption( 'task', 'i', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'UID of a specific task. Can be provided multiple times to execute multiple tasks sequentially.' ) ->addOption( 'force', 'f', InputOption::VALUE_NONE, 'Force execution of the task which is passed with --task option' ) ->addOption( 'stop', 's', InputOption::VALUE_NONE, 'Stop the task which is passed with --task option' ); } /** * Execute scheduler tasks */ protected function execute(InputInterface $input, OutputInterface $output): int { $this->io = new SymfonyStyle($input, $output); // Make sure the _cli_ user is loaded Bootstrap::initializeBackendAuthentication(); $overwrittenTaskList = $input->getOption('task'); $overwrittenTaskList = is_array($overwrittenTaskList) ? $overwrittenTaskList : []; $overwrittenTaskList = array_filter($overwrittenTaskList, static fn($value) => MathUtility::canBeInterpretedAsInteger($value)); $overwrittenTaskList = array_map('intval', $overwrittenTaskList); if ($overwrittenTaskList !== []) { $this->overwrittenTaskList = $overwrittenTaskList; } $this->forceExecution = (bool)$input->getOption('force'); $this->stopTasks = $this->shouldStopTasks((bool)$input->getOption('stop')); return $this->loopTasks() ? Command::SUCCESS : Command::FAILURE; } /** * Checks if the tasks should be stopped instead of being executed. * * Stopping is only performed when the --stop option is provided together with the --task option. * * @param bool $stopOption */ protected function shouldStopTasks(bool $stopOption): bool { if (!$stopOption) { return false; } if ($this->overwrittenTaskList !== []) { return true; } if ($this->io->isVerbose()) { $this->io->warning('Stopping tasks is only possible when the --task option is provided.'); } return false; } /** * Stop task */ protected function stopTask(AbstractTask $task) { $this->taskRepository->removeAllRegisteredExecutionsForTask($task); if ($this->io->isVeryVerbose()) { $this->io->writeln(sprintf('Task #%d was stopped', $task->getTaskUid())); } } /** * Return task a task for a given UID */ protected function getTask(int $taskUid): ?AbstractTask { $force = $this->stopTasks || $this->forceExecution; if ($force) { return $this->taskRepository->findByUid($taskUid); } return $this->taskRepository->findNextExecutableTaskForUid($taskUid); } /** * Execute tasks in loop that are ready to execute */ protected function loopTasks(): bool { $hasError = false; do { $task = null; // Try getting the next task and execute it // If there are no more tasks to execute, an exception is thrown by \TYPO3\CMS\Scheduler\Scheduler::fetchTask() try { $task = $this->fetchNextTask(); if ($task === null) { break; } try { $this->executeOrStopTask($task); } catch (\Exception $e) { $taskDetails = $this->taskService->getTaskDetailsFromTask($task); $messages = [ $e->getMessage() . PHP_EOL, 'Exception in scheduler task #' . $task->getTaskUid() . ' (' . $task->getTaskType() . ' - ' . $taskDetails['title'] . ')', ]; $messages[] = 'File: ' . $e->getFile() . ':' . $e->getLine(); $this->io->getErrorStyle()->error($messages); $hasError = true; // We ignore any exception that may have been thrown during execution, // as this is a background process. // The exception message has been recorded to the database anyway continue; } } catch (\UnexpectedValueException $e) { $this->io->getErrorStyle()->error($e->getMessage()); $hasError = true; continue; } } while ($task !== null); // Record the run in the system registry $this->scheduler->recordLastRun(); return !$hasError; } /** * When the --task option is provided, the next task is fetched from the provided task UIDs. Depending * on the --force option the task is fetched even if it is not marked for execution. * * Without the --task option we ask the scheduler for the next task with pending execution. * * @throws \UnexpectedValueException When no task is found by the provided UID or the task is not marked for execution. */ protected function fetchNextTask(): ?AbstractTask { if ($this->overwrittenTaskList === null) { return $this->taskRepository->findNextExecutableTask(); } if (count($this->overwrittenTaskList) === 0) { return null; } $taskUid = (int)array_shift($this->overwrittenTaskList); $task = $this->getTask($taskUid); if (!(new TaskValidator())->isValid($task)) { throw new \UnexpectedValueException( sprintf('The task #%d is not scheduled for execution or does not exist.', $taskUid), 1547675557 ); } return $task; } /** * When in stop mode the given task is stopped. Otherwise the task is executed. */ protected function executeOrStopTask(AbstractTask $task): void { if ($this->stopTasks) { $this->stopTask($task); return; } $this->scheduler->executeTask($task); if ($this->io->isVeryVerbose()) { $this->io->writeln(sprintf('Task #%d was executed', $task->getTaskUid())); } } }