Completed
Push — master ( 66405f...98cd2e )
by Thomas
06:52
created

generateRelationshipResponse()   D

Complexity

Conditions 9
Paths 72

Size

Total Lines 42
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 42
ccs 0
cts 0
cp 0
rs 4.909
cc 9
eloc 29
nc 72
nop 1
crap 90
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\response\base\ModelResponseTraitGenerator;
7
use keeko\tools\generator\response\BlankHtmlResponseGenerator;
8
use keeko\tools\generator\response\BlankJsonResponseGenerator;
9
use keeko\tools\generator\response\DumpJsonResponseGenerator;
10
use keeko\tools\generator\response\TwigHtmlResponseGenerator;
11
use keeko\tools\helpers\QuestionHelperTrait;
12
use phootwork\collection\Set;
13
use phootwork\file\File;
14
use Symfony\Component\Console\Input\InputArgument;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Input\InputOption;
17
use Symfony\Component\Console\Output\OutputInterface;
18
use Symfony\Component\Console\Question\ConfirmationQuestion;
19
use Symfony\Component\Console\Question\Question;
20
use phootwork\lang\Text;
21
use keeko\tools\generator\response\ToManyRelationshipJsonResponseGenerator;
22
use keeko\tools\generator\response\ToOneRelationshipJsonResponseGenerator;
23
24 20
class GenerateResponseCommand extends AbstractGenerateCommand {
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 response')
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
				'format',
43 20
				'',
44 20
				InputOption::VALUE_OPTIONAL,
45 1
				'The response format to create',
46 20
				'json'
47
			)
48
			->addOption(
49 20
				'template',
50
				'',
51 20
				InputOption::VALUE_OPTIONAL,
52 20
				'The template for the body method (blank or twig)',
53
				'blank'
54
			)
55
		;
56
		
57
		$this->configureGenerateOptions();
58 7
59 7
		parent::configure();
60 7
	}
61 1
62
	/**
63 6
	 * Checks whether actions can be generated at all by reading composer.json and verify
64
	 * all required information are available
65
	 */
66
	private function preCheck() {
67
		$module = $this->packageService->getModule();
68
		if ($module === null || count($module->getActionNames()) == 0) {
69
			throw new \DomainException('No action definition found in composer.json - please run `keeko generate:action`.');
70
		}
71
	}
72
73
	protected function interact(InputInterface $input, OutputInterface $output) {
74
		$this->preCheck();
75
		
76
		// check if the dialog can be skipped
77
		$name = $input->getArgument('name');
78
		$specificAction = false;
79
		
80
		if ($name === null) {
81
			$specificQuestion = new ConfirmationQuestion('Do you want to generate a response for a specific action?');
82
			$specificAction = $this->askConfirmation($specificQuestion);
83
		}
84
		
85
		// ask which action
86
		if ($specificAction) {
87
			$names = [];
88
			$module = $this->packageService->getModule();
89
			foreach ($module->getActionNames() as $name) {
90
				$names[] = $name;
91
			}
92
			
93
			$actionQuestion = new Question('Which action');
94
			$actionQuestion->setAutocompleterValues($names);
95
			$name = $this->askQuestion($actionQuestion);
96
			$input->setArgument('name', $name);
97
		} 
98
		
99
		
100
		// ask which format
101
		$formatQuestion = new Question('Which format', 'json');
102
		$formatQuestion->setAutocompleterValues(['json', 'html']);
103
		$format = $this->askQuestion($formatQuestion);
104
		$input->setOption('format', $format);
105 7
		
106 7
		// ask which template
107
		$templates = [
108 6
			'html' => ['twig', 'blank'],
109
			'json' => ['dump', 'blank']
110
		];
111 6
		
112 5
		$suggestions = isset($templates[$format]) ? $templates[$format] : [];
113 4
		$default = count($suggestions) ? $suggestions[0] : '';
114
		$templateQuestion = new Question('Which template', $default);
115
		$templateQuestion->setAutocompleterValues($suggestions);
116
		$template = $this->askQuestion($templateQuestion);
117 1
		$input->setOption('template', $template);
118
		
119 1
	}
120 1
121 1
	protected function execute(InputInterface $input, OutputInterface $output) {
122
		$this->preCheck();
123
		
124 5
		$name = $input->getArgument('name');
125 5
126
		// only a specific action
127 6
		if ($name) {
128 6
			$this->generateResponse($name);
129
		}
130 6
		
131 1
		// anyway all actions
132
		else {
133
			$actions = $this->packageService->getModule()->getActionNames();
134 5
			
135 5
			foreach ($actions as $name) {
136 5
				$this->generateResponse($name);
137 5
			}
138 5
		}
139
		
140 5
		$this->packageService->savePackage();
141 5
	}
142 5
	
