[TASK] Use null coalescing operator where possible
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Mvc / Cli / RequestBuilder.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Mvc\Cli;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 /**
18 * Builds a CLI request object from the raw command call
19 *
20 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
21 */
22 class RequestBuilder implements \TYPO3\CMS\Core\SingletonInterface
23 {
24 /**
25 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
26 */
27 protected $objectManager;
28
29 /**
30 * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
31 */
32 protected $reflectionService;
33
34 /**
35 * @var \TYPO3\CMS\Extbase\Mvc\Cli\CommandManager
36 */
37 protected $commandManager;
38
39 /**
40 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
41 */
42 protected $configurationManager;
43
44 /**
45 * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
46 */
47 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
48 {
49 $this->objectManager = $objectManager;
50 }
51
52 /**
53 * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
54 */
55 public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
56 {
57 $this->reflectionService = $reflectionService;
58 }
59
60 /**
61 * @param \TYPO3\CMS\Extbase\Mvc\Cli\CommandManager $commandManager
62 */
63 public function injectCommandManager(\TYPO3\CMS\Extbase\Mvc\Cli\CommandManager $commandManager)
64 {
65 $this->commandManager = $commandManager;
66 }
67
68 /**
69 * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
70 */
71 public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
72 {
73 $this->configurationManager = $configurationManager;
74 }
75
76 /**
77 * Builds a CLI request object from a command line.
78 *
79 * The given command line may be a string (e.g. "myextension:foo do-that-thing --force") or
80 * an array consisting of the individual parts. The array must not include the script
81 * name (like in $argv) but start with command right away.
82 *
83 * @param mixed $commandLine The command line, either as a string or as an array
84 * @param string $callingScript The calling script (usually ./typo3/sysext/core/bin/typo3)
85 * @return \TYPO3\CMS\Extbase\Mvc\Cli\Request The CLI request as an object
86 */
87 public function build($commandLine = '', $callingScript = './typo3/sysext/core/bin/typo3')
88 {
89 $request = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Cli\Request::class);
90 $request->setCallingScript($callingScript);
91 $request->setControllerObjectName(\TYPO3\CMS\Extbase\Command\HelpCommandController::class);
92 $rawCommandLineArguments = is_array($commandLine) ? $commandLine : explode(' ', $commandLine);
93 if (empty($rawCommandLineArguments)) {
94 $request->setControllerCommandName('helpStub');
95 return $request;
96 }
97 $commandIdentifier = trim(array_shift($rawCommandLineArguments));
98 try {
99 $command = $this->commandManager->getCommandByIdentifier($commandIdentifier);
100 $this->configurationManager->setConfiguration(['extensionName' => $command->getExtensionName()]);
101 } catch (\TYPO3\CMS\Extbase\Mvc\Exception\CommandException $exception) {
102 $request->setArgument('exception', $exception);
103 $request->setControllerCommandName('error');
104 return $request;
105 }
106 $controllerObjectName = $command->getControllerClassName();
107 $controllerCommandName = $command->getControllerCommandName();
108 $request->setControllerObjectName($controllerObjectName);
109 $request->setControllerCommandName($controllerCommandName);
110 list($commandLineArguments, $exceedingCommandLineArguments) = $this->parseRawCommandLineArguments($rawCommandLineArguments, $controllerObjectName, $controllerCommandName);
111 $request->setArguments($commandLineArguments);
112 $request->setExceedingArguments($exceedingCommandLineArguments);
113 return $request;
114 }
115
116 /**
117 * Takes an array of unparsed command line arguments and options and converts it separated
118 * by named arguments, options and unnamed arguments.
119 *
120 * @param array $rawCommandLineArguments The unparsed command parts (such as "--foo") as an array
121 * @param string $controllerObjectName Object name of the designated command controller
122 * @param string $controllerCommandName Command name of the recognized command (ie. method name without "Command" suffix)
123 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentMixingException
124 * @return array All and exceeding command line arguments
125 */
126 protected function parseRawCommandLineArguments(array $rawCommandLineArguments, $controllerObjectName, $controllerCommandName)
127 {
128 $commandLineArguments = [];
129 $exceedingArguments = [];
130 $commandMethodName = $controllerCommandName . 'Command';
131 $commandMethodParameters = $this->reflectionService->getMethodParameters($controllerObjectName, $commandMethodName);
132 $requiredArguments = [];
133 $optionalArguments = [];
134 $argumentNames = [];
135 foreach ($commandMethodParameters as $parameterName => $parameterInfo) {
136 $argumentNames[] = $parameterName;
137 if ($parameterInfo['optional'] === false) {
138 $requiredArguments[strtolower($parameterName)] = ['parameterName' => $parameterName, 'type' => $parameterInfo['type']];
139 } else {
140 $optionalArguments[strtolower($parameterName)] = ['parameterName' => $parameterName, 'type' => $parameterInfo['type']];
141 }
142 }
143 $decidedToUseNamedArguments = false;
144 $decidedToUseUnnamedArguments = false;
145 $argumentIndex = 0;
146 while (!empty($rawCommandLineArguments)) {
147 $rawArgument = array_shift($rawCommandLineArguments);
148 if ($rawArgument[0] === '-') {
149 if ($rawArgument[1] === '-') {
150 $rawArgument = substr($rawArgument, 2);
151 } else {
152 $rawArgument = substr($rawArgument, 1);
153 }
154 $argumentName = $this->extractArgumentNameFromCommandLinePart($rawArgument);
155 if (isset($optionalArguments[$argumentName])) {
156 $argumentValue = $this->getValueOfCurrentCommandLineOption($rawArgument, $rawCommandLineArguments, $optionalArguments[$argumentName]['type']);
157 $commandLineArguments[$optionalArguments[$argumentName]['parameterName']] = $argumentValue;
158 } elseif (isset($requiredArguments[$argumentName])) {
159 if ($decidedToUseUnnamedArguments) {
160 throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentMixingException(sprintf('Unexpected named argument "%s". If you use unnamed arguments, all required arguments must be passed without a name.', $argumentName), 1309971821);
161 }
162 $decidedToUseNamedArguments = true;
163 $argumentValue = $this->getValueOfCurrentCommandLineOption($rawArgument, $rawCommandLineArguments, $requiredArguments[$argumentName]['type']);
164 $commandLineArguments[$requiredArguments[$argumentName]['parameterName']] = $argumentValue;
165 unset($requiredArguments[$argumentName]);
166 }
167 } else {
168 if (!empty($requiredArguments)) {
169 if ($decidedToUseNamedArguments) {
170 throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentMixingException(sprintf('Unexpected unnamed argument "%s". If you use named arguments, all required arguments must be passed named.', $rawArgument), 1309971820);
171 }
172 $argument = array_shift($requiredArguments);
173 $commandLineArguments[$argument['parameterName']] = $rawArgument;
174 $decidedToUseUnnamedArguments = true;
175 } else {
176 if ($argumentIndex < count($argumentNames)) {
177 $commandLineArguments[$argumentNames[$argumentIndex]] = $rawArgument;
178 } else {
179 $exceedingArguments[] = $rawArgument;
180 }
181 }
182 }
183 $argumentIndex++;
184 }
185 return [$commandLineArguments, $exceedingArguments];
186 }
187
188 /**
189 * Extracts the option or argument name from the name / value pair of a command line.
190 *
191 * @param string $commandLinePart Part of the command line, e.g. "my-important-option=SomeInterestingValue
192 * @return string The lowercased argument name, e.g. "myimportantoption
193 */
194 protected function extractArgumentNameFromCommandLinePart($commandLinePart)
195 {
196 $nameAndValue = explode('=', $commandLinePart, 2);
197 return strtolower(str_replace('-', '', $nameAndValue[0]));
198 }
199
200 /**
201 * Returns the value of the first argument of the given input array. Shifts the parsed argument off the array.
202 *
203 * @param string $currentArgument The current argument
204 * @param array &$rawCommandLineArguments Array of the remaining command line arguments
205 * @param string $expectedArgumentType The expected type of the current argument, because booleans get special attention
206 * @return string The value of the first argument
207 */
208 protected function getValueOfCurrentCommandLineOption($currentArgument, array &$rawCommandLineArguments, $expectedArgumentType)
209 {
210 if (!isset($rawCommandLineArguments[0]) && strpos($currentArgument, '=') === false || isset($rawCommandLineArguments[0]) && $rawCommandLineArguments[0][0] === '-' && strpos($currentArgument, '=') === false) {
211 return true;
212 }
213 if (strpos($currentArgument, '=') === false) {
214 $possibleValue = trim(array_shift($rawCommandLineArguments));
215 if (strpos($possibleValue, '=') === false) {
216 if ($expectedArgumentType !== 'boolean') {
217 return $possibleValue;
218 }
219 if (array_search($possibleValue, ['on', '1', 'y', 'yes', 'true', 'TRUE']) !== false) {
220 return true;
221 }
222 if (array_search($possibleValue, ['off', '0', 'n', 'no', 'false', 'FALSE']) !== false) {
223 return false;
224 }
225 array_unshift($rawCommandLineArguments, $possibleValue);
226 return true;
227 }
228 $currentArgument .= $possibleValue;
229 }
230 $splitArgument = explode('=', $currentArgument, 2);
231 while ((!isset($splitArgument[1]) || trim($splitArgument[1]) === '') && !empty($rawCommandLineArguments)) {
232 $currentArgument .= array_shift($rawCommandLineArguments);
233 $splitArgument = explode('=', $currentArgument);
234 }
235 $value = $splitArgument[1] ?? '';
236 return $value;
237 }
238 }