Passed
Pull Request — master (#817)
by Fabio
07:46
created

TShellApplication::detectCronTabShell()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 1
c 0
b 0
f 0
nc 3
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
use Prado\TPropertyValue;
22
23
/**
24
 * TShellApplication class.
25
 *
26
 * TShellApplication is the base class for developing command-line PRADO
27
 * tools that share the same configurations as their Web application counterparts.
28
 *
29
 * A typical usage of TShellApplication in a command-line PHP script is as follows:
30
 * <code>
31
 * require 'path/to/vendor/autoload.php';
32
 * $application=new TShellApplication('path/to/application.xml');
33
 * $application->run($_SERVER);
34
 * // perform command-line tasks here
35
 * </code>
36
 *
37
 * Since the application instance has access to all configurations, including
38
 * path aliases, modules and parameters, the command-line script has nearly the same
39
 * accessibility to resources as the PRADO Web applications.
40
 *
41
 * @author Qiang Xue <[email protected]>
42
 * @author Brad Anderson <[email protected]> shell refactor
43
 * @since 3.1.0
44
 */
45
class TShellApplication extends \Prado\TApplication
46
{
47
	/** @var bool tells the application to be in quiet mode, levels [0..1], default 0, */
48
	private $_quietMode = 0;
49
50
	/**
51
	 * @var array<\Prado\Shell\TShellAction> cli shell Application commands. Modules can add their own command
52
	 */
53
	private $_actions = [];
54
55
	/**
56
	 * @var TShellWriter output writer.
57
	 */
58
	protected $_outWriter;
59
60
	/**
61
	 * @var array<string, callable> application command options and property set callable
62
	 */
63
	protected $_options = [];
64
65
	/**
66
	 * @var array<string, string> application command optionAliases of the short letter(s) and option name
67
	 */
68
	protected $_optionAliases = [];
69
70
	/**
71
	 * @var array<array> The option help text and help values
72
	 */
73
	protected $_optionsData = [];
74
75
	/**
76
	 * @var bool is the application help printed
77
	 */
78
	protected $_helpPrinted = false;
79
80
	/**
81
	 * @var string[] arguments to the application
82
	 */
83
	private $_arguments;
84
85
	/**
86
	 * Runs the application.
87
	 * This method overrides the parent implementation by initializing
88
	 * application with configurations specified when it is created.
89
	 * @param null|array<string> $args
90
	 */
91
	public function run($args = null)
92
	{
93
		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

93
		array_shift(/** @scrutinizer ignore-type */ $args);
Loading history...
94
		$this->_arguments = $args;
95
		$this->detectShellLanguageCharset();
96
97
		$this->addShellActionClass('Prado\\Shell\\Actions\\TFlushCachesAction');
98
		$this->addShellActionClass('Prado\\Shell\\Actions\\THelpAction');
99
		$this->addShellActionClass('Prado\\Shell\\Actions\\TPhpShellAction');
100
		$this->addShellActionClass('Prado\\Shell\\Actions\\TActiveRecordAction');
101
102
		$this->_outWriter = new TShellWriter(new TOutputWriter());
103
104
		$this->registerOption('quiet', [$this, 'setQuietMode'], 'Quiets the output to <level> [1..3], default 1', '=<level>');
105
		$this->registerOptionAlias('q', 'quiet');
106
107
		$this->attachEventHandler('onInitComplete', [$this, 'processArguments'], 20);
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

107
		$this->attachEventHandler('onInitComplete', [$this, 'processArguments'], /** @scrutinizer ignore-type */ 20);
Loading history...
108
109
		parent::run();
110
	}
111
112
	/**
113
	 * This takes the shell LANG and sets the HTTP_ACCEPT_LANGUAGE/HTTP_ACCEPT_CHARSET
114
	 * for the application to do I18N.
115
	 * @since 4.2.0
116
	 */
117
	private function detectShellLanguageCharset()
118
	{
119
		if (isset($_SERVER['LANG'])) {
120
			$lang = $_SERVER['LANG'];
121
			$pos = strpos($lang, '.');
122
			if ($pos !== false) {
123
				$_SERVER['HTTP_ACCEPT_CHARSET'] = substr($lang, $pos + 1);
124
				$lang = substr($lang, 0, $pos);
125
			}
126
			$_SERVER['HTTP_ACCEPT_LANGUAGE'] = $lang;
127
		}
128
	}
129
130
	/**
131
	 * This checks if shell environment is from a system CronTab.
132
	 * @return bool is the shell environment in crontab
133
	 * @since 4.2.2
134
	 */
135
	public static function detectCronTabShell()
136
	{
137
		return php_sapi_name() == 'cli' && (!($term = getenv('TERM')) || $term == 'unknown');
138
	}
139
140
	/**
141
	 * This processes the arguments entered into the cli.  This is processed after
142
	 * the application is initialized and modules can
143
	 * @param object $sender
144
	 * @param mixed $param
145
	 * @since 4.2.0
146
	 */
147
	public function processArguments($sender, $param)
148
	{
149
		$options = array_merge(['quiet' => [$this, 'setQuietMode']], $this->_options);
150
		$aliases = array_merge(['q' => 'quiet'], $this->_optionAliases);
151
		$skip = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $skip is dead and can be removed.
Loading history...
152
		foreach ($this->_arguments as $i => $arg) {
153
			$arg = explode('=', $arg);
154
			$processed = false;
155
			foreach ($options as $option => $setMethod) {
156
				$option = '--' . $option;
157
				if ($arg[0] === $option) {
158
					call_user_func($setMethod, $arg[1] ?? '');
159
					unset($this->_arguments[$i]);
160
					break;
161
				}
162
			}
163
			if (!$processed) {
164
				foreach ($aliases as $alias => $_option) {
165
					$alias = '-' . $alias;
166
					if (isset($options[$_option]) && $arg[0] === $alias) {
167
						call_user_func($options[$_option], $arg[1] ?? '');
168
						unset($this->_arguments[$i]);
169
						break;
170
					}
171
				}
172
			}
173
		}
174
		$this->_arguments = array_values($this->_arguments);
175
	}
176
177
	/**
178
	 * Runs the requested service.
179
	 * @since 4.2.0
180
	 */
181
	public function runService()
182
	{
183
		$args = $this->_arguments;
184
185
		$outWriter = $this->_outWriter;
186
		$valid = false;
187
188
		$this->printGreeting($outWriter);
189
		foreach ($this->_actions as $class => $action) {
190
			if (($method = $action->isValidAction($args)) !== null) {
191
				$action->setWriter($outWriter);
192
				$this->processActionArguments($args, $action, $method);
193
				$m = 'action' . str_replace('-', '', $method);
194
				if (method_exists($action, $m)) {
195
					$valid |= call_user_func([$action, $m], $args);
196
				} else {
197
					$outWriter->writeError("$method is not an available command");
198
					$valid = true;
199
				}
200
				break;
201
			}
202
		}
203
		if (!$valid && $this->_quietMode === 0) {
0 ignored issues
show
introduced by
The condition $this->_quietMode === 0 is always false.
Loading history...
204
			$this->printHelp($outWriter);
205
		}
206
	}
207
208
	/**
209
	 * This processes the arguments entered into the cli
210
	 * @param array $args
211
	 * @param TShellAction $action
212
	 * @param string $method
213
	 * @since 4.2.0
214
	 */
215
	public function processActionArguments(&$args, $action, $method)
216
	{
217
		$options = $action->options($method);
218
		$aliases = $action->optionAliases();
219
		$skip = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $skip is dead and can be removed.
Loading history...
220
		if (!$options) {
0 ignored issues
show
introduced by
$options is an empty array, thus ! $options is always true.
Loading history...
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...
221
			return;
222
		}
223
		$keys = array_flip($options);
224
		foreach ($args as $i => $arg) {
225
			$arg = explode('=', $arg);
226
			$processed = false;
227
			foreach ($options as $_option) {
228
				$option = '--' . $_option;
229
				if ($arg[0] === $option) {
230
					$action->$_option = $arg[1] ?? '';
231
					$processed = true;
232
					unset($args[$i]);
233
					break;
234
				}
235
			}
236
			if (!$processed) {
237
				foreach ($aliases as $alias => $_option) {
238
					$alias = '-' . $alias;
239
					if (isset($keys[$_option]) && $arg[0] === $alias) {
240
						$action->$_option = $arg[1] ?? '';
241
						unset($args[$i]);
242
						break;
243
					}
244
				}
245
			}
246
		}
247
		$args = array_values($args);
248
	}
249
250
251
	/**
252
	 * Flushes output to shell.
253
	 * @param bool $continueBuffering whether to continue buffering after flush if buffering was active
254
	 * @since 4.2.0
255
	 */
256
	public function flushOutput($continueBuffering = true)
257
	{
258
		$this->_outWriter->flush();
259
		if (!$continueBuffering) {
260
			$this->_outWriter = null;
261
		}
262
	}
263
264
	/**
265
	 * @param string $class action class name
266
	 * @since 4.2.0
267
	 */
268
	public function addShellActionClass($class)
269
	{
270
		$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...
271
	}
272
273
	/**
274
	 * @return \Prado\Shell\TShellAction[] the shell actions for the application
275
	 * @since 4.2.0
276
	 */
277
	public function getShellActions()
278
	{
279
		return $this->_actions;
280
	}
281
282
283
	/**
284
	 * This registers shell command line options and the setter callback
285
	 * @param string $name name of the option at the command line
286
	 * @param callable $setCallback the callback to set the property
287
	 * @param string $description Short description of the option
288
	 * @param string $values value after the option, eg "=<level>"
289
	 * @since 4.2.0
290
	 */
291
	public function registerOption($name, $setCallback, $description = '', $values = '')
292
	{
293
		$this->_options[$name] = $setCallback;
294
		$this->_optionsData[$name] = [TPropertyValue::ensureString($description), TPropertyValue::ensureString($values)];
295
	}
296
297
298
	/**
299
	 * This registers shell command line option aliases and linked variable
300
	 * @param string $alias the short command
301
	 * @param string $name the command name
302
	 * @since 4.2.0
303
	 */
304
	public function registerOptionAlias($alias, $name)
305
	{
306
		$this->_optionAliases[$alias] = $name;
307
	}
308
309
	/**
310
	 * @return \Prado\Shell\TShellWriter the writer for the class
311
	 * @since 4.2.0
312
	 */
313
	public function getWriter(): TShellWriter
314
	{
315
		return $this->_outWriter;
316
	}
317
318
	/**
319
	 * @param \Prado\Shell\TShellWriter $writer the writer for the class
320
	 * @since 4.2.0
321
	 */
322
	public function setWriter(TShellWriter $writer)
323
	{
324
		$this->_outWriter = $writer;
325
	}
326
327
	/**
328
	 * @return int the writer for the class, default 0
329
	 * @since 4.2.0
330
	 */
331
	public function getQuietMode(): int
332
	{
333
		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...
334
	}
335
336
	/**
337
	 * @param int $quietMode the writer for the class, [0..3]
338
	 * @since 4.2.0
339
	 */
340
	public function setQuietMode($quietMode)
341
	{
342
		$this->_quietMode = ($quietMode === '' ? 1 : min(max((int) $quietMode, 0), 3));
0 ignored issues
show
introduced by
The condition $quietMode === '' is always false.
Loading history...
343
	}
344
345
346
	/**
347
	 * @param mixed $outWriter
348
	 * @since 4.2.0
349
	 */
350
	public function printGreeting($outWriter)
351
	{
352
		if (!$this->_helpPrinted && $this->_quietMode === 0) {
0 ignored issues
show
introduced by
The condition $this->_quietMode === 0 is always false.
Loading history...
353
			$outWriter->write("  Command line tools for Prado " . Prado::getVersion() . ".", TShellWriter::DARK_GRAY);
354
			$outWriter->writeLine();
355
			$outWriter->flush();
356
			$this->_helpPrinted = true;
357
		}
358
	}
359
360
361
	/**
362
	 * Print command line help, default action.
363
	 * @param mixed $outWriter
364
	 * @since 4.2.0
365
	 */
366
	public function printHelp($outWriter)
367
	{
368
		$this->printGreeting($outWriter);
369
370
		$outWriter->write("usage: ");
371
		$outWriter->writeLine("php prado-cli.php command[/action] <parameter> [optional]", [TShellWriter::BLUE, TShellWriter::BOLD]);
372
		$outWriter->writeLine();
373
		$outWriter->writeLine("example: php prado-cli.php cache/flush-all");
374
		$outWriter->writeLine("example: prado-cli help");
375
		$outWriter->writeLine("example: prado-cli cron/tasks");
376
		$outWriter->writeLine();
377
		$outWriter->writeLine("The following options are available:");
378
		$outWriter->writeLine(str_pad("  -d=<folder>", 20) . " Loads the configuration.xml/php from <folder>");
379
		foreach ($this->_options as $option => $callable) {
380
			$data = $this->_optionsData[$option];
381
			$outWriter->writeLine(str_pad(" --{$option}{$data[1]}", 20) . ' ' . $data[0]);
382
		}
383
		foreach ($this->_optionAliases as $alias => $option) {
384
			$data = $this->_optionsData[$option] ?? ['', ''];
385
			$outWriter->writeLine(str_pad("  -{$alias}{$data[1]}", 20) . " is an alias for --" . $option);
386
		}
387
		$outWriter->writeLine();
388
		$outWriter->writeLine("The following commands are available:");
389
		foreach ($this->_actions as $action) {
390
			$action->setWriter($outWriter);
391
			$outWriter->writeLine($action->renderHelp());
392
		}
393
		$outWriter->writeLine("To see the help of each command, enter:");
394
		$outWriter->writeLine();
395
		$outWriter->writeLine("  prado-cli help <command-name>");
396
		$outWriter->writeLine();
397
	}
398
}
399