143
	protected function generateResponse($actionName) {
144
		$this->logger->info('Generate Response: ' . $actionName);
145 5
		$module = $this->packageService->getModule();
146 5
		
147 5
		if (!$module->hasAction($actionName)) {
148
			throw new \RuntimeException(sprintf('action (%s) not found', $actionName));
149
		}
150 5
		
151 2
		$input = $this->io->getInput();
152 2
		$format = $input->getOption('format');
153
		$template = $input->getOption('template');
154
		
155 4
		// check if relationship response
156 2
		if (Text::create($actionName)->contains('relationship') && $format == 'json') {
157 2
			return $this->generateRelationshipResponse($actionName);
158
		}
159
160 2
		$action = $module->getAction($actionName);
161 1
		$modelName = $this->modelService->getModelNameByAction($action);
162 1
163
		if (!$action->hasResponse($format)) {
164
			$action->setResponse($format, str_replace(['Action', 'action'], [ucwords($format) . 'Response', 'response'], $action->getClass()));
165 1
		}
166 1
167 1
		// find generator
168
		$overwrite = false;
169
		$generator = null;
170 5
		$type = $this->packageService->getActionType($actionName, $modelName);
171
		$isModel = $type && $this->modelService->isModelAction($action); 
172 5
173
		// model given and format is json
174
		if ($isModel && $format == 'json') {
175 5
			$generator = GeneratorFactory::createJsonResponseGenerator($type, $this->service);
176 2
		}
177 2
		
178 2
		// json + dump
179
		else if ($format == 'json' && $template == 'dump') {
180 2
			$generator = new DumpJsonResponseGenerator($this->service);
181 2
		}
182 2
		
183 2
		// blank json
184 2
		else if ($format == 'json') {
185
			$generator = new BlankJsonResponseGenerator($this->service);
186
		}
187 5
		
188 5
		// html + twig
189 5
		else if ($format == 'html' && $template == 'twig') {
190
			$generator = new TwigHtmlResponseGenerator($this->service);
191
		}
192
		
193
		// blank html as default
194
		else if ($format == 'html') {
195
			$generator = new BlankHtmlResponseGenerator($this->service);
196
		}
197
		
198
		// run generation, if generator was chosen
199
		if ($generator !== null) {
200
			/* @var $class PhpClass */
201
			$class = $generator->generate($action);
202
			
203
			// generate json trait
0 ignored issues
show
Unused Code Comprehensibility introduced by
46% 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...
204
// 			if ($isModel && $format === 'json') {
205
// 				$generator = new ModelResponseTraitGenerator($this->service);
206
// 				$trait = $generator->generate($action);
207
				
208
// 				if (!$class->hasTrait($trait)) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% 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...
209
// 					$class->addTrait($trait);
210
// 					$overwrite = true;
211
// 				}
212
				
213
// 				if (!$this->traits->contains($trait->getName())) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% 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...
214
// 					$this->codegenService->dumpStruct($trait, true);
215
// 					$this->traits->add($trait->getName());
216
// 				}
217
// 			}
218
219
			// write to file
220
			$file = new File($this->codegenService->getFilename($class));
221
			if (!$file->exists()) {
222
				$overwrite = true;
223
			}
224
			$overwrite = $overwrite || $input->getOption('force');
225
			$this->codegenService->dumpStruct($class, $overwrite);
226
		}
227
	}
228
	
229
	protected function generateRelationshipResponse($actionName) {
230
		$module = $this->packageService->getModule();
231
		$action = $module->getAction($actionName);
232
		$prefix = substr($actionName, 0, strpos($actionName, 'relationship') + 12);
233
		$readAction = $module->getAction($prefix.'-read');
234
		
235
		// get modules names
236
		$matches = [];
237
		preg_match('/([a-z_]+)-to-([a-z_]+)-relationship.*/i', $actionName, $matches);
238
		$model = $this->modelService->getModel($matches[1]);
239
		$foreign = $this->modelService->getModel($matches[2]);
240
241
		// response class name
242
		$response = sprintf('%s\\response\\%s%sJsonResponse',
243
			$this->modelService->getRootNamespace(),
244
			$model->getPhpName(),
245
			$foreign->getPhpName()
246
		);
247
		
248
		$many = $module->hasAction($prefix . '-read')
249
			&& $module->hasAction($prefix . '-update')
250
			&& $module->hasAction($prefix . '-add')
251
			&& $module->hasAction($prefix . '-remove')
252
		;
253
		$single = $module->hasAction($prefix . '-read')
254
			&& $module->hasAction($prefix . '-update')
255
			&& !$many
256
		;
257
		
258
		$generator = null;
259
		if ($many) {
260
			$generator = new ToManyRelationshipJsonResponseGenerator($this->service, $model, $foreign);
0 ignored issues
show
Bug introduced by
It seems like $model defined by $this->modelService->getModel($matches[1]) on line 238 can be null; however, keeko\tools\generator\re...enerator::__construct() 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...
Bug introduced by
It seems like $foreign defined by $this->modelService->getModel($matches[2]) on line 239 can be null; however, keeko\tools\generator\re...enerator::__construct() 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...
261
		} else if ($single) {
262
			$generator = new ToOneRelationshipJsonResponseGenerator($this->service, $model, $foreign);
0 ignored issues
show
Bug introduced by
It seems like $model defined by $this->modelService->getModel($matches[1]) on line 238 can be null; however, keeko\tools\generator\re...enerator::__construct() 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...
Bug introduced by
It seems like $foreign defined by $this->modelService->getModel($matches[2]) on line 239 can be null; however, keeko\tools\generator\re...enerator::__construct() 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...
263
		}
264
		
265
		if ($generator !== null) {
266
			$action->setResponse('json', $response);
267
			$response = $generator->generate($readAction);
268
			$this->codegenService->dumpStruct($response, true);
269
		}
270
	}
271
272
}
273