Completed
Push — master ( d6c5c0...118f52 )
by Thomas
04:57
created

GenerateResponderCommand   D

Complexity

Total Complexity 39

Size/Duplication

Total Lines 250
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 29

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 39
c 1
b 0
f 0
lcom 1
cbo 29
dl 0
loc 250
rs 4.2439

8 Methods

Rating   Name   Duplication   Size   Complexity  
B configure() 0 43 1
A check() 0 6 3
A interact() 0 6 1
B execute() 0 28 5
B generateModel() 0 18 5
D generateResponder() 0 82 17
B generateRelationshipResponder() 0 26 3
B getSerializer() 0 23 4
1
<?php
2
namespace keeko\tools\command;
3
4
use gossi\codegen\model\PhpClass;
5
use keeko\framework\schema\ActionSchema;
6
use keeko\framework\utils\NameUtils;
7
use keeko\tools\generator\responder\ApiJsonResponderGenerator;
8
use keeko\tools\generator\responder\SkeletonHtmlResponderGenerator;
9
use keeko\tools\generator\responder\SkeletonJsonResponderGenerator;
10
use keeko\tools\generator\responder\TwigHtmlResponderGenerator;
11
use keeko\tools\generator\Types;
12
use keeko\tools\helpers\QuestionHelperTrait;
13
use keeko\tools\model\Relationship;
14
use keeko\tools\ui\ResponseUI;
15
use keeko\tools\utils\NamespaceResolver;
16
use phootwork\collection\Set;
17
use phootwork\file\File;
18
use phootwork\lang\Text;
19
use Symfony\Component\Console\Input\InputArgument;
20
use Symfony\Component\Console\Input\InputInterface;
21
use Symfony\Component\Console\Input\InputOption;
22
use Symfony\Component\Console\Output\OutputInterface;
23
use Propel\Generator\Model\Table;
24
25
class GenerateResponderCommand extends AbstractKeekoCommand {
26
27
	use QuestionHelperTrait;
28
	
29
	protected $generated;
30
	
31
	protected function configure() {
32
		$this->generated = new Set();
33
		
34
		$this
35
			->setName('generate:responder')
36
			->setDescription('Generates code for a responder')
37
			->addArgument(
38
				'name',
39
				InputArgument::OPTIONAL,
40
				'The name of the action, which should be generated. Typically in the form %nomen%-%verb% (e.g. user-create)'
41
			)
42
			->addOption(
43
				'model',
44
				'm',
45
				InputOption::VALUE_OPTIONAL,
46
				'The model for which the response should be generated, when there is no name argument (if ommited all models will be generated)'
47
			)
48
			->addOption(
49
				'format',
50
				'',
51
				InputOption::VALUE_OPTIONAL,
52
				'The response format to create',
53
				'json'
54
			)
55
			->addOption(
56
				'template',
57
				'',
58
				InputOption::VALUE_OPTIONAL,
59
				'The template for the body method (blank or twig)',
60
				'blank'
61
			)
62
			->addOption(
63
				'serializer',
64
				'',
65
				InputOption::VALUE_OPTIONAL,
66
				'The serializer to be used for the json api template'
67
			)
68
		;
69
		
70
		$this->configureGenerateOptions();
71
72
		parent::configure();
73
	}
74
75
	/**
76
	 * Checks whether actions can be generated at all by reading composer.json and verify
77
	 * all required information are available
78
	 */
79
	private function check() {
80
		$module = $this->packageService->getModule();
81
		if ($module === null || count($module->getActionNames()) == 0) {
82
			throw new \DomainException('No action definition found in composer.json - please run `keeko generate:action`.');
83
		}
84
	}
85
86
	protected function interact(InputInterface $input, OutputInterface $output) {
87
		$this->check();
88
		
89
		$ui = new ResponseUI($this);
90
		$ui->show();
91
	}
92
93
	protected function execute(InputInterface $input, OutputInterface $output) {
94
		$this->check();
95
		
96
		$name = $input->getArgument('name');
97
		$modelName = $input->getOption('model');
98
99
		// generate responser for a specific action
100
		if ($name) {
101
			$this->generateResponder($name);
102
		}
103
		
104
		// generate a responder for a specific model
105
		else if ($modelName) {
106
			if (!$this->modelService->hasModel($modelName)) {
107
				throw new \RuntimeException(sprintf('Model (%s) does not exist.', $modelName));
108
			}
109
			$this->generateModel($this->modelService->getModel($modelName));
0 ignored issues
show
Bug introduced by
It seems like $this->modelService->getModel($modelName) can be null; however, generateModel() 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...
110
		}
111
		
112
		// generate responders for all models
113
		else {
114
			foreach ($this->modelService->getModels() as $model) {
115
				$this->generateModel($model);
116
			}
117
		}
118
		
119
		$this->packageService->savePackage();
120
	}
121
	
122
	private function generateModel(Table $model) {
123
		// generate responders for crud actions
124
		foreach (Types::getModelTypes($model) as $type) {
125
			$actionName = $this->factory->getActionNameGenerator()->generate($type, $model);
126
			$this->generateResponder($actionName);
127
		}
128
		
129
		// generate responders for relationships
130
		if (!$model->isReadOnly()) {
131
			$relationships = $this->modelService->getRelationships($model);
132
			foreach ($relationships->getAll() as $relationship) {
133
				foreach (Types::getRelationshipTypes($relationship) as $type) {
134
					$actionName = $this->factory->getActionNameGenerator()->generate($type, $relationship);
135
					$this->generateResponder($actionName);
136
				}
137
			}
138
		}
139
	}
140
	
141
	private function generateResponder($actionName) {
142
		$this->logger->info('Generate Responder for: ' . $actionName);
143
		$module = $this->packageService->getModule();
144
		
145
		if (!$module->hasAction($actionName)) {
146
			throw new \RuntimeException(sprintf('action (%s) not found', $actionName));
147
		}
148
		
149
		$input = $this->io->getInput();
150
		$force = $input->getOption('force');
151
		$format = $input->getOption('format');
152
		$template = $input->getOption('template');
153
		$action = $module->getAction($actionName);
154
		
155
		// check if relationship response
156
		if (Text::create($actionName)->contains('relationship') && $format == 'json') {
157
			return $this->generateRelationshipResponder($action);
158
		}
159
		
160
		// responder class name
161
		if (!$action->hasResponder($format)) {
0 ignored issues
show
Bug introduced by
The method hasResponder() does not seem to exist on object<keeko\framework\schema\ActionSchema>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
162
			$namespaceGenerator = $this->factory->getNamespaceGenerator();
163
			$actionNamespace = $namespaceGenerator->getActionNamespace();
164
			$className = Text::create($action->getClass())
165
				->replace($actionNamespace, '')
166
				->prepend($namespaceGenerator->getResponderNamespaceByFormat($format))
167
				->toString();
168
			$className = preg_replace('/Action$/', ucwords($format) . 'Responder', $className);
169
			$action->setResponder($format, $className);
0 ignored issues
show
Bug introduced by
The method setResponder() does not seem to exist on object<keeko\framework\schema\ActionSchema>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
170
		}
171
172
		// action information
173
		$parsed = $this->factory->getActionNameGenerator()->parseName($actionName);
174
		$type = $parsed['type'];
175
		$isModel = $type && $this->modelService->hasModel($parsed['modelName']);
176
		
177
		// find generator
178
		$generator = null;
179
180
		// model given and format is json
181
		if ($isModel && $format == 'json') {
182
			$force = true;
183
			$generator = $this->factory->createModelJsonResponderGenerator($type);
184
		}
185
		
186
		// payload
187
		else if ($template == 'payload') {
188
			$generator = $this->factory->createPayloadGenerator($format);
0 ignored issues
show
Bug introduced by
The method createPayloadGenerator() does not seem to exist on object<keeko\tools\generator\GeneratorFactory>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
189
		}
190
		
191
		// json + dump
192
		else if ($format == 'json' && $template == 'api') {
193
			$generator = new ApiJsonResponderGenerator($this->service);
194
			$generator->setSerializer($this->getSerializer());
195
		}
196
		
197
		// blank json
198
		else if ($format == 'json') {
199
			$generator = new SkeletonJsonResponderGenerator($this->service);
200
		}
201
		
202
		// html + twig
203
		else if ($format == 'html' && $template == 'twig') {
204
			$generator = new TwigHtmlResponderGenerator($this->service);
205
		}
206
		
207
		// blank html as default
208
		else if ($format == 'html') {
209
			$generator = new SkeletonHtmlResponderGenerator($this->service);
210
		}
211
		
212
		// run generation, if generator was chosen
213
		if ($generator !== null) {
214
			/* @var $class PhpClass */
215
			$class = $generator->generate($action);
216
217
			// write to file
218
			$file = $this->codegenService->getFile($class);
219
			$overwrite = !$file->exists() || $force;
220
			$this->codegenService->dumpStruct($class, $overwrite);
221
		}
222
	}
223
224
	private function generateRelationshipResponder(ActionSchema $action) {
225
		// find relationship
226
		$parsed = $this->factory->getActionNameGenerator()->parseRelationship($action->getName());
227
		$model = $this->modelService->getModel($parsed['modelName']);
228
		$relatedName = NameUtils::dasherize($parsed['relatedName']);
229
		$relationship = $this->modelService->getRelationship($model, $relatedName);
0 ignored issues
show
Bug introduced by
It seems like $model defined by $this->modelService->get...l($parsed['modelName']) on line 227 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...
230
231
		if ($relationship === null) {
232
			return;
233
		}
234
		
235
		// class name
236
		$className = $this->factory->getResponderClassNameGenerator()->generateJsonRelationshipResponder($relationship);
237
		$action->setResponder('json', $className);
0 ignored issues
show
Bug introduced by
The method setResponder() does not seem to exist on object<keeko\framework\schema\ActionSchema>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
238
239
		// return if already generated
240
		if ($this->generated->contains($className)) {
241
			return;
242
		}
243
		
244
		// generate code
245
		$generator = $this->factory->createRelationshipJsonResponderGenerator($relationship);
246
		$responder = $generator->generate($action);
247
		$this->codegenService->dumpStruct($responder, true);
248
		$this->generated->add($className);
249
	}
250
	
251
	private function getSerializer() {
252
		$input = $this->io->getInput();
253
		$serializer = $input->getOption('serializer');
254
		
255
		if (empty($serializer)) {
256
			throw new \RuntimeException('No serializer given, please pass --serializer for template');
257
		}
258
		
259
		// check fqcn
260
		$class = PhpClass::create($serializer);
261
		if ($class->getQualifiedName() == $serializer) {
262
			$class->setQualifiedName(NamespaceResolver::getNamespace('src/serializer', $this->package) . 
263
				'\\' . $serializer);
264
		}
265
		
266
		// check serializer exists
267
		$file = new File($this->codegenService->getFilename($class));
268
		if (!$file->exists()) {
269
			$this->io->writeln(sprintf('<error>Warning:</error> Serializer <info>%s</info> does not exists, please run `keeko generate:serializer %s`', $serializer, $class->getName()));
270
		}
271
272
		return $class->getQualifiedName();
273
	}
274
}
275