ConfigCommand   B
last analyzed

Complexity

Total Complexity 48

Size/Duplication

Total Lines 391
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 48
eloc 141
c 4
b 0
f 0
dl 0
loc 391
ccs 0
cts 158
cp 0
rs 8.5599

16 Methods

Rating   Name   Duplication   Size   Complexity  
A processDelete() 0 13 2
A prepareDependencies() 0 9 1
A isGlobal() 0 8 2
A getAggregatedOptions() 0 3 1
A getConfigSettings() 0 22 6
A getScopeFilter() 0 3 2
A processShow() 0 11 2
A getValueFilter() 0 3 2
A getConfigSetting() 0 13 3
A configure() 0 38 1
A execute() 0 7 4
A completeOptionValues() 0 9 2
B listSettings() 0 48 9
A openEditor() 0 6 1
A getConfigSettingsByScope() 0 11 3
B processEdit() 0 45 7

How to fix   Complexity   

Complex Class

Complex classes like ConfigCommand 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 ConfigCommand, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file is part of the SVN-Buddy library.
4
 * For the full copyright and license information, please view
5
 * the LICENSE file that was distributed with this source code.
6
 *
7
 * @copyright Alexander Obuhovich <[email protected]>
8
 * @link      https://github.com/console-helpers/svn-buddy
9
 */
