Completed
Push — master ( c9073d...17137d )
by Thomas
17:02
created

GenerateActionCommand::generateCode()   C

Complexity

Conditions 8
Paths 36

Size

Total Lines 63
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 42
CRAP Score 8

Importance

Changes 6
Bugs 0 Features 0
Metric Value
c 6
b 0
f 0
dl 0
loc 63
ccs 42
cts 42
cp 1
rs 6.8825
cc 8
eloc 38
nc 36
nop 1
crap 8

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace keeko\tools\command;
3
4
use gossi\codegen\model\PhpClass;
5
use gossi\codegen\model\PhpTrait;
6
use keeko\core\schema\ActionSchema;
7
use keeko\tools\generator\GeneratorFactory;
8
use keeko\tools\helpers\QuestionHelperTrait;
9
use keeko\tools\utils\NamespaceResolver;
10
use keeko\tools\utils\NameUtils;
11
use phootwork\file\File;
12
use phootwork\lang\Text;
13
use Symfony\Component\Console\Input\InputArgument;
14
use Symfony\Component\Console\Input\InputInterface;
15
use Symfony\Component\Console\Input\InputOption;
16
use Symfony\Component\Console\Output\OutputInterface;
17
use Symfony\Component\Console\Question\ConfirmationQuestion;
18
use Symfony\Component\Console\Question\Question;
19
use keeko\tools\generator\action\BlankActionGenerator;
20
use keeko\tools\generator\action\NoopActionGenerator;
21
22
class GenerateActionCommand extends AbstractGenerateCommand {
23
24
	use QuestionHelperTrait;
25 20
26 20
	protected function configure() {
27 20
		$this
28 20
			->setName('generate:action')
29 20
			->setDescription('Generates an action')
30 20
			->addArgument(
31 20
				'name',
32
				InputArgument::OPTIONAL,
33 20
				'The name of the action, which should be generated. Typically in the form %nomen%-%verb% (e.g. user-create)'
34 20
			)
35 20
			->addOption(
36 20
				'classname',
37 20
				'c',
38 20
				InputOption::VALUE_OPTIONAL,
39
				'The main class name (If ommited, class name will be guessed from action name)',
40 20
				null
41 20
			)
42 20
			->addOption(
43 20
				'model',
44 20
				'm',
45
				InputOption::VALUE_OPTIONAL,
46 20
				'The model for which the actions should be generated, when there is no name argument (if ommited all models will be generated)'
47 20
			)
48 20
			->addOption(
49 20
				'title',
50 20
				'',
51
				InputOption::VALUE_OPTIONAL,
52 20
				'The title for the generated option'
53 20
			)
54 20
			->addOption(
55 20
				'type',
56 20
				'',
57
				InputOption::VALUE_OPTIONAL,
58 20
				'The type of this action (list|create|read|update|delete) (if ommited template is guessed from action name)'
59 20
			)->addOption(
60 20
				'acl',
61 20
				'',
62
				InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
63 20
				'The acl\s for this action (guest, user and/or admin)'
64
			)
65
// 			->addOption(
66
// 				'schema',
67
// 				's',
68
// 				InputOption::VALUE_OPTIONAL,
69
// 				'Path to the database schema (if ommited, database/schema.xml is used)',
70
// 				null
71 1
// 			)
72
		;
73 20
		
74
		$this->configureGenerateOptions();
75 20
		
76 20
		parent::configure();
77
	}
78
79
	/**
80
	 * Checks whether actions can be generated at all by reading composer.json and verify
81
	 * all required information are available
82 10
	 */
83 10
	private function preCheck() {
84 10
		$module = $this->packageService->getModule();
85 1
		if ($module === null) {
86 4
			throw new \DomainException('No module definition found in composer.json - please run `keeko init`.');
87 9
		}
88
	}
89
	
90
	protected function interact(InputInterface $input, OutputInterface $output) {
91
		$this->preCheck();
92
		
93
		// check if the dialog can be skipped
94
		$name = $input->getArgument('name');
95
		$model = $input->getOption('model');
96
		
97
		if ($model !== null) {
98
			return;
99
		} else if ($name !== null) {
100
			$generateModel = false;
101
		} else {
102
			$modelQuestion = new ConfirmationQuestion('Do you want to generate an action based off a model?');
103
			$generateModel = $this->askConfirmation($modelQuestion);
104
		}
105
		
106
		// ask questions for a model
107
		if ($generateModel && !($this->package->getVendor() === 'keeko' && $this->modelService->isCoreSchema())) {
108
109
			$schema = str_replace(getcwd(), '', $this->getSchema());
0 ignored issues
show
Bug introduced by
The method getSchema() does not seem to exist on object<keeko\tools\command\GenerateActionCommand>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
110
			$allQuestion = new ConfirmationQuestion(sprintf('For all models in the schema (%s)?', $schema));
111
			$allModels = $this->askConfirmation($allQuestion);
112
113
			if (!$allModels) {
114
				$modelQuestion = new Question('Which model');
115
				$modelQuestion->setAutocompleterValues($this->modelService->getModelNames());
116
				$model = $this->askQuestion($modelQuestion);
117
				$input->setOption('model', $model);
118
			}
119
		} else if (!$generateModel) {
120
			$action = $this->getAction($name);
121
			
122
			// ask for title
123
			$pkgTitle = $action->getTitle();
124
			$title = $input->getOption('title');
125
			if ($title === null && !empty($pkgTitle)) {
126
				$title = $pkgTitle;
127
			}
128
			$titleQuestion = new Question('What\'s the title for your action?', $title);
129
			$title = $this->askQuestion($titleQuestion);
130
			$input->setOption('title', $title);
131
			
132
			// ask for classname
133
			$pkgClass = $action->getClass();
134
			$classname = $input->getOption('classname');
135
			if ($classname === null) {
136
				if (!empty($pkgClass)) {
137
					$classname = $pkgClass;
138
				} else {
139
					$classname = $this->guessClassname($name);
140
				}
141
			}
142
			$classname = $this->askQuestion(new Question('Classname', $classname));
143
			$input->setOption('classname', $classname);
144
			
145
			// ask for acl
146
			$acls = $this->getAcl($action);
147
			$aclQuestion = new Question('ACL (comma separated list, with these options: guest, user, admin)', implode(', ', $acls));
148
			$acls = $this->askQuestion($aclQuestion);
149
			$input->setOption('acl', $acls);
150
		}
151
	}
152 10
153 10
	protected function execute(InputInterface $input, OutputInterface $output) {
154
		$this->preCheck();
155
		
156
		// 1. find out which action(s) to generate
157
		// 2. generate the information in the package
158
		// 3. generate the code for the action
159 9
		
160 9
		$name = $input->getArgument('name');
161
		$model = $input->getOption('model');
162
163 9
		// only a specific action
164 3
		if ($name) {
165 2
			$this->generateAction($name);
166
		}
167
168 6
		// create action(s) from a model
169 2
		else if ($model) {
170 2
			$this->generateModel($model);
171
		}
172
		
173 4
		// if this is a core-module, find the related model
174 3
		else if ($this->package->getVendor() == 'keeko' && $this->modelService->isCoreSchema()) {
175 3
			$model = $this->package->getName();
176 2
			if ($this->modelService->hasModel($model)) {
177 2
				$input->setOption('model', $model);
178 2
				$this->generateModel($model);
179 1
			} else {
180
				$this->logger->error('Tried to find model on my own, wasn\'t lucky - please provide model with the --model option');
181 3
			}
182
		}
183
184
		// anyway, generate all
185 1
		else {
186 1
			foreach ($this->modelService->getModels() as $model) {
187 1
				$this->generateModel($model->getOriginCommonName());
188
			}
189
		}
190 8
		
191 8
		$this->packageService->savePackage();
192
	}
193 5
194 5
	private function generateModel($modelName) {
195 5
		$this->logger->info('Generate Action from Model: ' . $modelName);
196 5
		$input = $this->io->getInput();
197 5
		$typeDump = $input->getOption('type');
198 1
		if ($typeDump !== null) {
199 1
			$types = [$typeDump];
200 4
		} else {
201
			$types = ['create', 'read', 'list', 'update', 'delete'];
202
		}
203 5
204 5
		foreach ($types as $type) {
205 5
			$input->setOption('acl', ['admin']);
1 ignored issue
show
Documentation introduced by
array('admin') is of type array<integer,string,{"0":"string"}>, but the function expects a string|boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
206 5
			$input->setOption('type', $type);
207 5
			$actionName = $modelName . '-' . $type;
208 5
			$action = $this->getAction($actionName);
209 4
			if (Text::create($action->getTitle())->isEmpty()) {
210 5
				$action->setTitle($this->getActionTitle($modelName, $type));
211 5
			}
212 5
			$this->generateAction($actionName);
213
		}
214 5
		
215 5
		$input->setOption('type', $typeDump);
216
	}
217 4
218
	private function getActionTitle($modelName, $type) {
219 4
		switch ($type) {
220 3
			case 'list':
221
				return 'List all ' . NameUtils::pluralize($modelName);
222 4
223 4
			case 'create':
224 4
			case 'read':
225 4
			case 'update':
226 4
			case 'delete':
227
				return ucfirst($type) . 's ' . (in_array($modelName[0], ['a', 'e', 'i', 'o', 'u']) ? 'an' : 'a') . ' ' . $modelName;
228
		}
229
	}
230
231
	
232
	/**
233
	 * Generates an action.
234
	 *  
235
	 * @param string $actionName
236
	 * @param ActionSchema $action the action node from composer.json
0 ignored issues
show
Documentation introduced by
There is no parameter named $action. Did you maybe mean $actionName?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
237 8
	 */
238 8
	private function generateAction($actionName) {
239 8
		$this->logger->info('Generate Action: ' . $actionName);
240
		$input = $this->io->getInput();
241
		
242 8
		// get action and create it if it doesn't exist
243
		$action = $this->getAction($actionName);
244 8
		
245 2
		if (($title = $input->getOption('title')) !== null) {
246 2
			$action->setTitle($title);
247
		}
248 8
249 1
		if (Text::create($action->getTitle())->isEmpty()) {
250
			throw new \RuntimeException(sprintf('Cannot create action %s, because I am missing a title for it', $actionName));
251
		}
252 7
253 2
		if (($classname = $input->getOption('classname')) !== null) {
254 2
			$action->setClass($classname);
255
		}
256
		
257 7
		// guess classname if there is none set yet
258 4
		if (Text::create($action->getClass())->isEmpty()) {
259 4
			$action->setClass($this->guessClassname($actionName));
260
		}
261
		
262 7
		// guess title if there is none set yet
263 7
		if (Text::create($action->getTitle())->isEmpty() 
264 7
				&& $this->modelService->isModelAction($action)
265
				&& $this->modelService->isCrudAction($action)) {
266
			$modelName = $this->modelService->getModelNameByAction($action);
267
			$type = $this->modelService->getOperationByAction($action);
268
			$action->setTitle($this->getActionTitle($modelName, $type));
269
		}
270
		
271 7
		// set acl
272
		$action->setAcl($this->getAcl($action));
273
		
274 7
		// generate code
275 7
		$this->generateCode($action);
276
	}
277 4
	
278 4
	private function guessClassname($name) {
279 4
		$namespace = NamespaceResolver::getNamespace('src/action', $this->package);
280
		return $namespace . '\\' . NameUtils::toStudlyCase($name) . 'Action';
281
	}
282
	
283
	/**
284
	 * 
285
	 * @param string $actionName
286
	 * @return ActionSchema
287 8
	 */
288 8
	private function getAction($actionName) {
289 8
		$action = $this->packageService->getAction($actionName);
290 7
		if ($action == null) {
291 7
			$action = new ActionSchema($actionName);
292 7
			$module = $this->packageService->getModule();
293 7
			$module->addAction($action);
294 8
		}
295
		return $action;
296
	}
297 7
	
298 7
	private function getAcl(ActionSchema $action) {
299 7
		$acls = [];
300 7
		$acl = $this->io->getInput()->getOption('acl');
301 7
		if ($acl !== null && count($acl) > 0) {
302 2
			if (!is_array($acl)) {
303 2
				$acl = [$acl];
304 7
			}
305 7
			foreach ($acl as $group) {
306 1
				if (strpos($group, ',') !== false) {
307 1
					$groups = explode(',', $group);
308 1
					foreach ($groups as $g) {
309 1
						$acls[] = trim($g);
310 1
					}
311 6
				} else {
312
					$acls[] = $group;
313 7
				}
314
			}
315 7
			
316
			return $acls;
317
		}
318
		
319
		// read default from package
320
		if (!$action->getAcl()->isEmpty()) {
321
			return $action->getAcl()->toArray();
322
		}
323
324
		return $acls;
325
	}
326
	
327
	/**
328
	 * Generates code for an action
329
	 * 
330
	 * @param ActionSchema $action
331 7
	 */
332 7
	private function generateCode(ActionSchema $action) {
333 7
		$input = $this->io->getInput();
334
		$trait = null;
0 ignored issues
show
Unused Code introduced by
$trait is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
335
336 7
		// class
337 7
		$class = new PhpClass($action->getClass());
338 7
		$filename = $this->codegenService->getFilename($class);
339 7
		$traitNs = $class->getNamespace() . '\\base';
340 7
		$traitName = $class->getName() . 'Trait';
341
		$overwrite = false;
342
		
343 7
		// load from file, when class exists
344
		if (file_exists($filename)) {
345 1
			// load trait
346 1
			$trait = new PhpTrait($traitNs . '\\' . $traitName);
347
			$traitFile = new File($this->codegenService->getFilename($trait));
348 1
349 1
			if ($traitFile->exists()) {
350 1
				$trait = PhpTrait::fromFile($traitFile->getPathname());
0 ignored issues
show
Unused Code introduced by
$trait is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
351
			}
352
		
353 1
			// load class
354 1
			$class = PhpClass::fromFile($filename);
355 1
		}
356
		
357
		// anyway seed class information
358
		else {
359 6
			$overwrite = true;
360 6
			$class->setParentClassName('AbstractAction');
361 6
			$class->setDescription($action->getTitle());
362 6
			$class->setLongDescription($action->getDescription());
363 6
			$this->codegenService->addAuthors($class, $this->package);
364
		}
365
		
366
		// create base trait
367 7
		$modelName = $input->getOption('model');
368 5
		if ($modelName !== null) {
369 1
			$type = $this->packageService->getActionType($action->getName(), $modelName);
370 1
			$generator = GeneratorFactory::createActionTraitGenerator($type, $this->service);
371 1
			$trait = $generator->generate($traitNs . '\\' . $traitName, $action);
372 5
373 5
			$this->codegenService->addAuthors($trait, $this->package);
374
			$this->codegenService->dumpStruct($trait, true);
375 5
			
376 5
			if (!$class->hasTrait($trait)) {
377
				$class->addTrait($trait);
378 5
				$overwrite = true;
379 4
			}
380 4
		}
381 4
		
382 5
		// create class generator
383
		if ($modelName === null && !$class->hasMethod('run')) {
384 2
			$overwrite = true;
385 2
			$generator = new BlankActionGenerator($this->service);
386 2
		} else {
387 2
			$generator = new NoopActionGenerator($this->service);
388 2
		}
389
390
		$class = $generator->generate($class);
391 7
		$overwrite = $overwrite || $input->getOption('force');
392 7
393
		$this->codegenService->dumpStruct($class, $overwrite);
394
	}
395
396
}
397