Completed
Push — master ( 1c2262...88c0f4 )
by Thomas
05:39
created

GenerateActionCommand::guessClassname()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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