10
11
namespace ConsoleHelpers\SVNBuddy\Command;
12
13
14
use ConsoleHelpers\ConsoleKit\Config\ConfigEditor;
15
use ConsoleHelpers\SVNBuddy\Config\AbstractConfigSetting;
16
use ConsoleHelpers\SVNBuddy\Config\ArrayConfigSetting;
17
use ConsoleHelpers\SVNBuddy\Config\ChoiceConfigSetting;
18
use ConsoleHelpers\SVNBuddy\InteractiveEditor;
19
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
20
use Symfony\Component\Console\Helper\Table;
21
use Symfony\Component\Console\Helper\TableSeparator;
22
use Symfony\Component\Console\Input\InputArgument;
23
use Symfony\Component\Console\Input\InputInterface;
24
use Symfony\Component\Console\Input\InputOption;
25
use Symfony\Component\Console\Output\OutputInterface;
26
27
class ConfigCommand extends AbstractCommand implements IAggregatorAwareCommand
28
{
29
30
	/**
31
	 * Editor.
32
	 *
33
	 * @var InteractiveEditor
34
	 */
35
	private $_editor;
36
37
	/**
38
	 * Config editor.
39
	 *
40
	 * @var ConfigEditor
41
	 */
42
	private $_configEditor;
43
44
	/**
45
	 * Config settings.
46
	 *
47
	 * @var AbstractConfigSetting[]
48
	 */
49
	protected $configSettings = array();
50
51
	/**
52
	 * {@inheritdoc}
53
	 */
54
	protected function configure()
55
	{
56
		$this
57
			->setName('config')
58
			->setDescription('Change configuration settings, that are used by other commands')
59
			->setAliases(array('cfg'))
60
			->addArgument(
61
				'path',
62
				InputArgument::OPTIONAL,
63
				'Working copy path',
64
				'.'
65
			)
66
			->addOption(
67
				'show',
68
				's',
69
				InputOption::VALUE_REQUIRED,
70
				'Shows only given (instead of all) setting value'
71
			)
72
			->addOption(
73
				'edit',
74
				'e',
75
				InputOption::VALUE_REQUIRED,
76
				'Change setting value in the Interactive Editor'
77
			)
78
			->addOption(
79
				'delete',
80
				'd',
81
				InputOption::VALUE_REQUIRED,
82
				'Delete setting'
83
			)
84
			->addOption(
85
				'global',
86
				'g',
87
				InputOption::VALUE_NONE,
88
				'Operate on global instead of working copy-specific settings'
89
			);
90
91
		parent::configure();
92
	}
93
94
	/**
95
	 * Prepare dependencies.
96
	 *
97
	 * @return void
98
	 */
99
	protected function prepareDependencies()
100
	{
101
		parent::prepareDependencies();
102
103
		$container = $this->getContainer();
104
105
		$this->_editor = $container['editor'];
106
		$this->_configEditor = $container['config_editor'];
107
		$this->configSettings = $this->getConfigSettings();
108
	}
109
110
	/**
111
	 * Return possible values for the named option
112
	 *
113
	 * @param string            $optionName Option name.
114
	 * @param CompletionContext $context    Completion context.
115
	 *
116
	 * @return array
117
	 */
118
	public function completeOptionValues($optionName, CompletionContext $context)
119
	{
120
		$ret = parent::completeOptionValues($optionName, $context);
121
122
		if ( in_array($optionName, array('show', 'edit', 'delete')) ) {
123
			return array_keys($this->configSettings);
124
		}
125
126
		return $ret;
127
	}
128
129
	/**
130
	 * {@inheritdoc}
131
	 */
132
	protected function execute(InputInterface $input, OutputInterface $output)
133
	{
134
		if ( $this->processShow() || $this->processEdit() || $this->processDelete() ) {
135
			return;
136
		}
137
138
		$this->listSettings();
139
	}
140
141
	/**
142
	 * Shows setting value.
143
	 *
144
	 * @return boolean
145
	 */
146
	protected function processShow()
147
	{
148
		$setting_name = $this->io->getOption('show');
149
150
		if ( $setting_name === null ) {
151
			return false;
152
		}
153
154
		$this->listSettings($setting_name);
0 ignored issues
show
Bug introduced by
It seems like $setting_name can also be of type string[]; however, parameter $setting_name of ConsoleHelpers\SVNBuddy\...Command::listSettings() does only seem to accept string, 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

154
		$this->listSettings(/** @scrutinizer ignore-type */ $setting_name);
Loading history...
155
156
		return true;
157
	}
158
159
	/**
160
	 * Changes setting value.
161
	 *
162
	 * @return boolean
163
	 */
164
	protected function processEdit()
165
	{
166
		$setting_name = $this->io->getOption('edit');
167
168
		if ( $setting_name === null ) {
169
			return false;
170
		}
171
172
		$config_setting = $this->getConfigSetting($setting_name);
0 ignored issues
show
Bug introduced by
It seems like $setting_name can also be of type string[]; however, parameter $name of ConsoleHelpers\SVNBuddy\...and::getConfigSetting() does only seem to accept string, 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

172
		$config_setting = $this->getConfigSetting(/** @scrutinizer ignore-type */ $setting_name);
Loading history...
173
		$value = $config_setting->getValue($this->getValueFilter());
174
		$retry = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $retry is dead and can be removed.
Loading history...
175
176
		if ( $config_setting instanceof ArrayConfigSetting ) {
177
			$value = implode(PHP_EOL, $value);
178
		}
179
180
		do {
181
			try {
182
				$retry = false;
183
184
				if ( $config_setting instanceof ChoiceConfigSetting ) {
185
					$value = $this->io->choose(
186
						'Please choose value for "' . $setting_name . '" config setting [' . $value . ']:',
0 ignored issues
show
Bug introduced by
Are you sure $setting_name of type boolean|string|string[] can be used in concatenation? ( Ignorable by Annotation )

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

186
						'Please choose value for "' . /** @scrutinizer ignore-type */ $setting_name . '" config setting [' . $value . ']:',
Loading history...
187
						$config_setting->getChoices(),
188
						(string)$value,
189
						'Option value "%s" is invalid.'
190
					);
191
				}
192
				else {
193
					$value = $this->openEditor($value);
194
				}
195
196
				$config_setting->setValue($value, $this->getScopeFilter());
197
				$this->io->writeln('Setting <info>' . $setting_name . '</info> was edited.');
198
			}
199
			catch ( \InvalidArgumentException $e ) {
200
				$this->io->writeln(array('<error>' . $e->getMessage() . '</error>', ''));
201
202
				if ( $this->io->askConfirmation('Retry editing', false) ) {
203
					$retry = true;
204
				}
205
			}
206
		} while ( $retry );
207
208
		return true;
209
	}
210
211
	/**
212
	 * Opens value editing.
213
	 *
214
	 * @param mixed $value Value.
215
	 *
216
	 * @return mixed
217
	 */
218
	protected function openEditor($value)
219
	{
220
		return $this->_editor
221
			->setDocumentName('config_setting_value')
222
			->setContent($value)
223
			->launch();
224
	}
225
226
	/**
227
	 * Deletes setting value.
228
	 *
229
	 * @return boolean
230
	 */
231
	protected function processDelete()
232
	{
233
		$setting_name = $this->io->getOption('delete');
234
235
		if ( $setting_name === null ) {
236
			return false;
237
		}
238
239
		$config_setting = $this->getConfigSetting($setting_name);
0 ignored issues
show
Bug introduced by
It seems like $setting_name can also be of type string[]; however, parameter $name of ConsoleHelpers\SVNBuddy\...and::getConfigSetting() does only seem to accept string, 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

239
		$config_setting = $this->getConfigSetting(/** @scrutinizer ignore-type */ $setting_name);
Loading history...
240
		$config_setting->setValue(null, $this->getScopeFilter());
241
		$this->io->writeln('Setting <info>' . $setting_name . '</info> was deleted.');
0 ignored issues
show
Bug introduced by
Are you sure $setting_name of type boolean|string|string[] can be used in concatenation? ( Ignorable by Annotation )

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

241
		$this->io->writeln('Setting <info>' . /** @scrutinizer ignore-type */ $setting_name . '</info> was deleted.');
Loading history...
242
243
		return true;
244
	}
245
246
	/**
247
	 * Lists values for every stored setting.
248
	 *
249
	 * @param string $setting_name Setting name.
250
	 *
251
	 * @return void
252
	 */
253
	protected function listSettings($setting_name = null)
254
	{
255
		if ( isset($setting_name) ) {
256
			$this->getConfigSetting($setting_name);
257
		}
258
259
		$extra_title = isset($setting_name) ? ' (filtered)' : '';
260
261
		if ( $this->isGlobal() ) {
262
			$this->io->writeln('Showing global settings' . $extra_title . ':');
263
		}
264
		else {
265
			$this->io->writeln(
266
				'Showing settings' . $extra_title . ' for <info>' . $this->getWorkingCopyUrl() . '</info> url:'
267
			);
268
		}
269
270
		$table = new Table($this->io->getOutput());
271
272
		$table->setHeaders(array(
273
			'Setting Name',
274
			'Setting Value',
275
		));
276
277
		$value_filter = $this->getValueFilter();
278
279
		$prev_heading = null;
280
281
		foreach ( $this->getConfigSettingsByScope($this->getScopeFilter()) as $name => $config_setting ) {
282
			if ( isset($setting_name) && $name !== $setting_name ) {
283
				continue;
284
			}
285
286
			list($new_heading,) = explode('.', $name);
287
288
			if ( $prev_heading !== null && $new_heading !== $prev_heading ) {
289
				$table->addRow(new TableSeparator());
290
			}
291
292
			$table->addRow(array(
293
				$name,
294
				var_export($config_setting->getValue($value_filter), true),
295
			));
296
297
			$prev_heading = $new_heading;
298
		}
299
300
		$table->render();
301
	}
302
303
	/**
304
	 * Returns config settings filtered by scope.
305
	 *
306
	 * @param integer $scope_filter Scope filter.
307
	 *
308
	 * @return AbstractConfigSetting[]
309
	 */
310
	protected function getConfigSettingsByScope($scope_filter)
311
	{
312
		$ret = array();
313
314
		foreach ( $this->configSettings as $name => $config_setting ) {
315
			if ( $config_setting->isWithinScope($scope_filter) ) {
316
				$ret[$name] = $config_setting;
317
			}
318
		}
319
320
		return $ret;
321
	}
322
323
	/**
324
	 * Validates setting name.
325
	 *
326
	 * @param string $name Setting name.
327
	 *
328
	 * @return AbstractConfigSetting
329
	 * @throws \InvalidArgumentException When non-existing/outside of scope setting given.
330
	 */
331
	protected function getConfigSetting($name)
332
	{
333
		if ( !array_key_exists($name, $this->configSettings) ) {
334
			throw new \InvalidArgumentException('The "' . $name . '" setting is unknown.');
335
		}
336
337
		$config_setting = $this->configSettings[$name];
338
339
		if ( !$config_setting->isWithinScope($this->getScopeFilter()) ) {
340
			throw new \InvalidArgumentException('The "' . $name . '" setting cannot be used in this scope.');
341
		}
342
343
		return $config_setting;
344
	}
345
346
	/**
347
	 * Returns scope filter for viewing config settings.
348
	 *
349
	 * @return integer
350
	 */
351
	protected function getScopeFilter()
352
	{
353
		return $this->isGlobal() ? AbstractConfigSetting::SCOPE_GLOBAL : AbstractConfigSetting::SCOPE_WORKING_COPY;
354
	}
355
356
	/**
357
	 * Returns value filter for editing config settings.
358
	 *
359
	 * @return integer
360
	 */
361
	protected function getValueFilter()
362
	{
363
		return $this->isGlobal() ? AbstractConfigSetting::SCOPE_GLOBAL : null;
364
	}
365
366
	/**
367
	 * Returns possible settings with their defaults.
368
	 *
369
	 * @return AbstractConfigSetting[]
370
	 */
371
	protected function getConfigSettings()
372
	{
373
		/** @var AbstractConfigSetting[] $config_settings */
374
		$config_settings = array();
375
376
		foreach ( $this->getApplication()->all() as $command ) {
377
			if ( $command instanceof IConfigAwareCommand ) {
378
				foreach ( $command->getConfigSettings() as $config_setting ) {
379
					$config_settings[$config_setting->getName()] = $config_setting;
380
				}
381
			}
382
		}
383
384
		// Allow to operate on global settings outside of working copy.
385
		$wc_url = $this->isGlobal() ? '' : $this->getWorkingCopyUrl();
386
387
		foreach ( $config_settings as $config_setting ) {
388
			$config_setting->setWorkingCopyUrl($wc_url);
389
			$config_setting->setEditor($this->_configEditor);
390
		}
391
392
		return $config_settings;
393
	}
394
395
	/**
396
	 * Determines if global only config settings should be used.
397
	 *
398
	 * @return boolean
399
	 */
400
	protected function isGlobal()
401
	{
402
		// During auto-complete the IO isn't set.
403
		if ( !isset($this->io) ) {
404
			return true;
405
		}
406
407
		return $this->io->getOption('global');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->io->getOption('global') also could return the type string|string[] which is incompatible with the documented return type boolean.
Loading history...
408
	}
409
410
	/**
411
	 * Returns option names, that makes sense to use in aggregation mode.
412
	 *
413
	 * @return array
414
	 */
415
	public function getAggregatedOptions()
416
	{
417
		return array();
418
	}
419
420
}
421