Passed
Push — master ( 5fd064...896ccb )
by Fabio
04:59
created

TShellApplication   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 325
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 122
c 2
b 0
f 0
dl 0
loc 325
rs 8.96
wmc 43

16 Methods

Rating   Name   Duplication   Size   Complexity  
A runService() 0 24 6
A addShellActionClass() 0 3 1
B processArguments() 0 28 8
A detectShellLanguageCharset() 0 10 3
A getShellActions() 0 3 1
A flushOutput() 0 5 2
B processActionArguments() 0 33 9
A registerOptionAlias() 0 3 1
A printHelp() 0 24 2
A getWriter() 0 3 1
A setQuietMode() 0 3 2
A getQuietMode() 0 3 1
A registerOption() 0 3 1
A printGreeting() 0 7 3
A run() 0 16 1
A setWriter() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like TShellApplication often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TShellApplication, and based on these observations, apply Extract Interface, too.

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
 * @package Prado\Shell
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
19
use Prado\IO\ITextWriter;
20
use Prado\IO\TOutputWriter;
21
use Prado\Shell\TShellWriter;
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
 * @package Prado\Shell
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 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 bool is the application help printed
73
	 */
74
	protected $_helpPrinted = false;
75
	
76
	/**
77
	 * @var string[] arguments to the application
78
	 */
79
	private $_arguments;
80
	
81
	/**
82
	 * Runs the application.
83
	 * This method overrides the parent implementation by initializing
84
	 * application with configurations specified when it is created.
85
	 * @param null|array<string> $args
86
	 */
87
	public function run($args = null)
88
	{
89
		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

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