[FEATURE] Introduce scheduler task to execute console commands
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Console / CommandRegistry.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Core\Console;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use Symfony\Component\Console\Command\Command;
19 use TYPO3\CMS\Core\Package\PackageManager;
20 use TYPO3\CMS\Core\SingletonInterface;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22
23 /**
24 * Registry for Symfony commands, populated from extensions
25 */
26 class CommandRegistry implements \IteratorAggregate, SingletonInterface
27 {
28 /**
29 * @var PackageManager
30 */
31 protected $packageManager;
32
33 /**
34 * Map of commands
35 *
36 * @var Command[]
37 */
38 protected $commands = [];
39
40 /**
41 * @param PackageManager $packageManager
42 */
43 public function __construct(PackageManager $packageManager = null)
44 {
45 $this->packageManager = $packageManager ?: GeneralUtility::makeInstance(PackageManager::class);
46 }
47
48 /**
49 * @return \Generator
50 */
51 public function getIterator(): \Generator
52 {
53 $this->populateCommandsFromPackages();
54 foreach ($this->commands as $commandName => $command) {
55 yield $commandName => $command;
56 }
57 }
58
59 /**
60 * @param string $identifier
61 * @throws CommandNameAlreadyInUseException
62 * @throws UnknownCommandException
63 * @return Command
64 */
65 public function getCommandByIdentifier(string $identifier): Command
66 {
67 $this->populateCommandsFromPackages();
68
69 if (!isset($this->commands[$identifier])) {
70 throw new UnknownCommandException(
71 sprintf('Command "%s" has not been registered.', $identifier),
72 1510906768
73 );
74 }
75
76 return $this->commands[$identifier] ?? null;
77 }
78
79 /**
80 * Find all Configuration/Commands.php files of extensions and create a registry from it.
81 * The file should return an array with a command key as key and the command description
82 * as value. The command description must be an array and have a class key that defines
83 * the class name of the command. Example:
84 *
85 * <?php
86 * return [
87 * 'backend:lock' => [
88 * 'class' => \TYPO3\CMS\Backend\Command\LockBackendCommand::class
89 * ],
90 * ];
91 *
92 * @throws CommandNameAlreadyInUseException
93 */
94 protected function populateCommandsFromPackages()
95 {
96 if ($this->commands) {
97 return;
98 }
99 foreach ($this->packageManager->getActivePackages() as $package) {
100 $commandsOfExtension = $package->getPackagePath() . 'Configuration/Commands.php';
101 if (@is_file($commandsOfExtension)) {
102 /*
103 * We use require instead of require_once here because it eases the testability as require_once returns
104 * a boolean from the second execution on. As this class is a singleton, this require is only called
105 * once per request anyway.
106 */
107 $commands = require $commandsOfExtension;
108 if (is_array($commands)) {
109 foreach ($commands as $commandName => $commandConfig) {
110 if (array_key_exists($commandName, $this->commands)) {
111 throw new CommandNameAlreadyInUseException(
112 'Command "' . $commandName . '" registered by "' . $package->getPackageKey() . '" is already in use',
113 1484486383
114 );
115 }
116 $this->commands[$commandName] = GeneralUtility::makeInstance($commandConfig['class'], $commandName);
117 }
118 }
119 }
120 }
121 }
122 }