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

GenerateActionCommand   D

Complexity

Total Complexity 42

Size/Duplication

Total Lines 349
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 29

Test Coverage

Coverage 75.66%

Importance

Changes 25
Bugs 1 Features 2
Metric Value
wmc 42
c 25
b 1
f 2
lcom 1
cbo 29
dl 0
loc 349
rs 4.1818
ccs 171
cts 226
cp 0.7566

14 Methods

Rating   Name   Duplication   Size   Complexity  
B configure() 0 45 1
A initialize() 0 3 1
A preCheck() 0 6 2
A interact() 0 6 1
B execute() 0 32 5
B generateModel() 0 56 9
B getActionTitle() 0 13 7
A generateDomain() 0 5 1
A generateSerializer() 0 5 1
A generateSkeleton() 0 12 1
C generateAction() 0 37 8
A guessClassname() 0 4 1
B generateToOneRelationshipActions() 0 38 2
B generateToManyRelationshipActions() 0 42 2

How to fix   Complexity   

Complex Class

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

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\ActionCommandHelperTrait;
15
use keeko\tools\model\ManyRelationship;
16
use keeko\tools\model\Relationship;
17
use keeko\tools\ui\ActionUI;
18
use keeko\tools\utils\NamespaceResolver;
19
use phootwork\lang\Text;
20
use Propel\Generator\Model\Table;
21
use Symfony\Component\Console\Input\InputArgument;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Input\InputOption;
24
use Symfony\Component\Console\Output\OutputInterface;
25 20
26 20
class GenerateActionCommand extends AbstractKeekoCommand {
27 20
28 20
	use ActionCommandHelperTrait;
29 20
30 20
	protected function configure() {
31 20
		$this
32
			->setName('generate:action')
33 20
			->setDescription('Generates an action')
34 20
			->addArgument(
35 20
				'name',
36 20
				InputArgument::OPTIONAL,
37 20
				'The name of the action, which should be generated. Typically in the form %nomen%-%verb% (e.g. user-create)'
38 20
			)
39
			->addOption(
40 20
				'classname',
41 20
				'c',
42 20
				InputOption::VALUE_OPTIONAL,
43 20
				'The main class name (If ommited, class name will be guessed from action name)',
44 20
				null
45
			)
46 20
			->addOption(
47 20
				'model',
48 20
				'm',
49 20
				InputOption::VALUE_OPTIONAL,
50 20
				'The model for which the actions should be generated, when there is no name argument (if ommited all models will be generated)'
51
			)
52 20
			->addOption(
53 20
				'title',
54 20
				'',
55 20
				InputOption::VALUE_OPTIONAL,
56 20
				'The title for the generated option'
57
			)
58 20
			->addOption(
59 20
				'type',
60 20
				'',
61 20
				InputOption::VALUE_OPTIONAL,
62
				'The type of this action (list|create|read|update|delete) (if ommited template is guessed from action name)'
63 20
			)->addOption(
64
				'acl',
65
				'',
66
				InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
67
				'The acl\s for this action (guest, user and/or admin)'
68
			)
69
		;
70
		
71 1
		$this->configureGenerateOptions();
72
		
73 20
		parent::configure();
74
	}
75 20
76 20
	protected function initialize(InputInterface $input, OutputInterface $output) {
77
		parent::initialize($input, $output);
78
	}
79
80
	/**
81
	 * Checks whether actions can be generated at all by reading composer.json and verify
82 10
	 * all required information are available
83 10
	 */
84 10
	private function preCheck() {
85 1
		$module = $this->packageService->getModule();
86 4
		if ($module === null) {
87 9
			throw new \DomainException('No module definition found in composer.json - please run `keeko init`.');
88
		}
89
	}
90
	
91
	protected function interact(InputInterface $input, OutputInterface $output) {
92
		$this->preCheck();
93
		
94
		$ui = new ActionUI($this);
95
		$ui->show();
96
	}
97
98
	protected function execute(InputInterface $input, OutputInterface $output) {
99
		$this->preCheck();
100
101
		$name = $input->getArgument('name');
102
		$model = $input->getOption('model');
103
104
		// generate a skeleton action (or model, if action name belongs to a model)
105
		if ($name) {
106
			$action = $this->getAction($name);
107
			if ($this->modelService->isModelAction($action)) {
108
				$this->generateModel($this->modelService->getModelNameByAction($action));
109
			} else {
110
				$this->generateSkeleton($name);
111
			}
112
		}
113
114
		// generate an action for a specific model
115
		else if ($model) {
116
			$this->generateModel($model);
117
		}
118
119
		// generate actions for all models
120
		else {
121
			foreach ($this->modelService->getModels() as $model) {
122
				$modelName = $model->getOriginCommonName();
123
				$input->setOption('model', $modelName);
124
				$this->generateModel($modelName);
125
			}
126
		}
127
		
128
		$this->packageService->savePackage();
129
	}
130
131
	private function generateModel($modelName) {
132
		$this->logger->info('Generate Action from Model: ' . $modelName);
133
		$input = $this->io->getInput();
134
		$model = $this->modelService->getModel($modelName);
135
136
		// generate domain + serializer
137
		$this->generateDomain($model);
138
		$this->generateSerializer($model);
139
140
		// generate action type(s)
141
		$typeDump = $input->getOption('type');
142
		if ($typeDump !== null) {
143
			$types = [$typeDump];
144
		} else {
145
			$types = ['create', 'read', 'list', 'update', 'delete'];
146
		}
147
		
148
		foreach ($types as $type) {
149
			$input->setOption('acl', ['admin']);
150
			$input->setOption('type', $type);
151
			$actionName = $modelName . '-' . $type;
152 10
			
153 10
			if ($model->isReadOnly() && in_array($type, ['create', 'update', 'delete'])) {
154
				$this->logger->info(sprintf('Skip generate Action (%s), because Model (%s) is read-only', $actionName, $modelName));
155
				continue;
156
			}
157
			
158
			$action = $this->getAction($actionName);
159 9
			if (Text::create($action->getTitle())->isEmpty()) {
160 9
				$action->setTitle($this->getActionTitle($modelName, $type));
161
			}
162
			$action = $this->generateAction($actionName);
163 9
			
164 3
			// generate code
165 2
			$generator = GeneratorFactory::createModelActionGenerator($type, $this->service);
166
			$class = $generator->generate($action);
167
			$this->codegenService->dumpStruct($class, true);
168 6
		}
169 2
		
170 2
		// generate relationship actions
171
		if (!$model->isReadOnly()) {
172
			$relationships = $this->modelService->getRelationships($model);
173 4
				
174 3
			// to-one relationships
175 3
			foreach ($relationships->getOne() as $one) {
176 2
				$this->generateToOneRelationshipActions($one);
177 2
			}
178 2
			
179 1
			// to-many relationships
180
			foreach ($relationships->getMany() as $many) {
181 3
				$this->generateToManyRelationshipActions($many);
182
			}
183
		}
184
		
185 1
		$input->setOption('type', $typeDump);
186 1
	}
187 1
188
	private function getActionTitle($modelName, $type) {
189
		$name = NameUtils::dasherize($modelName);
190 8
		switch ($type) {
191 8
			case 'list':
192
				return 'List all ' . NameUtils::pluralize($name);
193 5
194 5
			case 'create':
195 5
			case 'read':
196 5
			case 'update':
197 5
			case 'delete':
198 1
				return ucfirst($type) . 's ' . (in_array($name[0], ['a', 'e', 'i', 'o', 'u']) ? 'an' : 'a') . ' ' . $name;
199 1
		}
200 4
	}
201
202
	/**
203 5
	 * Generates a domain with trait for the given model
204 5
	 * 
205 5
	 * @param Table $model
206 5
	 */
207 5
	private function generateDomain(Table $model) {
208 5
		$this->runCommand('generate:domain', [
209 4
			'--model' => $model->getOriginCommonName()
210 5
		]);
211 5
	}
212 5
	
213
	/**
214 5
	 * Generates a serializer for the given model
215 5
	 *
216
	 * @param Table $model
217 4
	 */
218
	private function generateSerializer(Table $model) {
219 4
		$this->runCommand('generate:serializer', [
220 3
			'--model' => $model->getOriginCommonName()
221
		]);
222 4
	}
223 4
	
224 4
	/**
225 4
	 * Generates an action.
226 4
	 *  
227
	 * @param string $actionName
228
	 */
229
	private function generateSkeleton($actionName) {
230
		$this->logger->info('Generate Skeleton Action: ' . $actionName);
231
		$input = $this->io->getInput();
232
		
233
		// generate action
234
		$action = $this->generateAction($actionName);
235
		
236
		// generate code
237 8
		$generator = new SkeletonActionGenerator($this->service);
238 8
		$class = $generator->generate($action);
239 8
		$this->codegenService->dumpStruct($class, $input->getOption('force'));
240
	}
241
	
242 8
	/**
243
	 * Generates the action for the package
244 8
	 * 
245 2
	 * @param string $actionName
246 2
	 * @throws \RuntimeException
247
	 * @return ActionSchema
248 8
	 */
249 1
	private function generateAction($actionName) {
250
		$input = $this->io->getInput();
251
		
252 7
		// get action and create it if it doesn't exist
253 2
		$action = $this->getAction($actionName);
254 2
		
255
		if (($title = $input->getOption('title')) !== null) {
256
			$action->setTitle($title);
257 7
		}
258 4
		
259 4
		if (Text::create($action->getTitle())->isEmpty()) {
260
			throw new \RuntimeException(sprintf('Cannot create action %s, because I am missing a title for it', $actionName));
261
		}
262 7
		
263 7
		if (($classname = $input->getOption('classname')) !== null) {
264 7
			$action->setClass($classname);
265
		}
266
		
267
		// guess classname if there is none set yet
268
		if (Text::create($action->getClass())->isEmpty()) {
269
			$action->setClass($this->guessClassname($actionName));
270
		}
271 7
		
272
		// guess title if there is none set yet
273
		if (Text::create($action->getTitle())->isEmpty()
274 7
				&& $this->modelService->isModelAction($action)
275 7
				&& $this->modelService->isCrudAction($action)) {
276
			$modelName = $this->modelService->getModelNameByAction($action);
277 4
			$type = $this->modelService->getOperationByAction($action);
278 4
			$action->setTitle($this->getActionTitle($modelName, $type));
279 4
		}
280
	
281
		// set acl
282
		$action->setAcl($this->getAcl($action));
283
		
284
		return $action;
285
	}
286
	
287 8
	private function guessClassname($name) {
288 8
		$namespace = NamespaceResolver::getNamespace('src/action', $this->package);
289 8
		return $namespace . '\\' . NameUtils::toStudlyCase($name) . 'Action';
290 7
	}
291 7
	
292 7
	private function generateToOneRelationshipActions(Relationship $relationship) {
293 7
		$model = $relationship->getModel();
294 8
		$foreign = $relationship->getForeign();
295
		$module = $this->package->getKeeko()->getModule();
296
		$fkModelName = $foreign->getPhpName();
297 7
		$actionNamePrefix = sprintf('%s-to-%s-relationship', $model->getOriginCommonName(), $foreign->getOriginCommonName());
298 7
	
299 7
		$generators = [
300 7
			'read' => new ToOneRelationshipReadActionGenerator($this->service),
301 7
			'update' => new ToOneRelationshipUpdateActionGenerator($this->service)
302 2
		];
303 2
		$titles = [
304 7
			'read' => 'Reads the relationship of {model} to {foreign}',
305 7
			'update' => 'Updates the relationship of {model} to {foreign}'
306 1
		];
307 1
	
308 1
		foreach (array_keys($generators) as $type) {
309 1
			// generate fqcn
310 1
			$className = sprintf('%s%s%sAction', $model->getPhpName(), $fkModelName, ucfirst($type));
311 6
			$fqcn = $this->packageService->getNamespace() . '\\action\\' . $className;
312
313 7
			// generate action
314
			$action = new ActionSchema($actionNamePrefix . '-' . $type);
315 7
			$action->addAcl('admin');
316
			$action->setClass($fqcn);
317
			$action->setTitle(str_replace(
318
				['{model}', '{foreign}'],
319
				[$model->getOriginCommonName(), $foreign->getoriginCommonName()],
320
				$titles[$type]
321
			));
322
			$module->addAction($action);
323
	
324
			// generate class
325
			$generator = $generators[$type];
326
			$class = $generator->generate($action, $relationship);
327
			$this->codegenService->dumpStruct($class, true);
328
		}
329
	}
330
	
331 7
	private function generateToManyRelationshipActions(ManyRelationship $relationship) {
332 7
		$model = $relationship->getModel();
333 7
		$foreign = $relationship->getForeign();
334
		$module = $this->package->getKeeko()->getModule();
335
		$fkModelName = $foreign->getPhpName();
336 7
		$actionNamePrefix = sprintf('%s-to-%s-relationship', $model->getOriginCommonName(), $foreign->getOriginCommonName());
337 7
		
338 7
		$generators = [
339 7
			'read' => new ToManyRelationshipReadActionGenerator($this->service),
340 7
			'update' => new ToManyRelationshipUpdateActionGenerator($this->service),
341
			'add' => new ToManyRelationshipAddActionGenerator($this->service),
342
			'remove' => new ToManyRelationshipRemoveActionGenerator($this->service)
343 7
		];
344
		$titles = [
345 1
			'read' => 'Reads the relationship of {model} to {foreign}',
346 1
			'update' => 'Updates the relationship of {model} to {foreign}',
347
			'add' => 'Adds {foreign} as relationship to {model}',
348 1
			'remove' => 'Removes {foreign} as relationship of {model}'
349 1
		];
350 1
	
351
		foreach (array_keys($generators) as $type) {
352
			// generate fqcn
353 1
			$className = sprintf('%s%s%sAction', $model->getPhpName(), $fkModelName, ucfirst($type));
354 1
			$fqcn = $this->packageService->getNamespace() . '\\action\\' . $className;
355 1
	
356
			// generate action
357
			$action = new ActionSchema($actionNamePrefix . '-' . $type);
358
			$action->addAcl('admin');
359 6
			$action->setClass($fqcn);
360 6
			$action->setTitle(str_replace(
361 6
				['{model}', '{foreign}'],
362 6
				[$model->getOriginCommonName(), $foreign->getoriginCommonName()],
363 6
				$titles[$type]
364
			));
365
			$module->addAction($action);
366
	
367 7
			// generate class
368 5
			$generator = $generators[$type];
369 1
			$class = $generator->generate($action, $relationship);
370 1
			$this->codegenService->dumpStruct($class, true);
371 1
		}
372 5
	}
373 5
374
}
375