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

GenerateResponseCommand::generateResponder()   C

Complexity

Conditions 16
Paths 74

Size

Total Lines 68
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 16

Importance

Changes 3
Bugs 1 Features 0
Metric Value
c 3
b 1
f 0
dl 0
loc 68
rs 5.7201
ccs 29
cts 29
cp 1
cc 16
eloc 35
nc 74
nop 1
crap 16

How to fix   Long Method    Complexity   

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 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