Completed
Push — master ( c7b947...966759 )
by Thomas
08:09
created

GenerateActionCommand::generateModel()   C

Complexity

Conditions 9
Paths 16

Size

Total Lines 52
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 38.5207

Importance

Changes 14
Bugs 0 Features 2
Metric Value
c 14
b 0
f 2
dl 0
loc 52
rs 6.5703
ccs 10
cts 35
cp 0.2857
cc 9
eloc 33
nc 16
nop 1
crap 38.5207

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\helpers\ActionCommandHelperTrait;
8
use keeko\tools\model\Relationship;
9
use keeko\tools\ui\ActionUI;
10
use phootwork\lang\Text;
11
use Symfony\Component\Console\Input\InputArgument;
12
use Symfony\Component\Console\Input\InputInterface;
13
use Symfony\Component\Console\Input\InputOption;
14
use Symfony\Component\Console\Output\OutputInterface;
15
16
class GenerateActionCommand extends AbstractKeekoCommand {
17
18
	use ActionCommandHelperTrait;
19
20
	protected function configure() {
21
		$this
22
			->setName('generate:action')
23
			->setDescription('Generates an action')
24
			->addArgument(
25 20
				'name',
26 20
				InputArgument::OPTIONAL,
27 20
				'The name of the action, which should be generated. Typically in the form %nomen%-%verb% (e.g. user-create)'
28 20
			)
29 20
			->addOption(
30 20
				'classname',
31 20
				'c',
32
				InputOption::VALUE_OPTIONAL,
33 20
				'The main class name (If ommited, class name will be guessed from action name)',
34 20
				null
35 20
			)
36 20
			->addOption(
37 20
				'model',
38 20
				'm',
39
				InputOption::VALUE_OPTIONAL,
40 20
				'The model for which the actions should be generated, when there is no name argument (if ommited all models will be generated)'
41 20
			)
42 20
			->addOption(
43 20
				'title',
44 20
				'',
45
				InputOption::VALUE_OPTIONAL,
46 20
				'The title for the generated option'
47 20
			)
48 20
			->addOption(
49 20
				'type',
50 20
				'',
51
				InputOption::VALUE_OPTIONAL,
52 20
				'The type of this action (list|create|read|update|delete) (if ommited template is guessed from action name)'
53 20
			)->addOption(
54 20
				'acl',
55 20
				'',
56 20
				InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
57
				'The acl\s for this action (guest, user and/or admin)'
58 20
			)
59 20
		;
60 20
		
61 20
		$this->configureGenerateOptions();
62
		
63 20
		parent::configure();
64
	}
65
66
	protected function initialize(InputInterface $input, OutputInterface $output) {
67
		parent::initialize($input, $output);
68
	}
69
70
	/**
71 1
	 * Checks whether actions can be generated at all by reading composer.json and verify
72
	 * all required information are available
73 20
	 */
74
	private function preCheck() {
75 20
		$module = $this->packageService->getModule();
76 20
		if ($module === null) {
77
			throw new \DomainException('No module definition found in composer.json - please run `keeko init`.');
78
		}
79
	}
80
	
81
	protected function interact(InputInterface $input, OutputInterface $output) {
82 10
		$this->preCheck();
83 10
		
84 10
		$ui = new ActionUI($this);
85 1
		$ui->show();
86 4
	}
87 9
88
	protected function execute(InputInterface $input, OutputInterface $output) {
89
		$this->preCheck();
90
91
		$name = $input->getArgument('name');
92
		$model = $input->getOption('model');
93
94
		// generate a skeleton action (or model, if action name belongs to a model)
95
		if ($name) {
96
// 			$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...
97
// 			if ($this->modelService->isModelAction($action)) {
98
// 				$this->generateModel($this->modelService->getModelNameByAction($action));
99
// 			} else {
100
				$this->generateSkeleton($name);
101
// 			}
102
		}
103
104
		// generate an action for a specific model
105
		else if ($model) {
106
			$this->generateModel($model);
107
		}
108
109
		// generate actions for all models
110
		else {
111
			foreach ($this->modelService->getModelNames() as $modelName) {
112
				$this->generateModel($modelName);
113
			}
114
		}
115
		
116
		$this->packageService->savePackage();
117
	}
118
119
	private function generateModel($modelName) {
120
		$this->logger->info('Generate Actions from Model: ' . $modelName);
121
		$input = $this->io->getInput();
122
		$model = $this->modelService->getModel($modelName);
123
124
		// generate action type(s)
125
		$typeDump = $input->getOption('type');
126
		if ($typeDump !== null) {
127
			$types = [$typeDump];
128
		} else {
129
			$types = ['create', 'read', 'list', 'update', 'delete'];
130
		}
131
		
132
		foreach ($types as $type) {
133
			$input->setOption('acl', ['admin']);
134
			$input->setOption('type', $type);
135
			$actionName = $modelName . '-' . $type;
136
			
137
			if ($model->isReadOnly() && in_array($type, ['create', 'update', 'delete'])) {
138
				$this->logger->info(sprintf('Skip generate Action (%s), because Model (%s) is read-only', $actionName, $modelName));
139
				continue;
140
			}
141
			
142
			$action = $this->getAction($actionName);
143
			if (Text::create($action->getTitle())->isEmpty()) {
144
				$action->setTitle($this->getActionTitle($modelName, $type));
145
			}
146
			$action = $this->generateAction($actionName);
147
			
148
			// generate code
149
			$generator = $this->factory->createModelActionGenerator($type);
150
			$class = $generator->generate($action);
151
			$this->codegenService->dumpStruct($class, true);
152 10
		}
153 10
		
154
		// generate relationship actions
155
		if (!$model->isReadOnly()) {
156
			$types = [
157
				Relationship::ONE_TO_ONE => ['read', 'update'],
158
				Relationship::ONE_TO_MANY => ['read', 'add', 'update', 'remove'],
159 9
				Relationship::MANY_TO_MANY => ['read', 'add', 'update', 'remove']
160 9
			];
161
			$relationships = $this->modelService->getRelationships($model);
162
			foreach ($relationships->getAll() as $relationship) {
163 9
				foreach ($types[$relationship->getType()] as $type) {
164 3
					$this->generateRelationshipAction($relationship, $type);
165 2
				}
166
			}
167
		}
168 6
		
169 2
		$input->setOption('type', $typeDump);
170 2
	}
171
172
	private function getActionTitle($modelName, $type) {
173 4
		$name = NameUtils::dasherize($modelName);
174 3
		switch ($type) {
175 3
			case 'list':
176 2
				return 'List all ' . NameUtils::pluralize($name);
177 2
178 2
			case 'create':
179 1
			case 'read':
180
			case 'update':
181 3
			case 'delete':
182
				return ucfirst($type) . 's ' . (in_array($name[0], ['a', 'e', 'i', 'o', 'u']) ? 'an' : 'a') . ' ' . $name;
183
		}
184
	}
185 1
	
186 1
	/**
187 1
	 * Generates an action.
188
	 *  
189
	 * @param string $actionName
190 8
	 */
191 8
	private function generateSkeleton($actionName) {
192
		$this->logger->info('Generate Skeleton Action: ' . $actionName);
193 5
		$input = $this->io->getInput();
194 5
		
195 5
		// generate action
196 5
		$action = $this->generateAction($actionName);
197 5
		
198 1
		// generate code
199 1
		$generator = new SkeletonActionGenerator($this->service);
200 4
		$class = $generator->generate($action);
201
		$this->codegenService->dumpStruct($class, $input->getOption('force'));
202
	}
203 5
	
204 5
	/**
205 5
	 * Generates the action for the package
206 5
	 * 
207 5
	 * @param string $actionName
208 5
	 * @throws \RuntimeException
209 4
	 * @return ActionSchema
210 5
	 */
211 5
	private function generateAction($actionName) {
212 5
		$input = $this->io->getInput();
213
		
214 5
		// get action and create it if it doesn't exist
215 5
		$action = $this->getAction($actionName);
216
		
217 4
		if (($title = $input->getOption('title')) !== null) {
218
			$action->setTitle($title);
219 4
		}
220 3
		
221
		if (Text::create($action->getTitle())->isEmpty()) {
222 4
			throw new \RuntimeException(sprintf('Cannot create action %s, because I am missing a title for it', $actionName));
223 4
		}
224 4
		
225 4
		if (($classname = $input->getOption('classname')) !== null) {
226 4
			$action->setClass($classname);
227
		}
228
		
229
		// guess classname if there is none set yet
230
		if (Text::create($action->getClass())->isEmpty()) {
231
			$action->setClass($this->guessClassname($actionName));
232
		}
233
		
234
		// guess title if there is none set yet
235
		if (Text::create($action->getTitle())->isEmpty()
236
				&& $this->modelService->isModelAction($action)
237 8
				&& $this->modelService->isCrudAction($action)) {
0 ignored issues
show
Bug introduced by
The method isCrudAction() does not seem to exist on object<keeko\tools\services\ModelService>.

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...
238 8
			$modelName = $this->modelService->getModelNameByAction($action);
239 8
			$type = $this->modelService->getOperationByAction($action);
0 ignored issues
show
Bug introduced by
The method getOperationByAction() does not seem to exist on object<keeko\tools\services\ModelService>.

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...
240
			$action->setTitle($this->getActionTitle($modelName, $type));
241
		}
242 8
243
		// set acl
244 8
		$action->setAcl($this->getAcl($action));
245 2
		
246 2
		return $action;
247
	}
248 8
	
249 1
	private function generateRelationshipAction(Relationship $relationship, $type) {
250
		$model = $relationship->getModel();
251
		$module = $this->package->getKeeko()->getModule();
252 7
		$relatedName = $relationship->getRelatedName();
253 2
		$relatedActionName = NameUtils::toSnakeCase($relationship->getRelatedName());
254 2
		$actionNamePrefix = sprintf('%s-to-%s-relationship', $model->getOriginCommonName(), $relatedActionName);
255
		
256
		$titles = [
257 7
			'read' => 'Reads the relationship of {model} to {related}',
258 4
			'update' => 'Updates the relationship of {model} to {related}',
259 4
			'add' => 'Adds {related} as relationship to {model}',
260
			'remove' => 'Removes {related} as relationship of {model}'
261
		];
262 7
		
263 7
		// generate fqcn
264 7
		$className = sprintf('%s%s%sAction', $model->getPhpName(), $relatedName, ucfirst($type));
265
		$fqcn = $this->packageService->getNamespace() . '\\action\\' . $className;
266
		
267
		// generate action
268
		$action = new ActionSchema($actionNamePrefix . '-' . $type);
269
		$action->addAcl('admin');
270
		$action->setClass($fqcn);
271 7
		$action->setTitle(str_replace(
272
			['{model}', '{related}'],
273
			[$model->getOriginCommonName(), $relatedActionName],
274 7
			$titles[$type]
275 7
		));
276
		$module->addAction($action);
277 4
		
278 4
		// generate class
279 4
		$generator = $this->factory->createActionRelationshipGenerator($type, $relationship);
280
		$class = $generator->generate($action, $relationship);
281
		$this->codegenService->dumpStruct($class, true);
282
	}
283
284
}
285