Completed
Push — master ( e6e2aa...27549f )
by Thomas
09:03
created

GenerateActionCommand::generateModel()   B

Complexity

Conditions 9
Paths 16

Size

Total Lines 56
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 35
CRAP Score 9.0017

Importance

Changes 12
Bugs 0 Features 2
Metric Value
c 12
b 0
f 2
dl 0
loc 56
ccs 35
cts 36
cp 0.9722
rs 7.1584
cc 9
eloc 32
nc 16
nop 1
crap 9.0017

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 keeko\framework\schema\ActionSchema;
5
use keeko\framework\utils\NameUtils;
6
use keeko\tools\generator\action\SkeletonActionGenerator;
7
use keeko\tools\generator\action\ToManyRelationshipAddActionGenerator;
8
use keeko\tools\generator\action\ToManyRelationshipReadActionGenerator;
9
use keeko\tools\generator\action\ToManyRelationshipRemoveActionGenerator;
10
use keeko\tools\generator\action\ToManyRelationshipUpdateActionGenerator;
11
use keeko\tools\generator\action\ToOneRelationshipReadActionGenerator;
12
use keeko\tools\generator\action\ToOneRelationshipUpdateActionGenerator;
13
use keeko\tools\generator\GeneratorFactory;
14
use keeko\tools\helpers\QuestionHelperTrait;
15
use keeko\tools\model\ManyRelationship;
16
use keeko\tools\model\Relationship;
17
use keeko\tools\utils\NamespaceResolver;
18
use phootwork\lang\Text;
19
use Propel\Generator\Model\Table;
20
use Symfony\Component\Console\Input\InputArgument;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Input\InputOption;
23
use Symfony\Component\Console\Output\OutputInterface;
24
use Symfony\Component\Console\Question\ConfirmationQuestion;
25 20
use Symfony\Component\Console\Question\Question;
26 20
27 20
class GenerateActionCommand extends AbstractGenerateCommand {
28 20
29 20
	use QuestionHelperTrait;
30 20
31 20
	protected function configure() {
32
		$this
33 20
			->setName('generate:action')
34 20
			->setDescription('Generates an action')
35 20
			->addArgument(
36 20
				'name',
37 20
				InputArgument::OPTIONAL,
38 20
				'The name of the action, which should be generated. Typically in the form %nomen%-%verb% (e.g. user-create)'
39
			)
40 20
			->addOption(
41 20
				'classname',
42 20
				'c',
43 20
				InputOption::VALUE_OPTIONAL,
44 20
				'The main class name (If ommited, class name will be guessed from action name)',
45
				null
46 20
			)
47 20
			->addOption(
48 20
				'model',
49 20
				'm',
50 20
				InputOption::VALUE_OPTIONAL,
51
				'The model for which the actions should be generated, when there is no name argument (if ommited all models will be generated)'
52 20
			)
53 20
			->addOption(
54 20
				'title',
55 20
				'',
56 20
				InputOption::VALUE_OPTIONAL,
57
				'The title for the generated option'
58 20
			)
59 20
			->addOption(
60 20
				'type',
61 20
				'',
62
				InputOption::VALUE_OPTIONAL,
63 20
				'The type of this action (list|create|read|update|delete) (if ommited template is guessed from action name)'
64
			)->addOption(
65
				'acl',
66
				'',
67
				InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
68
				'The acl\s for this action (guest, user and/or admin)'
69
			)
70
		;
71 1
		
72
		$this->configureGenerateOptions();
73 20
		
74
		parent::configure();
75 20
	}
76 20
77
	protected function initialize(InputInterface $input, OutputInterface $output) {
78
		parent::initialize($input, $output);
79
	}
80
81
	/**
82 10
	 * Checks whether actions can be generated at all by reading composer.json and verify
83 10
	 * all required information are available
84 10
	 */
85 1
	private function preCheck() {
86 4
		$module = $this->packageService->getModule();
87 9
		if ($module === null) {
88
			throw new \DomainException('No module definition found in composer.json - please run `keeko init`.');
89
		}
90
	}
91
	
92
	protected function interact(InputInterface $input, OutputInterface $output) {
93
		$this->preCheck();
94
		
95
		// check if the dialog can be skipped
96
		$name = $input->getArgument('name');
97
		$model = $input->getOption('model');
98
		
99
		if ($model !== null) {
100
			return;
101
		} else if ($name !== null) {
102
			$generateModel = false;
103
		} else {
104
			$modelQuestion = new ConfirmationQuestion('Do you want to generate an action based off a model?');
105
			$generateModel = $this->askConfirmation($modelQuestion);
106
		}
107
		
108
		// ask questions for a model
109
		if ($generateModel !== false) {
110
			$schema = str_replace(getcwd(), '', $this->modelService->getSchema());
111
			$allQuestion = new ConfirmationQuestion(sprintf('For all models in the schema (%s)?', $schema));
112
			$allModels = $this->askConfirmation($allQuestion);
113
114
			if (!$allModels) {
115
				$modelQuestion = new Question('Which model');
116
				$modelQuestion->setAutocompleterValues($this->modelService->getModelNames());
117
				$model = $this->askQuestion($modelQuestion);
118
				$input->setOption('model', $model);
119
			}
120
		} else {
121
			if ($name === null) {
122
				$nameQuestion = new Question('What\'s the name for your action (must be a unique identifier)?', '');
123
				$name = $this->askQuestion($nameQuestion);
124
				$input->setArgument('name', $name);
125
			}
126
			$action = $this->getAction($name);
127
			
128
			// ask for title
129
			$pkgTitle = $action->getTitle();
130
			$title = $input->getOption('title');
131
			if ($title === null && !empty($pkgTitle)) {
132
				$title = $pkgTitle;
133
			}
134
			$titleQuestion = new Question('What\'s the title for your action?', $title);
135
			$title = $this->askQuestion($titleQuestion);
136
			$input->setOption('title', $title);
137
			
138
			// ask for classname
139
			$pkgClass = $action->getClass();
140
			$classname = $input->getOption('classname');
141
			if ($classname === null) {
142
				if (!empty($pkgClass)) {
143
					$classname = $pkgClass;
144
				} else {
145
					$classname = $this->guessClassname($name);
146
				}
147
			}
148
			$classname = $this->askQuestion(new Question('Classname', $classname));
149
			$input->setOption('classname', $classname);
150
			
151
			// ask for acl
152 10
			$acls = $this->getAcl($action);
153 10
			$aclQuestion = new Question('ACL (comma separated list, with these options: guest, user, admin)', implode(', ', $acls));
154
			$acls = $this->askQuestion($aclQuestion);
155
			$input->setOption('acl', $acls);
156
		}
157
	}
158
159 9
	protected function execute(InputInterface $input, OutputInterface $output) {
160 9
		$this->preCheck();
161
162
		$name = $input->getArgument('name');
163 9
		$model = $input->getOption('model');
164 3
165 2
		// generate a skeleton action (or model, if action name belongs to a model)
166
		if ($name) {
167
			$action = $this->getAction($name);
168 6
			if ($this->modelService->isModelAction($action)) {
169 2
				$this->generateModel($this->modelService->getModelNameByAction($action));
170 2
			} else {
171
				$this->generateSkeleton($name);
172
			}
173 4
		}
174 3
175 3
		// generate an action for a specific model
176 2
		else if ($model) {
177 2
			$this->generateModel($model);
178 2
		}
179 1
180
		// generate actions for all models
181 3
		else {
182
			foreach ($this->modelService->getModels() as $model) {
183
				$modelName = $model->getOriginCommonName();
184
				$input->setOption('model', $modelName);
185 1
				$this->generateModel($modelName);
186 1
			}
187 1
		}
188
		
189
		$this->packageService->savePackage();
190 8
	}
191 8
192
	private function generateModel($modelName) {
193 5
		$this->logger->info('Generate Action from Model: ' . $modelName);
194 5
		$input = $this->io->getInput();
195 5
		$model = $this->modelService->getModel($modelName);
196 5
197 5
		// generate domain + serializer
198 1
		$this->generateDomain($model);
199 1
		$this->generateSerializer($model);
200 4
201
		// generate action type(s)
202
		$typeDump = $input->getOption('type');
203 5
		if ($typeDump !== null) {
204 5
			$types = [$typeDump];
205 5
		} else {
206 5
			$types = ['create', 'read', 'list', 'update', 'delete'];
207 5
		}
208 5
		
209 4
		foreach ($types as $type) {
210 5
			$input->setOption('acl', ['admin']);
211 5
			$input->setOption('type', $type);
212 5
			$actionName = $modelName . '-' . $type;
213
			
214 5
			if ($model->isReadOnly() && in_array($type, ['create', 'update', 'delete'])) {
215 5
				$this->logger->info(sprintf('Skip generate Action (%s), because Model (%s) is read-only', $actionName, $modelName));
216
				continue;
217 4
			}
218
			
219 4
			$action = $this->getAction($actionName);
220 3
			if (Text::create($action->getTitle())->isEmpty()) {
221
				$action->setTitle($this->getActionTitle($modelName, $type));
222 4
			}
223 4
			$action = $this->generateAction($actionName);
224 4
			
225 4
			// generate code
226 4
			$generator = GeneratorFactory::createModelActionGenerator($type, $this->service);
227
			$class = $generator->generate($action);
228
			$this->codegenService->dumpStruct($class, true);
229
		}
230
		
231
		// generate relationship actions
232
		if (!$model->isReadOnly()) {
233
			$relationships = $this->modelService->getRelationships($model);
234
				
235
			// to-one relationships
236
			foreach ($relationships->getOne() as $one) {
237 8
				$this->generateToOneRelationshipActions($one);
238 8
			}
239 8
			
240
			// to-many relationships
241
			foreach ($relationships->getMany() as $many) {
242 8
				$this->generateToManyRelationshipActions($many);
243
			}
244 8
		}
245 2
		
246 2
		$input->setOption('type', $typeDump);
247
	}
248 8
249 1
	private function getActionTitle($modelName, $type) {
250
		$name = NameUtils::dasherize($modelName);
251
		switch ($type) {
252 7
			case 'list':
253 2
				return 'List all ' . NameUtils::pluralize($name);
254 2
255
			case 'create':
256
			case 'read':
257 7
			case 'update':
258 4
			case 'delete':
259 4
				return ucfirst($type) . 's ' . (in_array($name[0], ['a', 'e', 'i', 'o', 'u']) ? 'an' : 'a') . ' ' . $name;
260
		}
261
	}
262 7
263 7
	/**
264 7
	 * Generates a domain with trait for the given model
265
	 * 
266
	 * @param Table $model
267
	 */
268
	private function generateDomain(Table $model) {
269
		$this->runCommand('generate:domain', [
270
			'--model' => $model->getOriginCommonName()
271 7
		]);
272
	}
273
	
274 7
	/**
275 7
	 * Generates a serializer for the given model
276
	 *
277 4
	 * @param Table $model
278 4
	 */
279 4
	private function generateSerializer(Table $model) {
280
		$this->runCommand('generate:serializer', [
281
			'--model' => $model->getOriginCommonName()
282
		]);
283
	}
284
	
285
	/**
286
	 * Generates an action.
287 8
	 *  
288 8
	 * @param string $actionName
289 8
	 */
290 7
	private function generateSkeleton($actionName) {
291 7
		$this->logger->info('Generate Skeleton Action: ' . $actionName);
292 7
		$input = $this->io->getInput();
293 7
		
294 8
		// generate action
295
		$action = $this->generateAction($actionName);
296
		
297 7
		// generate code
298 7
		$generator = new SkeletonActionGenerator($this->service);
299 7
		$class = $generator->generate($action);
300 7
		$this->codegenService->dumpStruct($class, $input->getOption('force'));
301 7
	}
302 2
	
303 2
	/**
304 7
	 * Generates the action for the package
305 7
	 * 
306 1
	 * @param string $actionName
307 1
	 * @throws \RuntimeException
308 1
	 * @return ActionSchema
309 1
	 */
310 1
	private function generateAction($actionName) {
311 6
		$input = $this->io->getInput();
312
		
313 7
		// get action and create it if it doesn't exist
314
		$action = $this->getAction($actionName);
315 7
		
316
		if (($title = $input->getOption('title')) !== null) {
317
			$action->setTitle($title);
318
		}
319
		
320
		if (Text::create($action->getTitle())->isEmpty()) {
321
			throw new \RuntimeException(sprintf('Cannot create action %s, because I am missing a title for it', $actionName));
322
		}
323
		
324
		if (($classname = $input->getOption('classname')) !== null) {
325
			$action->setClass($classname);
326
		}
327
		
328
		// guess classname if there is none set yet
329
		if (Text::create($action->getClass())->isEmpty()) {
330
			$action->setClass($this->guessClassname($actionName));
331 7
		}
332 7
		
333 7
		// guess title if there is none set yet
334
		if (Text::create($action->getTitle())->isEmpty()
335
				&& $this->modelService->isModelAction($action)
336 7
				&& $this->modelService->isCrudAction($action)) {
337 7
			$modelName = $this->modelService->getModelNameByAction($action);
338 7
			$type = $this->modelService->getOperationByAction($action);
339 7
			$action->setTitle($this->getActionTitle($modelName, $type));
340 7
		}
341
	
342
		// set acl
343 7
		$action->setAcl($this->getAcl($action));
344
		
345 1
		return $action;
346 1
	}
347
	
348 1
	private function guessClassname($name) {
349 1
		$namespace = NamespaceResolver::getNamespace('src/action', $this->package);
350 1
		return $namespace . '\\' . NameUtils::toStudlyCase($name) . 'Action';
351
	}
352
	
353 1
	/**
354 1
	 * 
355 1
	 * @param string $actionName
356
	 * @return ActionSchema
357
	 */
358
	private function getAction($actionName) {
359 6
		$action = $this->packageService->getAction($actionName);
360 6
		if ($action === null) {
361 6
			$action = new ActionSchema($actionName);
362 6
			$module = $this->packageService->getModule();
363 6
			$module->addAction($action);
364
		}
365
		return $action;
366
	}
367 7
	
368 5
	private function getAcl(ActionSchema $action) {
369 1
		$acls = [];
370 1
		$acl = $this->io->getInput()->getOption('acl');
371 1
		if ($acl !== null && count($acl) > 0) {
372 5
			if (!is_array($acl)) {
373 5
				$acl = [$acl];
374
			}
375 5
			foreach ($acl as $group) {
376 5
				if (strpos($group, ',') !== false) {
377
					$groups = explode(',', $group);
378 5
					foreach ($groups as $g) {
379 4
						$acls[] = trim($g);
380 4
					}
381 4
				} else {
382 5
					$acls[] = $group;
383
				}
384 2
			}
385 2
			
386 2
			return $acls;
387 2
		}
388 2
		
389
		// read default from package
390
		if (!$action->getAcl()->isEmpty()) {
391 7
			return $action->getAcl()->toArray();
392 7
		}
393
394
		return $acls;
395
	}
396
	
397
	private function generateToOneRelationshipActions(Relationship $relationship) {
398
		$model = $relationship->getModel();
399
		$foreign = $relationship->getForeign();
400
		$module = $this->package->getKeeko()->getModule();
401
		$fkModelName = $foreign->getPhpName();
402
		$actionNamePrefix = sprintf('%s-to-%s-relationship', $model->getOriginCommonName(), $foreign->getOriginCommonName());
403
	
404
		$generators = [
405
			'read' => new ToOneRelationshipReadActionGenerator($this->service),
406
			'update' => new ToOneRelationshipUpdateActionGenerator($this->service)
407
		];
408
		$titles = [
409
			'read' => 'Reads the relationship of {model} to {foreign}',
410
			'update' => 'Updates the relationship of {model} to {foreign}'
411
		];
412
	
413
		foreach (array_keys($generators) as $type) {
414
			// generate fqcn
415
			$className = sprintf('%s%s%sAction', $model->getPhpName(), $fkModelName, ucfirst($type));
416
			$fqcn = $this->packageService->getNamespace() . '\\action\\' . $className;
417
418
			// generate action
419
			$action = new ActionSchema($actionNamePrefix . '-' . $type);
420
			$action->addAcl('admin');
421
			$action->setClass($fqcn);
422
			$action->setTitle(str_replace(
423
				['{model}', '{foreign}'],
424
				[$model->getOriginCommonName(), $foreign->getoriginCommonName()],
425
				$titles[$type]
426
			));
427
			$module->addAction($action);
428
	
429
			// generate class
430
			$generator = $generators[$type];
431
			$class = $generator->generate($action, $relationship);
432
			$this->codegenService->dumpStruct($class, true);
433
		}
434
	}
435
	
436
	private function generateToManyRelationshipActions(ManyRelationship $relationship) {
437
		$model = $relationship->getModel();
438
		$foreign = $relationship->getForeign();
439
		$module = $this->package->getKeeko()->getModule();
440
		$fkModelName = $foreign->getPhpName();
441
		$actionNamePrefix = sprintf('%s-to-%s-relationship', $model->getOriginCommonName(), $foreign->getOriginCommonName());
442
		
443
		$generators = [
444
			'read' => new ToManyRelationshipReadActionGenerator($this->service),
445
			'update' => new ToManyRelationshipUpdateActionGenerator($this->service),
446
			'add' => new ToManyRelationshipAddActionGenerator($this->service),
447
			'remove' => new ToManyRelationshipRemoveActionGenerator($this->service)
448
		];
449
		$titles = [
450
			'read' => 'Reads the relationship of {model} to {foreign}',
451
			'update' => 'Updates the relationship of {model} to {foreign}',
452
			'add' => 'Adds {foreign} as relationship to {model}',
453
			'remove' => 'Removes {foreign} as relationship of {model}'
454
		];
455
	
456
		foreach (array_keys($generators) as $type) {
457
			// generate fqcn
458
			$className = sprintf('%s%s%sAction', $model->getPhpName(), $fkModelName, ucfirst($type));
459
			$fqcn = $this->packageService->getNamespace() . '\\action\\' . $className;
460
	
461
			// generate action
462
			$action = new ActionSchema($actionNamePrefix . '-' . $type);
463
			$action->addAcl('admin');
464
			$action->setClass($fqcn);
465
			$action->setTitle(str_replace(
466
				['{model}', '{foreign}'],
467
				[$model->getOriginCommonName(), $foreign->getoriginCommonName()],
468
				$titles[$type]
469
			));
470
			$module->addAction($action);
471
	
472
			// generate class
473
			$generator = $generators[$type];
474
			$class = $generator->generate($action, $relationship);
475
			$this->codegenService->dumpStruct($class, true);
476
		}
477
	}
478
479
}
480