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

GenerateResponseCommand   F

Complexity

Total Complexity 45

Size/Duplication

Total Lines 264
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 25

Test Coverage

Coverage 74.55%

Importance

Changes 21
Bugs 1 Features 0
Metric Value
wmc 45
c 21
b 1
f 0
lcom 2
cbo 25
dl 0
loc 264
rs 3.7614
ccs 82
cts 110
cp 0.7455

8 Methods

Rating   Name   Duplication   Size   Complexity  
B configure() 0 43 1
A preCheck() 0 6 3
A interact() 0 6 1
B execute() 0 25 4
B generateModel() 0 27 6
C generateResponder() 0 73 17
D generateRelationshipResponder() 0 43 9
B getSerializer() 0 23 4

How to fix   Complexity   

Complex Class

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

1
<?php
2
namespace keeko\tools\command;
3
4
use gossi\codegen\model\PhpClass;
5
use keeko\framework\utils\NameUtils;
6
use keeko\tools\generator\responder\ApiJsonResponderGenerator;
7
use keeko\tools\generator\responder\SkeletonHtmlResponderGenerator;
8
use keeko\tools\generator\responder\SkeletonJsonResponderGenerator;
9
use keeko\tools\generator\responder\ToManyRelationshipJsonResponderGenerator;
10
use keeko\tools\generator\responder\ToOneRelationshipJsonResponderGenerator;
11
use keeko\tools\generator\responder\TwigHtmlResponderGenerator;
12
use keeko\tools\helpers\QuestionHelperTrait;
13
use keeko\tools\model\Relationship;
14
use keeko\tools\ui\ResponseUI;
15
use keeko\tools\utils\NamespaceResolver;
16
use phootwork\collection\Set;
17
use phootwork\file\File;
18
use phootwork\lang\Text;
19
use Symfony\Component\Console\Input\InputArgument;
20
use Symfony\Component\Console\Input\InputInterface;
21
use Symfony\Component\Console\Input\InputOption;
22
use Symfony\Component\Console\Output\OutputInterface;
23
24 20
class GenerateResponseCommand extends AbstractKeekoCommand {
25 20
26 20
	use QuestionHelperTrait;
27 20
	
28 20
	protected $traits;
29 20
	
30 20
	protected function configure() {
31
		$this->traits = new Set();
32 20
		
33 20
		$this
34 20
			->setName('generate:response')
35 20
			->setDescription('Generates code for a responder')
36 20
			->addArgument(
37 20
				'name',
38
				InputArgument::OPTIONAL,
39 20
				'The name of the action, which should be generated. Typically in the form %nomen%-%verb% (e.g. user-create)'
40 20
			)
41 20
			->addOption(
42 20
				'model',
43 20
				'm',
44 20
				InputOption::VALUE_OPTIONAL,
45 1
				'The model for which the response should be generated, when there is no name argument (if ommited all models will be generated)'
46 20
			)
47
			->addOption(
48
				'format',
49 20
				'',
50
				InputOption::VALUE_OPTIONAL,
51 20
				'The response format to create',
52 20
				'json'
53
			)
54
			->addOption(
55
				'template',
56
				'',
57
				InputOption::VALUE_OPTIONAL,
58 7
				'The template for the body method (blank or twig)',
59 7
				'blank'
60 7
			)
61 1
			->addOption(
62
				'serializer',
63 6
				'',
64
				InputOption::VALUE_OPTIONAL,
65
				'The serializer to be used for the json api template'
66
			)
67
		;
68
		
69
		$this->configureGenerateOptions();
70
71
		parent::configure();
72
	}
73
74
	/**
75
	 * Checks whether actions can be generated at all by reading composer.json and verify
76
	 * all required information are available
77
	 */
78
	private function preCheck() {
79
		$module = $this->packageService->getModule();
80
		if ($module === null || count($module->getActionNames()) == 0) {
81
			throw new \DomainException('No action definition found in composer.json - please run `keeko generate:action`.');
82
		}
83
	}
84
85
	protected function interact(InputInterface $input, OutputInterface $output) {
86
		$this->preCheck();
87
		
88
		$ui = new ResponseUI($this);
89
		$ui->show();
90
	}
91
92
	protected function execute(InputInterface $input, OutputInterface $output) {
93
		$this->preCheck();
94
		
95
		$name = $input->getArgument('name');
96
		$model = $input->getOption('model');
97
98
		// generate responser for a specific action
99
		if ($name) {
100
			$this->generateResponder($name);
101
		}
102
		
103
		// generate a responder for a specific model
104
		else if ($model) {
105 7
			$this->generateModel($model);
106 7
		}
107
		
108 6
		// generate responders for all models
109
		else {
110
			foreach ($this->modelService->getModels() as $model) {
111 6
				$this->generateModel($model->getOriginCommonName());
112 5
			}
113 4
		}
114
		
115
		$this->packageService->savePackage();
116
	}
117 1
	
118
	protected function generateModel($modelName) {
119 1
		$model = $this->modelService->getModel($modelName);
120 1
		$types = $model->isReadOnly() ? ['read', 'list'] : ['read', 'list', 'create', 'update', 'delete'];
121 1
	
122
		// generate responders for crud actions
123
		foreach ($types as $type) {
124 5
			$actionName = $modelName . '-' . $type;
125 5
	
126
			$this->generateResponder($actionName);
127 6
		}
128 6
		
129
		// generate responders for relationships
130 6
		if (!$model->isReadOnly()) {
131 1
			$types = [
132
				Relationship::ONE_TO_ONE => ['read', 'update'],
133
				Relationship::ONE_TO_MANY => ['read', 'add', 'update', 'remove'],
134 5
				Relationship::MANY_TO_MANY => ['read', 'add', 'update', 'remove'] 
135 5
			];
136 5
			$relationships = $this->modelService->getRelationships($model);
137 5
			foreach ($relationships->getAll() as $relationship) {
138 5
				$relatedName = NameUtils::toSnakeCase($relationship->getRelatedTypeName());
139
				foreach ($types[$relationship->getType()] as $type) {
140 5
					$this->generateResponder($modelName . '-to-' . $relatedName . '-relationship-' . $type);
141 5
				}
142 5
			}
143
		}
144
	}
145 5
	
146 5
	protected function generateResponder($actionName) {
147 5
		$this->logger->info('Generate Responder for: ' . $actionName);
148
		$module = $this->packageService->getModule();
149
		
150 5
		if (!$module->hasAction($actionName)) {
151 2
			throw new \RuntimeException(sprintf('action (%s) not found', $actionName));
152 2
		}
153
		
154
		$input = $this->io->getInput();
155 4
		$format = $input->getOption('format');
156 2
		$template = $input->getOption('template');
157 2
		
158
		// check if relationship response
159
		if (Text::create($actionName)->contains('relationship') && $format == 'json') {
160 2
			return $this->generateRelationshipResponder($actionName);
161 1
		}
162 1
163
		$action = $module->getAction($actionName);
164
		$modelName = $this->modelService->getModelNameByAction($action);
165 1
166 1
		if (!$action->hasResponse($format)) {
167 1
			$className = str_replace('action', 'responder', $action->getClass());
168
			$className = preg_replace('/Action$/', ucwords($format) . 'Responder', $className);
169
			$action->setResponse($format, $className);
170 5
		}
171
172 5
		// find generator
173
		$generator = null;
174
		$type = $this->packageService->getActionType($actionName, $modelName);
175 5
		$isModel = $type && $this->modelService->isModelAction($action); 
176 2
177 2
		// model given and format is json
178 2
		if ($isModel && $format == 'json') {
179
			$generator = $this->factory->createModelJsonResponderGenerator($type);
180 2
		}
181 2
		
182 2
		// payload
183 2
		else if ($template == 'payload') {
184 2
			$generator = $this->factory->createPayloadGenerator($format);
185
		}
186
		
187 5
		// json + dump
188 5
		else if ($format == 'json' && $template == 'api') {
189 5
			$generator = new ApiJsonResponderGenerator($this->service);
190
			$generator->setSerializer($this->getSerializer());
191
		}
192
		
193
		// blank json
194
		else if ($format == 'json') {
195
			$generator = new SkeletonJsonResponderGenerator($this->service);
196
		}
197
		
198
		// html + twig
199
		else if ($format == 'html' && $template == 'twig') {
200
			$generator = new TwigHtmlResponderGenerator($this->service);
201
		}
202
		
203
		// blank html as default
204
		else if ($format == 'html') {
205
			$generator = new SkeletonHtmlResponderGenerator($this->service);
206
		}
207
		
208
		// run generation, if generator was chosen
209
		if ($generator !== null) {
210
			/* @var $class PhpClass */
211
			$class = $generator->generate($action);
212
213
			// write to file
214
			$file = $this->codegenService->getFile($class);
215
			$overwrite = !$file->exists() || $input->getOption('force');
216
			$this->codegenService->dumpStruct($class, $overwrite);
217
		}
218
	}
219
220
	protected function generateRelationshipResponder($actionName) {
221
		$module = $this->packageService->getModule();
222
		$action = $module->getAction($actionName);
223
		$prefix = substr($actionName, 0, strpos($actionName, 'relationship') + 12);
224
		$readAction = $module->getAction($prefix . '-read');
225
		
226
		// get modules names
227
		$matches = [];
228
		preg_match('/([a-z_]+)-to-([a-z_]+)-relationship.*/i', $actionName, $matches);
229
		$model = $this->modelService->getModel($matches[1]);
230
		$relatedName = NameUtils::dasherize($matches[2]);
231
		$relationship = $this->modelService->getRelationship($model, $relatedName);
0 ignored issues
show
Bug introduced by
It seems like $model defined by $this->modelService->getModel($matches[1]) on line 229 can be null; however, keeko\tools\services\Mod...vice::getRelationship() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
232
233
		// response class name
234
		$responder = sprintf('%s\\responder\\%s%sJsonResponder',
235
			$this->packageService->getNamespace(),
236
			$model->getPhpName(),
237
			$relationship->getRelatedName()
238
		);
239
		
240
		$many = $module->hasAction($prefix . '-read')
241
			&& $module->hasAction($prefix . '-update')
242
			&& $module->hasAction($prefix . '-add')
243
			&& $module->hasAction($prefix . '-remove')
244
		;
245
		$single = $module->hasAction($prefix . '-read')
246
			&& $module->hasAction($prefix . '-update')
247
			&& !$many
248
		;
249
		
250
		$generator = null;
251
		if ($many) {
252
			$generator = new ToManyRelationshipJsonResponderGenerator($this->service, $relationship);
253
		} else if ($single) {
254
			$generator = new ToOneRelationshipJsonResponderGenerator($this->service, $relationship);
255
		}
256
		
257
		if ($generator !== null) {
258
			$action->setResponse('json', $responder);
259
			$responder = $generator->generate($readAction);
260
			$this->codegenService->dumpStruct($responder, true);
261
		}
262
	}
263
	
264
	private function getSerializer() {
265
		$input = $this->io->getInput();
266
		$serializer = $input->getOption('serializer');
267
		
268
		if (empty($serializer)) {
269
			throw new \RuntimeException('No serializer given, please pass --serializer for template');
270
		}
271
		
272
		// check fqcn
273
		$class = PhpClass::create($serializer);
274
		if ($class->getQualifiedName() == $serializer) {
275
			$class->setQualifiedName(NamespaceResolver::getNamespace('src/serializer', $this->package) . 
276
				'\\' . $serializer);
277
		}
278
		
279
		// check serializer exists
280
		$file = new File($this->codegenService->getFilename($class));
281
		if (!$file->exists()) {
282
			$this->io->writeln(sprintf('<error>Warning:</error> Serializer <info>%s</info> does not exists, please run `keeko generate:serializer %s`', $serializer, $class->getName()));
283
		}
284
285
		return $class->getQualifiedName();
286
	}
287
}
288