Completed
Push — master ( dd800d...af5658 )
by Thomas
07:26
created

InitCommand   C

Complexity

Total Complexity 41

Size/Duplication

Total Lines 277
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 19

Test Coverage

Coverage 50.26%

Importance

Changes 13
Bugs 1 Features 1
Metric Value
wmc 41
c 13
b 1
f 1
lcom 1
cbo 19
dl 0
loc 277
rs 5.8305
ccs 95
cts 189
cp 0.5026

12 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 64 1
A initialize() 0 3 1
A getPackage() 0 3 1
A getIO() 0 3 1
A interact() 0 4 1
A execute() 0 4 1
F generatePackage() 0 80 20
A manageDependencies() 0 19 3
A generateCode() 0 16 3
A generateClass() 0 22 3
A handleAppClass() 0 14 2
B handleModuleClass() 0 23 4

How to fix   Complexity   

Complex Class

Complex classes like InitCommand 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 InitCommand, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace keeko\tools\command;
3
4
use gossi\codegen\model\PhpClass;
5
use gossi\codegen\model\PhpMethod;
6
use gossi\codegen\model\PhpParameter;
7
use gossi\docblock\tags\LicenseTag;
8
use keeko\framework\schema\AuthorSchema;
9
use keeko\tools\helpers\InitCommandHelperTrait;
10
use keeko\tools\services\IOService;
11
use keeko\tools\ui\InitUI;
12
use Symfony\Component\Console\Input\InputInterface;
13
use Symfony\Component\Console\Input\InputOption;
14
use Symfony\Component\Console\Output\OutputInterface;
15
16
class InitCommand extends AbstractKeekoCommand {
17
	
18
	use InitCommandHelperTrait;
19
20
	protected function configure() {
21
		$this
22
			->setName('init')
23
			->setDescription('Initializes composer.json with keeko related values')
24
			->addOption(
25 20
				'name',
26 20
				'',
27 20
				InputOption::VALUE_REQUIRED,
28 20
				'Name of the package'
29 20
			)
30 20
			->addOption(
31 20
				'description',
32 20
				'd',
33
				InputOption::VALUE_OPTIONAL,
34 20
				'Description of the package'
35 20
			)
36 20
			->addOption(
37 20
				'author',
38 20
				'',
39
				InputOption::VALUE_OPTIONAL,
40 20
				'Author name of the package'
41 20
			)
42 20
			->addOption(
43 20
				'type',
44 20
				't',
45
				InputOption::VALUE_REQUIRED,
46 20
				'The type of the package (app|module)'
47 20
			)
48 20
			->addOption(
49 20
				'namespace',
50 20
				'ns',
51
				InputOption::VALUE_OPTIONAL,
52 20
				'The package\'s namespace for the src/ folder (If ommited, the package name is used)'
53 20
			)
54 20
			->addOption(
55 20
				'license',
56 20
				'l',
57
				InputOption::VALUE_OPTIONAL,
58 20
				'License of the package'
59 20
			)
60 20
			->addOption(
61 20
				'title',
62 20
				'',
63
				InputOption::VALUE_OPTIONAL,
64 20
				'The package\'s title (If ommited, second part of the package name is used)',
65 20
				null
66 20
			)
67 20
			->addOption(
68 20
				'classname',
69 20
				'c',
70
				InputOption::VALUE_OPTIONAL,
71 20
				'The main class name (If ommited, there is a default handler)',
72 20
				null
73 20
			)
74 20
			->addOption(
75 20
				'force',
76 20
				'f',
77
				InputOption::VALUE_NONE,
78 20
				'Allows to overwrite existing values'
79
			)
80
		;
81
		
82
		$this->configureGlobalOptions();
83
	}
84
85 20
	protected function initialize(InputInterface $input, OutputInterface $output) {
86 20
		parent::initialize($input, $output);
87 20
	}
88 20
	
89
	/**
90 20
	 * @return PackageSchema
91 20
	 */
92 20
	protected function getPackage() {
93 20
		return $this->package;
94 20
	}
95
	
96 20
	/**
97
	 * @return IOService
98
	 */
99 20
	protected function getIO() {
100 20
		return $this->io;
101
	}
102 3
103 3
	protected function interact(InputInterface $input, OutputInterface $output) {
104 3
		$ui = new InitUI($this);
105
		$ui->show();
106
	}
107
108
	protected function execute(InputInterface $input, OutputInterface $output) {
109
		$this->generatePackage();
110
		$this->generateCode();
111
	}
112
	
113
	private function generatePackage() {
114
		$input = $this->io->getInput();
115
		$force = $input->getOption('force');
116
117
		// name
118
		$localName = $this->package->getFullName();
119
		if (empty($localName) && $input->getOption('name') === null) {
120
			throw new \RuntimeException('No name for the package given');
121
		}
122
		
123
		if (($force || empty($localName)) && ($name = $input->getOption('name')) !== null) {
124
			$this->validateName($name);
125
			$this->package->setFullName($name);
126
		}
127
		
128
		// description
129
		if (($desc = $input->getOption('description')) !== null) {
130
			$this->package->setDescription($desc);
131
		}
132
		
133
		// type
134
		if (($type = $input->getOption('type')) !== null) {
135
			if (in_array($type, ['app', 'module'])) {
136
				$this->package->setType('keeko-' . $type);
137
			}
138
		}
139
		
140
		// license
141
		if (($license = $input->getOption('license')) !== null) {
142
			$this->package->setLicense($license);
143
		}
144
		
145
		// author
146
		if (($author = $input->getOption('author')) !== null
147
				&& ($this->package->getAuthors()->isEmpty() || $force)) {
148
			list($name, $email) = sscanf($author, '%s <%s>');
149
		
150
			$author = new AuthorSchema();
151
			$author->setName($name);
152
		
153
			if (substr($email, -1) == '>') {
154
				$email = substr($email, 0, -1);
155
			}
156
			$author->setEmail($email);
157
				
158
			$this->package->getAuthors()->add($author);
159
		}
160
161
		// autoload
162
		if (!$this->hasAutoload()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->hasAutoload() of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
163
			$namespace = $input->getOption('namespace');
164
			if ($namespace === null) {
165
				$namespace = str_replace('/', '\\', $this->package->getFullName());
166
			}
167
			if (substr($namespace, -2) !== '\\') {
168
				$namespace .= '\\';
169
			}
170
			$this->setAutoload($namespace);
171
		}
172
		
173
		$this->manageDependencies();
174
		
175
		// KEEKO
176
		if ($type === null) {
177
			$type = $this->getPackageType();
178
		}
179
		
180
		// title
181
		$keeko = $this->packageService->getKeeko()->getKeekoPackage($type);
182
		if (($title = $this->getPackageTitle()) !== null) {
183
			$keeko->setTitle($title);
184
		}
185
		
186
		// class
187
		if (($classname = $this->getPackageClass()) !== null) {
188
			$keeko->setClass($classname);
189
		}
190
		
191
		$this->packageService->savePackage($this->package);
192
	}
193
194
	private function manageDependencies() {
195
		// add require statements
196
		$require = $this->package->getRequire();
197
198
		if (!$require->has('php')) {
199
			$require->set('php', '>=5.4');
200
		}
201
202
		if (!$require->has('keeko/composer-installer')) {
203
			$require->set('keeko/composer-installer', '*');
204
		}
205
206
		// add require dev statements
207
		$requireDev = $this->package->getRequireDev();
208
		$requireDev->set('keeko/core', 'dev-master');
209
		$requireDev->set('composer/composer', '@dev');
210
		$requireDev->set('propel/propel', '@dev');
211
		$requireDev->set('puli/composer-plugin', '@beta');
212
	}
213
214
	private function generateCode() {
215
		$class = $this->generateClass();
216
		$type = $this->getPackageType();
217
218
		switch ($type) {
219
			case 'app':
220
				$this->handleAppClass($class);
221
				break;
222
				
223
			case 'module':
224
				$this->handleModuleClass($class);
225
				break;
226
		}
227
228
		$this->codegenService->dumpStruct($class, true);
229
	}
230
231
	private function generateClass() {
232
		$input = $this->io->getInput();
233
		$type = $this->getPackageType();
234
		$package = $this->package->getKeeko()->getKeekoPackage($type);
235
		$fqcn = str_replace(['\\', 'keeko-', '-module', '-app'], ['/', '', '', ''], $package->getClass());
236
		$classname = basename($fqcn);
237
		$filename = $this->project->getRootPath() . '/src/' . $classname . '.php';
238 3
		$fqcn = str_replace('/', '\\', $fqcn);
239 3
		
240 3
		if (!file_exists($filename) || $input->getOption('force')) {
241 3
			$class = PhpClass::create($fqcn);
242
			$class->setDescription($package->getTitle());
243 3
			
244 3
			$docblock = $class->getDocblock();
245
			$docblock->appendTag(new LicenseTag($this->package->getLicense()));
246
			$this->codegenService->addAuthors($class, $this->package);
247 3
		} else {
248 3
			$class = PhpClass::fromFile($filename);
249
		}
250
		
251
		return $class;
252 3
	}
253 3
254 3
	private function handleAppClass(PhpClass $class) {
255 3
		// set parent
256
		$class->setParentClassName('AbstractApplication');
257
		$class->addUseStatement('keeko\\framework\\foundation\\AbstractApplication');
258 3
259 3
		// method: run(Request $request, $path)
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
260 3
		if (!$class->hasMethod('run')) {
261
			$class->setMethod(PhpMethod::create('run')
262
				->addParameter(PhpParameter::create('request')->setType('Request'))
263 3
				->addParameter(PhpParameter::create('path')->setType('string'))
264 3
			);
265 3
			$class->addUseStatement('Symfony\\Component\\HttpFoundation\\Request');
266 3
		}
267 3
	}
268
	
269
	private function handleModuleClass(PhpClass $class) {
270 3
		// set parent
271 3
		$class->setParentClassName('AbstractModule');
272 3
		$class->addUseStatement('keeko\\framework\\foundation\\AbstractModule');
273
		
274
		// method: install()
275 3
		if (!$class->hasMethod('install')) {
276 3
			$class->setMethod(PhpMethod::create('install'));
277 3
		}
278
		
279 3
		// method: uninstall()
280 3
		if (!$class->hasMethod('uninstall')) {
281
			$class->setMethod(PhpMethod::create('uninstall'));
282 3
		}
283
		
284
		// method: update($from, $to)
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
285 3
		if (!$class->hasMethod('update')) {
286
			$class->setMethod(PhpMethod::create('update')
287 3
				->addParameter(PhpParameter::create('from')->setType('mixed'))
288 3
				->addParameter(PhpParameter::create('to')->setType('mixed'))
289
			);
290
		}
291 3
	}
292
}
293