Passed
Pull Request — master (#817)
by
unknown
05:07
created

TShellApplication::getShellActions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
/**
3
 * TShellApplication class file
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 */
9
10
namespace Prado\Shell;
11
12
use Prado\Prado;
13
use Prado\Shell\Actions\TActiveRecordAction;
14
use Prado\Shell\Actions\THelpAction;
15
use Prado\Shell\Actions\TFlushCachesAction;
16
use Prado\Shell\Actions\TPhpShellAction;
17
18
use Prado\IO\ITextWriter;
19
use Prado\IO\TOutputWriter;
20
use Prado\Shell\TShellWriter;
21
22
/**
23
 * TShellApplication class.
24
 *
25
 * TShellApplication is the base class for developing command-line PRADO
26
 * tools that share the same configurations as their Web application counterparts.
27
 *
28
 * A typical usage of TShellApplication in a command-line PHP script is as follows:
29
 * <code>
30
 * require 'path/to/vendor/autoload.php';
31
 * $application=new TShellApplication('path/to/application.xml');
32
 * $application->run($_SERVER);
33
 * // perform command-line tasks here
34
 * </code>
35
 *
36
 * Since the application instance has access to all configurations, including
37
 * path aliases, modules and parameters, the command-line script has nearly the same
38
 * accessibility to resources as the PRADO Web applications.
39
 *
40
 * @author Qiang Xue <[email protected]>
41
 * @author Brad Anderson <[email protected]> shell refactor
42
 * @since 3.1.0
43
 */
44
class TShellApplication extends \Prado\TApplication
45
{
46
	/** @var bool tells the application to be in quiet mode, levels [0..1], default 0, */
47
	private $_quietMode = 0;
48
49
	/**
50
	 * @var array<\Prado\Shell\TShellAction> cli shell Application commands. Modules can add their own command
51
	 */
52
	private $_actions = [];
53
54
	/**
55
	 * @var TShellWriter output writer.
56
	 */
57
	protected $_outWriter;
58
59
	/**
60
	 * @var array<string, callable> application command options and property set callable
61
	 */
62
	protected $_options = [];
63
64
	/**
65
	 * @var array<string, string> application command optionAliases of the short letter(s) and option name
66
	 */
67
	protected $_optionAliases = [];
68
69
	/**
70
	 * @var bool is the application help printed
71
	 */
72
	protected $_helpPrinted = false;
73
74
	/**
75
	 * @var string[] arguments to the application
76
	 */
77
	private $_arguments;
78
79
	/**
80
	 * Runs the application.
81
	 * This method overrides the parent implementation by initializing
82
	 * application with configurations specified when it is created.
83
	 * @param null|array<string> $args
84
	 */
85
	public function run($args = null)
86
	{
87
		array_shift($args);
0 ignored issues
show
Bug introduced by
It seems like $args can also be of type null; however, parameter $array of array_shift() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

87
		array_shift(/** @scrutinizer ignore-type */ $args);
Loading history...
88
		$this->_arguments = $args;
89
		$this->detectShellLanguageCharset();
90
91
		$this->addShellActionClass('Prado\\Shell\\Actions\\TFlushCachesAction');
92
		$this->addShellActionClass('Prado\\Shell\\Actions\\THelpAction');
93
		$this->addShellActionClass('Prado\\Shell\\Actions\\TPhpShellAction');
94
		$this->addShellActionClass('Prado\\Shell\\Actions\\TActiveRecordAction');
95
96
		$this->_outWriter = new TShellWriter(new TOutputWriter());
97
98
		$this->attachEventHandler('onInitComplete', [$this, 'processArguments']);
99
100
		parent::run();
101
	}
102
103
	/**
104
	 * This takes the shell LANG and sets the HTTP_ACCEPT_LANGUAGE/HTTP_ACCEPT_CHARSET
105
	 * for the application to do I18N.
106
	 * @since 4.2.0
107
	 */
108
	private function detectShellLanguageCharset()
109
	{
110
		if (isset($_SERVER['LANG'])) {
111
			$lang = $_SERVER['LANG'];
112
			$pos = strpos($lang, '.');
113
			if ($pos !== false) {
114
				$_SERVER['HTTP_ACCEPT_CHARSET'] = substr($lang, $pos + 1);
115
				$lang = substr($lang, 0, $pos);
116
			}
117
			$_SERVER['HTTP_ACCEPT_LANGUAGE'] = $lang;
118
		}
119
	}
120
121
	/**
122
	 * This checks if shell environment is from a system CronTab.
123
	 * @return bool is the shell environment in crontab
124
	 * @since 4.2.3
125
	 */
126
	public static function detectCronTabShell()
127
	{
128
		return php_sapi_name() == 'cli' && !getenv('LANG') && !getenv('TERM') && !getenv('TERM_PROGRAM');
129
	}
130
131
	/**
132
	 * This processes the arguments entered into the cli.  This is processed after
133
	 * the application is initialized and modules can
134
	 * @param object $sender
135
	 * @param mixed $param
136
	 * @since 4.2.0
137
	 */
138
	public function processArguments($sender, $param)
139
	{
140
		$options = array_merge(['quiet' => [$this, 'setQuietMode']], $this->_options);
141
		$aliases = array_merge(['q' => 'quiet'], $this->_optionAliases);
142
		$skip = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $skip is dead and can be removed.
Loading history...
143
		foreach ($this->_arguments as $i => $arg) {
144
			$arg = explode('=', $arg);
145
			$processed = false;
146
			foreach ($options as $option => $setMethod) {
147
				$option = '--' . $option;
148
				if ($arg[0] === $option) {
149
					call_user_func($setMethod, $arg[1] ?? '');
150
					unset($this->_arguments[$i]);
151
					break;
152
				}
153
			}
154
			if (!$processed) {
155
				foreach ($aliases as $alias => $_option) {
156
					$alias = '-' . $alias;
157
					if (isset($options[$_option]) && $arg[0] === $alias) {
158
						call_user_func($options[$_option], $arg[1] ?? '');
159
						unset($this->_arguments[$i]);
160
						break;
161
					}
162
				}
163
			}
164
		}
165
		$this->_arguments = array_values($this->_arguments);
166
	}
167
168
	/**
169
	 * Runs the requested service.
170
	 * @since 4.2.0
171
	 */
172
	public function runService()
173
	{
174
		$args = $this->_arguments;
175
176
		$outWriter = $this->_outWriter;
177
		$valid = false;
178
179
		$this->printGreeting($outWriter);
180
		foreach ($this->_actions as $class => $action) {
181
			if (($method = $action->isValidAction($args)) !== null) {
182
				$action->setWriter($outWriter);
183
				$this->processActionArguments($args, $action, $method);
184
				$m = 'action' . str_replace('-', '', $method);
185
				if (method_exists($action, $m)) {
186
					$valid |= call_user_func([$action, $m], $args);
187
				} else {
188
					$outWriter->writeError("$method is not an available command");
189
					$valid = true;
190
				}
191
				break;
192
			}
193
		}
194
		if (!$valid && $this->_quietMode === 0) {
0 ignored issues
show
introduced by
The condition $this->_quietMode === 0 is always false.
Loading history...
195
			$this->printHelp($outWriter);
196
		}
197
	}
198
199
	/**
200
	 * This processes the arguments entered into the cli
201
	 * @param array $args
202
	 * @param TShellAction $action
203
	 * @param string $method
204
	 * @since 4.2.0
205
	 */
206
	public function processActionArguments(&$args, $action, $method)
207
	{
208
		$options = $action->options($method);
209
		$aliases = $action->optionAliases();
210
		$skip = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $skip is dead and can be removed.
Loading history...
211
		if (!$options) {
0 ignored issues
show
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
introduced by
$options is an empty array, thus ! $options is always true.
Loading history...
212
			return;
213
		}
214
		$keys = array_flip($options);
215
		foreach ($args as $i => $arg) {
216
			$arg = explode('=', $arg);
217
			$processed = false;
218
			foreach ($options as $_option) {
219
				$option = '--' . $_option;
220
				if ($arg[0] === $option) {
221
					$action->$_option = $arg[1] ?? '';
222
					$processed = true;
223
					unset($args[$i]);
224
					break;
225
				}
226
			}
227
			if (!$processed) {
228
				foreach ($aliases as $alias => $_option) {
229
					$alias = '-' . $alias;
230
					if (isset($keys[$_option]) && $arg[0] === $alias) {
231
						$action->$_option = $arg[1] ?? '';
232
						unset($args[$i]);
233
						break;
234
					}
235
				}
236
			}
237
		}
238
		$args = array_values($args);
239
	}
240
241
242
	/**
243
	 * Flushes output to shell.
244
	 * @param bool $continueBuffering whether to continue buffering after flush if buffering was active
245
	 * @since 4.2.0
246
	 */
247
	public function flushOutput($continueBuffering = true)
248
	{
249
		$this->_outWriter->flush();
250
		if (!$continueBuffering) {
251
			$this->_outWriter = null;
252
		}
253
	}
254
255
	/**
256
	 * @param string $class action class name
257
	 * @since 4.2.0
258
	 */
259
	public function addShellActionClass($class)
260
	{
261
		$this->_actions[is_array($class) ? $class['class'] : $class] = Prado::createComponent($class);
0 ignored issues
show
introduced by
The condition is_array($class) is always false.
Loading history...
262
	}
263
264
	/**
265
	 * @return \Prado\Shell\TShellAction[] the shell actions for the application
266
	 * @since 4.2.0
267
	 */
268
	public function getShellActions()
269
	{
270
		return $this->_actions;
271
	}
272
273
274
	/**
275
	 * This registers shell command line options and the setter callback
276
	 * @param string $name name of the option at the command line
277
	 * @param callable $setCallback the callback to set the property
278
	 * @since 4.2.0
279
	 */
280
	public function registerOption($name, $setCallback)
281
	{
282
		$this->_options[$name] = $setCallback;
283
	}
284
285
286
	/**
287
	 * This registers shell command line option aliases and linked variable
288
	 * @param string $alias the short command
289
	 * @param string $name the command name
290
	 * @since 4.2.0
291
	 */
292
	public function registerOptionAlias($alias, $name)
293
	{
294
		$this->_optionAliases[$alias] = $name;
295
	}
296
297
	/**
298
	 * @return \Prado\Shell\TShellWriter the writer for the class
299
	 * @since 4.2.0
300
	 */
301
	public function getWriter(): TShellWriter
302
	{
303
		return $this->_outWriter;
304
	}
305
306
	/**
307
	 * @param \Prado\Shell\TShellWriter $writer the writer for the class
308
	 * @since 4.2.0
309
	 */
310
	public function setWriter(TShellWriter $writer)
311
	{
312
		$this->_outWriter = $writer;
313
	}
314
315
	/**
316
	 * @return int the writer for the class, default 0
317
	 * @since 4.2.0
318
	 */
319
	public function getQuietMode(): int
320
	{
321
		return $this->_quietMode;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_quietMode returns the type boolean which is incompatible with the type-hinted return integer.
Loading history...
322
	}
323
324
	/**
325
	 * @param int $quietMode the writer for the class, [0..3]
326
	 * @since 4.2.0
327
	 */
328
	public function setQuietMode($quietMode)
329
	{
330
		$this->_quietMode = ($quietMode === '' ? 1 : min(max((int) $quietMode, 0), 3));
0 ignored issues
show
introduced by
The condition $quietMode === '' is always false.
Loading history...
331
	}
332
333
334
	/**
335
	 * @param mixed $outWriter
336
	 * @since 4.2.0
337
	 */
338
	public function printGreeting($outWriter)
339
	{
340
		if (!$this->_helpPrinted && $this->_quietMode === 0) {
0 ignored issues
show
introduced by
The condition $this->_quietMode === 0 is always false.
Loading history...
341
			$outWriter->write("  Command line tools for Prado " . Prado::getVersion() . ".", TShellWriter::DARK_GRAY);
342
			$outWriter->writeLine();
343
			$outWriter->flush();
344
			$this->_helpPrinted = true;
345
		}
346
	}
347
348
349
	/**
350
	 * Print command line help, default action.
351
	 * @param mixed $outWriter
352
	 * @since 4.2.0
353
	 */
354
	public function printHelp($outWriter)
355
	{
356
		$this->printGreeting($outWriter);
357
358
		$outWriter->write("usage: ");
359
		$outWriter->writeLine("php prado-cli.php command[/action] <parameter> [optional]", [TShellWriter::BLUE, TShellWriter::BOLD]);
360
		$outWriter->writeLine();
361
		$outWriter->writeLine("example: php prado-cli.php cache/flush-all");
362
		$outWriter->writeLine("example: prado-cli help");
363
		$outWriter->writeLine("example: prado-cli cron/tasks");
364
		$outWriter->writeLine();
365
		$outWriter->writeLine("The following options are available:");
366
		$outWriter->writeLine(str_pad("  -d=<folder>", 20) . "Loads the configuration.xml/php from <folder>");
367
		$outWriter->writeLine(str_pad("  -q=<level>", 20) . "Quiets the output to <level> [1..3]");
368
		$outWriter->writeLine();
369
		$outWriter->writeLine("The following commands are available:");
370
		foreach ($this->_actions as $action) {
371
			$action->setWriter($outWriter);
372
			$outWriter->writeLine($action->renderHelp());
373
		}
374
		$outWriter->writeLine("To see the help of each command, enter:");
375
		$outWriter->writeLine();
376
		$outWriter->writeLine("  prado-cli help <command-name>");
377
		$outWriter->writeLine();
378
	}
379
}
380