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

GenerateResponseCommand   D

Complexity

Total Complexity 44

Size/Duplication

Total Lines 258
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 23

Test Coverage

Coverage 74.55%

Importance

Changes 19
Bugs 1 Features 0
Metric Value
wmc 44
c 19
b 1
f 0
lcom 2
cbo 23
dl 0
loc 258
rs 4.3294
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 26 6
C generateResponder() 0 68 16
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\tools\generator\GeneratorFactory;
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\ui\ResponseUI;
14
use keeko\tools\utils\NamespaceResolver;
15
use phootwork\collection\Set;
16
use phootwork\file\File;
17
use phootwork\lang\Text;
18
use Symfony\Component\Console\Input\InputArgument;
19
use Symfony\Component\Console\Input\InputInterface;
20
use Symfony\Component\Console\Input\InputOption;
21
use Symfony\Component\Console\Output\OutputInterface;
22
23
class GenerateResponseCommand extends AbstractKeekoCommand {
24 20
25 20
	use QuestionHelperTrait;
26 20
	
27 20
	protected $traits;
28 20
	
29 20
	protected function configure() {
30 20
		$this->traits = new Set();
31
		
32 20
		$this
33 20
			->setName('generate:response')
34 20
			->setDescription('Generates code for a responder')
35 20
			->addArgument(
36 20
				'name',
37 20
				InputArgument::OPTIONAL,
38
				'The name of the action, which should be generated. Typically in the form %nomen%-%verb% (e.g. user-create)'
39 20
			)
40 20
			->addOption(
41 20
				'model',
42 20
				'm',
43 20
				InputOption::VALUE_OPTIONAL,
44 20
				'The model for which the response should be generated, when there is no name argument (if ommited all models will be generated)'
45 1
			)
46 20
			->addOption(
47
				'format',
48
				'',
49 20
				InputOption::VALUE_OPTIONAL,
50
				'The response format to create',
51 20
				'json'
52 20
			)
53
			->addOption(
54
				'template',
55
				'',
56
				InputOption::VALUE_OPTIONAL,
57
				'The template for the body method (blank or twig)',
58 7
				'blank'
59 7
			)
60 7
			->addOption(
61 1
				'serializer',
62
				'',
63 6
				InputOption::VALUE_OPTIONAL,
64
				'The serializer to be used for the json api template'
65
			)
66
		;
67
		
68
		$this->configureGenerateOptions();
69
70
		parent::configure();
71
	}
72
73
	/**
74
	 * Checks whether actions can be generated at all by reading composer.json and verify
75
	 * all required information are available
76
	 */
77
	private function preCheck() {
78
		$module = $this->packageService->getModule();
79
		if ($module === null || count($module->getActionNames()) == 0) {
80
			throw new \DomainException('No action definition found in composer.json - please run `keeko generate:action`.');
81
		}
82
	}
83
84
	protected function interact(InputInterface $input, OutputInterface $output) {
85
		$this->preCheck();
86
		
87
		$ui = new ResponseUI($this);
88
		$ui->show();
89
	}
90
91
	protected function execute(InputInterface $input, OutputInterface $output) {
92
		$this->preCheck();
93
		
94
		$name = $input->getArgument('name');
95
		$model = $input->getOption('model');
96
97
		// generate responser for a specific action
98
		if ($name) {
99
			$this->generateResponder($name);
100
		}
101
		
102
		// generate a responder for a specific model
103
		else if ($model) {
104
			$this->generateModel($model);
105 7
		}
106 7
		
107
		// generate responders for all models
108 6
		else {
109
			foreach ($this->modelService->getModels() as $model) {
110
				$this->generateModel($model->getOriginCommonName());
111 6
			}
112 5
		}
113 4
		
114
		$this->packageService->savePackage();
115
	}
116
	
117 1
	protected function generateModel($modelName) {
118
		$model = $this->modelService->getModel($modelName);
119 1
		$types = $model->isReadOnly() ? ['read', 'list'] : ['read', 'list', 'create', 'update', 'delete'];
120 1
	
121 1
		// generate responders for crud actions
122
		foreach ($types as $type) {
123
			$actionName = $modelName . '-' . $type;
124 5
	
125 5
			$this->generateResponder($actionName);
126
		}
127 6
		
128 6
		// generate responders for relationships
129
		if (!$model->isReadOnly()) {
130 6
			$types = [
131 1
				'one' => ['read', 'update'],
132
				'many' => ['read', 'add', 'update', 'remove']
133
			];
134 5
			$relationships = $this->modelService->getRelationships($model);
135 5
			foreach ($relationships->getAll() as $relationship) {
136 5
				$foreignName = $relationship->getForeign()->getOriginCommonName();
137 5
				foreach ($types[$relationship->getType()] as $type) {
138 5
					$this->generateResponder($modelName . '-to-' . $foreignName . '-relationship-' . $type);
139
				}
140 5
			}
141 5
		}
142 5
	}
143
	
144
	protected function generateResponder($actionName) {
145 5
		$this->logger->info('Generate Responder for: ' . $actionName);
146 5
		$module = $this->packageService->getModule();
147 5
		
148
		if (!$module->hasAction($actionName)) {
149
			throw new \RuntimeException(sprintf('action (%s) not found', $actionName));
150 5
		}
151 2
		
152 2
		$input = $this->io->getInput();
153
		$format = $input->getOption('format');
154
		$template = $input->getOption('template');
155 4
		
156 2
		// check if relationship response
157 2
		if (Text::create($actionName)->contains('relationship') && $format == 'json') {
158
			return $this->generateRelationshipResponder($actionName);
159
		}
160 2
161 1
		$action = $module->getAction($actionName);
162 1
		$modelName = $this->modelService->getModelNameByAction($action);
163
164
		if (!$action->hasResponse($format)) {
165 1
			$className = str_replace('action', 'responder', $action->getClass());
166 1
			$className = preg_replace('/Action$/', ucwords($format) . 'Responder', $className);
167 1
			$action->setResponse($format, $className);
168
		}
169
170 5
		// find generator
171
		$generator = null;
172 5
		$type = $this->packageService->getActionType($actionName, $modelName);
173
		$isModel = $type && $this->modelService->isModelAction($action); 
174
175 5
		// model given and format is json
176 2
		if ($isModel && $format == 'json') {
177 2
			$generator = GeneratorFactory::createModelJsonResponderGenerator($type, $this->service);
178 2
		}
179
		
180 2
		// json + dump
181 2
		else if ($format == 'json' && $template == 'api') {
182 2
			$generator = new ApiJsonResponderGenerator($this->service);
183 2
			$generator->setSerializer($this->getSerializer());
184 2
		}
185
		
186
		// blank json
187 5
		else if ($format == 'json') {
188 5
			$generator = new SkeletonJsonResponderGenerator($this->service);
189 5
		}
190
		
191
		// html + twig
192
		else if ($format == 'html' && $template == 'twig') {
193
			$generator = new TwigHtmlResponderGenerator($this->service);
194
		}
195
		
196
		// blank html as default
197
		else if ($format == 'html') {
198
			$generator = new SkeletonHtmlResponderGenerator($this->service);
199
		}
200
		
201
		// run generation, if generator was chosen
202
		if ($generator !== null) {
203
			/* @var $class PhpClass */
204
			$class = $generator->generate($action);
205
206
			// write to file
207
			$file = $this->codegenService->getFile($class);
208
			$overwrite = !$file->exists() || $input->getOption('force');
209
			$this->codegenService->dumpStruct($class, $overwrite);
210
		}
211
	}
212
213
	protected function generateRelationshipResponder($actionName) {
214
		$module = $this->packageService->getModule();
215
		$action = $module->getAction($actionName);
216
		$prefix = substr($actionName, 0, strpos($actionName, 'relationship') + 12);
217
		$readAction = $module->getAction($prefix . '-read');
218
		
219
		// get modules names
220
		$matches = [];
221
		preg_match('/([a-z_]+)-to-([a-z_]+)-relationship.*/i', $actionName, $matches);
222
		$model = $this->modelService->getModel($matches[1]);
223
		$foreign = $this->modelService->getModel($matches[2]);
224
		$relationship = $this->modelService->getRelationship($model, $foreign->getOriginCommonName());
0 ignored issues
show
Bug introduced by
It seems like $model defined by $this->modelService->getModel($matches[1]) on line 222 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...
225
226
		// response class name
227
		$responder = sprintf('%s\\responder\\%s%sJsonResponder',
228
			$this->packageService->getNamespace(),
229
			$model->getPhpName(),
230
			$foreign->getPhpName()
231
		);
232
		
233
		$many = $module->hasAction($prefix . '-read')
234
			&& $module->hasAction($prefix . '-update')
235
			&& $module->hasAction($prefix . '-add')
236
			&& $module->hasAction($prefix . '-remove')
237
		;
238
		$single = $module->hasAction($prefix . '-read')
239
			&& $module->hasAction($prefix . '-update')
240
			&& !$many
241
		;
242
		
243
		$generator = null;
244
		if ($many) {
245
			$generator = new ToManyRelationshipJsonResponderGenerator($this->service, $relationship);
246
		} else if ($single) {
247
			$generator = new ToOneRelationshipJsonResponderGenerator($this->service, $relationship);
248
		}
249
		
250
		if ($generator !== null) {
251
			$action->setResponse('json', $responder);
252
			$responder = $generator->generate($readAction);
253
			$this->codegenService->dumpStruct($responder, true);
254
		}
255
	}
256
	
257
	private function getSerializer() {
258
		$input = $this->io->getInput();
259
		$serializer = $input->getOption('serializer');
260
		
261
		if (empty($serializer)) {
262
			throw new \RuntimeException('No serializer given, please pass --serializer for template');
263
		}
264
		
265
		// check fqcn
266
		$class = PhpClass::create($serializer);
267
		if ($class->getQualifiedName() == $serializer) {
268
			$class->setQualifiedName(NamespaceResolver::getNamespace('src/serializer', $this->package) . 
269
				'\\' . $serializer);
270
		}
271
		
272
		// check serializer exists
273
		$file = new File($this->codegenService->getFilename($class));
274
		if (!$file->exists()) {
275
			$this->io->writeln(sprintf('<error>Warning:</error> Serializer <info>%s</info> does not exists, please run `keeko generate:serializer %s`', $serializer, $class->getName()));
276
		}
277
278
		return $class->getQualifiedName();
279
	}
280
}
281