Completed
Push — master ( 2fb143...d99a08 )
by Thomas
09:36
created

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