1 | <?php |
||||
2 | |||||
3 | /** |
||||
4 | * TShellApplication class file |
||||
5 | * |
||||
6 | * @author Qiang Xue <[email protected]> |
||||
7 | * @link https://github.com/pradosoft/prado |
||||
8 | * @license https://github.com/pradosoft/prado/blob/master/LICENSE |
||||
9 | */ |
||||
10 | |||||
11 | namespace Prado\Shell; |
||||
12 | |||||
13 | use Prado\Prado; |
||||
14 | use Prado\Shell\Actions\TActiveRecordAction; |
||||
15 | use Prado\Shell\Actions\THelpAction; |
||||
16 | use Prado\Shell\Actions\TFlushCachesAction; |
||||
17 | use Prado\Shell\Actions\TPhpShellAction; |
||||
18 | use Prado\Shell\Actions\TWebServerAction; |
||||
19 | use Prado\IO\ITextWriter; |
||||
20 | use Prado\IO\TStdOutWriter; |
||||
21 | use Prado\Shell\TShellWriter; |
||||
22 | use Prado\TPropertyValue; |
||||
23 | |||||
24 | /** |
||||
25 | * TShellApplication class. |
||||
26 | * |
||||
27 | * TShellApplication is the base class for developing command-line PRADO |
||||
28 | * tools that share the same configurations as their Web application counterparts. |
||||
29 | * |
||||
30 | * A typical usage of TShellApplication in a command-line PHP script is as follows: |
||||
31 | * ```php |
||||
32 | * require 'path/to/vendor/autoload.php'; |
||||
33 | * $application=new TShellApplication('path/to/application.xml'); |
||||
34 | * $application->run($_SERVER); |
||||
35 | * // perform command-line tasks here |
||||
36 | * ``` |
||||
37 | * |
||||
38 | * Since the application instance has access to all configurations, including |
||||
39 | * path aliases, modules and parameters, the command-line script has nearly the same |
||||
40 | * accessibility to resources as the PRADO Web applications. |
||||
41 | * |
||||
42 | * @author Qiang Xue <[email protected]> |
||||
43 | * @author Brad Anderson <[email protected]> shell refactor |
||||
44 | * @since 3.1.0 |
||||
45 | */ |
||||
46 | class TShellApplication extends \Prado\TApplication |
||||
47 | { |
||||
48 | /** @var bool tells the application to be in quiet mode, levels [0..1], default 0, */ |
||||
49 | private $_quietMode = 0; |
||||
50 | |||||
51 | /** |
||||
52 | * @var array<\Prado\Shell\TShellAction> cli shell Application commands. Modules can add their own command |
||||
53 | */ |
||||
54 | private $_actions = []; |
||||
55 | |||||
56 | /** |
||||
57 | * @var TShellWriter output writer. |
||||
58 | */ |
||||
59 | protected $_outWriter; |
||||
60 | |||||
61 | /** |
||||
62 | * @var array<string, callable> application command options and property set callable |
||||
63 | */ |
||||
64 | protected $_options = []; |
||||
65 | |||||
66 | /** |
||||
67 | * @var array<string, string> application command optionAliases of the short letter(s) and option name |
||||
68 | */ |
||||
69 | protected $_optionAliases = []; |
||||
70 | |||||
71 | /** |
||||
72 | * @var array<array> The option help text and help values |
||||
73 | */ |
||||
74 | protected $_optionsData = []; |
||||
75 | |||||
76 | /** |
||||
77 | * @var bool is the application help printed |
||||
78 | */ |
||||
79 | protected $_helpPrinted = false; |
||||
80 | |||||
81 | /** |
||||
82 | * @var string[] arguments to the application |
||||
83 | */ |
||||
84 | private $_arguments; |
||||
85 | |||||
86 | /** |
||||
87 | * Runs the application. |
||||
88 | * This method overrides the parent implementation by initializing |
||||
89 | * application with configurations specified when it is created. |
||||
90 | * @param null|array<string> $args |
||||
91 | */ |
||||
92 | public function run($args = null) |
||||
93 | { |
||||
94 | array_shift($args); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
95 | $this->_arguments = $args; |
||||
96 | $this->detectShellLanguageCharset(); |
||||
97 | |||||
98 | $this->_outWriter = new TShellWriter(new TStdOutWriter()); |
||||
99 | |||||
100 | $this->registerOption('quiet', [$this, 'setQuietMode'], 'Quiets the output to <level> [1..3], default 1 (when specified)', '=<level>'); |
||||
101 | $this->registerOptionAlias('q', 'quiet'); |
||||
102 | |||||
103 | $this->attachEventHandler('onInitComplete', [$this, 'processArguments'], 20); |
||||
0 ignored issues
–
show
20 of type integer is incompatible with the type Prado\numeric|null expected by parameter $priority of Prado\TComponent::attachEventHandler() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
104 | |||||
105 | parent::run(); |
||||
106 | } |
||||
107 | |||||
108 | /** |
||||
109 | * This takes the shell LANG and sets the HTTP_ACCEPT_LANGUAGE/HTTP_ACCEPT_CHARSET |
||||
110 | * for the application to do I18N. |
||||
111 | * @since 4.2.0 |
||||
112 | */ |
||||
113 | private function detectShellLanguageCharset() |
||||
114 | { |
||||
115 | if (isset($_SERVER['LANG'])) { |
||||
116 | $lang = $_SERVER['LANG']; |
||||
117 | $pos = strpos($lang, '.'); |
||||
118 | if ($pos !== false) { |
||||
119 | $_SERVER['HTTP_ACCEPT_CHARSET'] = substr($lang, $pos + 1); |
||||
120 | $lang = substr($lang, 0, $pos); |
||||
121 | } |
||||
122 | $_SERVER['HTTP_ACCEPT_LANGUAGE'] = $lang; |
||||
123 | } |
||||
124 | } |
||||
125 | |||||
126 | /** |
||||
127 | * This checks if shell environment is from a system CronTab. |
||||
128 | * @return bool is the shell environment in crontab |
||||
129 | * @since 4.2.2 |
||||
130 | */ |
||||
131 | public static function detectCronTabShell() |
||||
132 | { |
||||
133 | return php_sapi_name() == 'cli' && (!($term = getenv('TERM')) || $term == 'unknown'); |
||||
134 | } |
||||
135 | |||||
136 | /** |
||||
137 | * This processes the arguments entered into the cli. This is processed after |
||||
138 | * the application is initialized and modules can |
||||
139 | * @param object $sender |
||||
140 | * @param mixed $param |
||||
141 | * @since 4.2.0 |
||||
142 | */ |
||||
143 | public function processArguments($sender, $param) |
||||
144 | { |
||||
145 | $this->installShellActions(); |
||||
146 | |||||
147 | $options = $this->_options; |
||||
148 | $aliases = $this->_optionAliases; |
||||
149 | $skip = false; |
||||
0 ignored issues
–
show
|
|||||
150 | foreach ($this->_arguments as $i => $arg) { |
||||
151 | $arg = explode('=', $arg, 2); |
||||
152 | $processed = false; |
||||
153 | foreach ($options as $option => $setMethod) { |
||||
154 | $option = '--' . $option; |
||||
155 | if ($arg[0] === $option) { |
||||
156 | call_user_func($setMethod, $arg[1] ?? ''); |
||||
157 | unset($this->_arguments[$i]); |
||||
158 | break; |
||||
159 | } |
||||
160 | } |
||||
161 | if (!$processed) { |
||||
162 | foreach ($aliases as $alias => $_option) { |
||||
163 | $alias = '-' . $alias; |
||||
164 | if (isset($options[$_option]) && $arg[0] === $alias) { |
||||
165 | call_user_func($options[$_option], $arg[1] ?? ''); |
||||
166 | unset($this->_arguments[$i]); |
||||
167 | break; |
||||
168 | } |
||||
169 | } |
||||
170 | } |
||||
171 | } |
||||
172 | $this->_arguments = array_values($this->_arguments); |
||||
173 | } |
||||
174 | |||||
175 | /** |
||||
176 | * Installs the shell actions. |
||||
177 | * @since 4.3.0 |
||||
178 | */ |
||||
179 | public function installShellActions() |
||||
180 | { |
||||
181 | $this->addShellActionClass(TFlushCachesAction::class); |
||||
182 | $this->addShellActionClass(THelpAction::class); |
||||
183 | $this->addShellActionClass(TPhpShellAction::class); |
||||
184 | $this->addShellActionClass(TActiveRecordAction::class); |
||||
185 | |||||
186 | $app = Prado::getApplication(); |
||||
187 | if ($app->getMode() === \Prado\TApplicationMode::Debug || TPropertyValue::ensureBoolean($app->getParameters()[TWebServerAction::DEV_WEBSERVER_PARAM])) { |
||||
188 | $this->addShellActionClass(TWebServerAction::class); |
||||
189 | } |
||||
190 | } |
||||
191 | |||||
192 | /** |
||||
193 | * Runs the requested service. |
||||
194 | * @since 4.2.0 |
||||
195 | */ |
||||
196 | public function runService() |
||||
197 | { |
||||
198 | $args = $this->_arguments; |
||||
199 | |||||
200 | $outWriter = $this->_outWriter; |
||||
201 | $valid = false; |
||||
202 | |||||
203 | $this->printGreeting($outWriter); |
||||
204 | foreach ($this->_actions as $class => $action) { |
||||
205 | if (($method = $action->isValidAction($args)) !== null) { |
||||
206 | $action->setWriter($outWriter); |
||||
207 | $this->processActionArguments($args, $action, $method); |
||||
208 | $m = 'action' . str_replace('-', '', $method); |
||||
209 | if (method_exists($action, $m)) { |
||||
210 | $valid |= call_user_func([$action, $m], $args); |
||||
211 | } else { |
||||
212 | $outWriter->writeError("$method is not an available command"); |
||||
213 | $valid = true; |
||||
214 | } |
||||
215 | break; |
||||
216 | } |
||||
217 | } |
||||
218 | if (!$valid && $this->_quietMode === 0) { |
||||
0 ignored issues
–
show
|
|||||
219 | $this->printHelp($outWriter); |
||||
220 | } |
||||
221 | } |
||||
222 | |||||
223 | /** |
||||
224 | * This processes the arguments entered into the cli |
||||
225 | * @param array $args |
||||
226 | * @param TShellAction $action |
||||
227 | * @param string $method |
||||
228 | * @since 4.2.0 |
||||
229 | */ |
||||
230 | public function processActionArguments(&$args, $action, $method) |
||||
231 | { |
||||
232 | $options = $action->options($method); |
||||
233 | $aliases = $action->optionAliases(); |
||||
234 | $skip = false; |
||||
0 ignored issues
–
show
|
|||||
235 | if (!$options) { |
||||
0 ignored issues
–
show
The expression
$options of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||||
236 | return; |
||||
237 | } |
||||
238 | $keys = array_flip($options); |
||||
239 | foreach ($args as $i => $arg) { |
||||
240 | $arg = explode('=', $arg); |
||||
241 | $processed = false; |
||||
242 | foreach ($options as $_option) { |
||||
243 | $option = '--' . $_option; |
||||
244 | if ($arg[0] === $option) { |
||||
245 | $action->$_option = $arg[1] ?? ''; |
||||
246 | $processed = true; |
||||
247 | unset($args[$i]); |
||||
248 | break; |
||||
249 | } |
||||
250 | } |
||||
251 | if (!$processed) { |
||||
252 | foreach ($aliases as $alias => $_option) { |
||||
253 | $alias = '-' . $alias; |
||||
254 | if (isset($keys[$_option]) && $arg[0] === $alias) { |
||||
255 | $action->$_option = $arg[1] ?? ''; |
||||
256 | unset($args[$i]); |
||||
257 | break; |
||||
258 | } |
||||
259 | } |
||||
260 | } |
||||
261 | } |
||||
262 | $args = array_values($args); |
||||
263 | } |
||||
264 | |||||
265 | |||||
266 | /** |
||||
267 | * Flushes output to shell. |
||||
268 | * @param bool $continueBuffering whether to continue buffering after flush if buffering was active |
||||
269 | * @since 4.2.0 |
||||
270 | */ |
||||
271 | public function flushOutput($continueBuffering = true) |
||||
272 | { |
||||
273 | $this->_outWriter->flush(); |
||||
274 | if (!$continueBuffering) { |
||||
275 | $this->_outWriter = null; |
||||
276 | } |
||||
277 | } |
||||
278 | |||||
279 | /** |
||||
280 | * @param string $class action class name |
||||
281 | * @since 4.2.0 |
||||
282 | */ |
||||
283 | public function addShellActionClass($class) |
||||
284 | { |
||||
285 | $this->_actions[is_array($class) ? $class['class'] : $class] = Prado::createComponent($class); |
||||
0 ignored issues
–
show
|
|||||
286 | } |
||||
287 | |||||
288 | /** |
||||
289 | * @return \Prado\Shell\TShellAction[] the shell actions for the application |
||||
290 | * @since 4.2.0 |
||||
291 | */ |
||||
292 | public function getShellActions() |
||||
293 | { |
||||
294 | return $this->_actions; |
||||
295 | } |
||||
296 | |||||
297 | |||||
298 | /** |
||||
299 | * This registers shell command line options and the setter callback |
||||
300 | * @param string $name name of the option at the command line |
||||
301 | * @param callable $setCallback the callback to set the property |
||||
302 | * @param string $description Short description of the option |
||||
303 | * @param string $values value after the option, eg "=<level>" |
||||
304 | * @since 4.2.0 |
||||
305 | */ |
||||
306 | public function registerOption($name, $setCallback, $description = '', $values = '') |
||||
307 | { |
||||
308 | $this->_options[$name] = $setCallback; |
||||
309 | $this->_optionsData[$name] = [TPropertyValue::ensureString($description), TPropertyValue::ensureString($values)]; |
||||
310 | } |
||||
311 | |||||
312 | |||||
313 | /** |
||||
314 | * This registers shell command line option aliases and linked variable |
||||
315 | * @param string $alias the short command |
||||
316 | * @param string $name the command name |
||||
317 | * @since 4.2.0 |
||||
318 | */ |
||||
319 | public function registerOptionAlias($alias, $name) |
||||
320 | { |
||||
321 | $this->_optionAliases[$alias] = $name; |
||||
322 | } |
||||
323 | |||||
324 | /** |
||||
325 | * @return \Prado\Shell\TShellWriter the writer for the class |
||||
326 | * @since 4.2.0 |
||||
327 | */ |
||||
328 | public function getWriter(): TShellWriter |
||||
329 | { |
||||
330 | return $this->_outWriter; |
||||
331 | } |
||||
332 | |||||
333 | /** |
||||
334 | * @param \Prado\Shell\TShellWriter $writer the writer for the class |
||||
335 | * @since 4.2.0 |
||||
336 | */ |
||||
337 | public function setWriter(TShellWriter $writer) |
||||
338 | { |
||||
339 | $this->_outWriter = $writer; |
||||
340 | } |
||||
341 | |||||
342 | /** |
||||
343 | * @return int the writer for the class, default 0 |
||||
344 | * @since 4.2.0 |
||||
345 | */ |
||||
346 | public function getQuietMode(): int |
||||
347 | { |
||||
348 | return $this->_quietMode; |
||||
0 ignored issues
–
show
|
|||||
349 | } |
||||
350 | |||||
351 | /** |
||||
352 | * @param int $quietMode the writer for the class, [0..3] |
||||
353 | * @since 4.2.0 |
||||
354 | */ |
||||
355 | public function setQuietMode($quietMode) |
||||
356 | { |
||||
357 | $this->_quietMode = ($quietMode === '' ? 1 : min(max((int) $quietMode, 0), 3)); |
||||
0 ignored issues
–
show
|
|||||
358 | } |
||||
359 | |||||
360 | |||||
361 | /** |
||||
362 | * @param mixed $outWriter |
||||
363 | * @since 4.2.0 |
||||
364 | */ |
||||
365 | public function printGreeting($outWriter) |
||||
366 | { |
||||
367 | if (!$this->_helpPrinted && $this->_quietMode === 0) { |
||||
0 ignored issues
–
show
|
|||||
368 | $outWriter->write(" Command line tools for Prado " . Prado::getVersion() . ".", TShellWriter::DARK_GRAY); |
||||
369 | $outWriter->writeLine(); |
||||
370 | $outWriter->flush(); |
||||
371 | $this->_helpPrinted = true; |
||||
372 | } |
||||
373 | } |
||||
374 | |||||
375 | |||||
376 | /** |
||||
377 | * Print command line help, default action. |
||||
378 | * @param mixed $outWriter |
||||
379 | * @since 4.2.0 |
||||
380 | */ |
||||
381 | public function printHelp($outWriter) |
||||
382 | { |
||||
383 | $this->printGreeting($outWriter); |
||||
384 | |||||
385 | $outWriter->write("usage: "); |
||||
386 | $outWriter->writeLine("php prado-cli.php command[/action] <parameter> [optional]", [TShellWriter::BLUE, TShellWriter::BOLD]); |
||||
387 | $outWriter->writeLine(); |
||||
388 | $outWriter->writeLine("example: prado-cli http"); |
||||
389 | $outWriter->writeLine("example: php prado-cli.php cache/flush-all"); |
||||
390 | $outWriter->writeLine("example: prado-cli help"); |
||||
391 | $outWriter->writeLine("example: prado-cli cron/tasks"); |
||||
392 | $outWriter->writeLine(); |
||||
393 | $outWriter->writeLine("The following options are available:"); |
||||
394 | $outWriter->writeLine(str_pad(" -d=<folder>", 20) . " Loads the configuration.xml/php from <folder>"); |
||||
395 | foreach ($this->_options as $option => $callable) { |
||||
396 | $data = $this->_optionsData[$option]; |
||||
397 | $outWriter->writeLine(str_pad(" --{$option}{$data[1]}", 20) . ' ' . $data[0]); |
||||
398 | } |
||||
399 | foreach ($this->_optionAliases as $alias => $option) { |
||||
400 | $data = $this->_optionsData[$option] ?? ['', '']; |
||||
401 | $outWriter->writeLine(str_pad(" -{$alias}{$data[1]}", 20) . " is an alias for --" . $option); |
||||
402 | } |
||||
403 | $outWriter->writeLine(); |
||||
404 | $outWriter->writeLine("The following commands are available:"); |
||||
405 | foreach ($this->_actions as $action) { |
||||
406 | $action->setWriter($outWriter); |
||||
407 | $outWriter->writeLine($action->renderHelp()); |
||||
408 | } |
||||
409 | $outWriter->writeLine("To see the help of each command, enter:"); |
||||
410 | $outWriter->writeLine(); |
||||
411 | $outWriter->writeLine(" prado-cli help <command-name>"); |
||||
412 | $outWriter->writeLine(); |
||||
413 | } |
||||
414 | } |
||||
415 |