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

GenerateResponseCommand::getSerializer()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 23
ccs 0
cts 0
cp 0
rs 8.7972
cc 4
eloc 13
nc 5
nop 0
crap 20
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