Completed
Push — master ( 17137d...17de33 )
by Thomas
08:11
created

GenerateApiCommand::execute()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 7
Bugs 0 Features 0
Metric Value
c 7
b 0
f 0
dl 0
loc 25
ccs 0
cts 15
cp 0
rs 8.8571
cc 3
eloc 14
nc 3
nop 2
crap 12
1
<?php
2
namespace keeko\tools\command;
3
4
use gossi\swagger\collections\Definitions;
5
use gossi\swagger\collections\Paths;
6
use gossi\swagger\Swagger;
7
use keeko\tools\command\AbstractGenerateCommand;
8
use keeko\tools\utils\NameUtils;
9
use phootwork\file\File;
10
use Propel\Generator\Model\Table;
11
use Symfony\Component\Console\Input\InputInterface;
12
use Symfony\Component\Console\Output\OutputInterface;
13 20
14 20
class GenerateApiCommand extends AbstractGenerateCommand {
15 20
16 20
	protected function configure() {
17
		$this
18
			->setName('generate:api')
19 20
			->setDescription('Generates the api for the module')
20 20
		;
21
		
22
		$this->configureGenerateOptions();
23
			
24
		parent::configure();
25
	}
26
	
27
	/**
28
	 * Checks whether api can be generated at all by reading composer.json and verify
29
	 * all required information are available
30
	 */
31
	private function preCheck() {
32
		$module = $this->packageService->getModule();
33
		if ($module === null) {
34
			throw new \DomainException('No module definition found in composer.json - please run `keeko init`.');
35
		}
36
	}
37
38
	protected function execute(InputInterface $input, OutputInterface $output) {
39
		$this->preCheck();
40
		$api = new File($this->project->getApiFileName());
41
		
42
		// if generation is forced, generate new API from scratch
43
		if ($input->getOption('force')) {
44
			$swagger = new Swagger();
45
		}
46
		
47
		// ... anyway reuse existing one
48
		else {
49
			if (!$api->exists()) {
50
				$api->write('{}');
51
			}
52
			
53
			$swagger = Swagger::fromFile($this->project->getApiFileName());
54
		}
55
56
		$swagger->setVersion('2.0');
57
		$this->generatePaths($swagger);
58
		$this->generateDefinitions($swagger);
59
		
60
		$this->jsonService->write($api->getPathname(), $swagger->toArray());
61
		$this->io->writeln(sprintf('API for <info>%s</info> written at <info>%s</info>', $this->package->getFullName(), $api->getPathname()));
62
	}
63
	
64
	protected function generatePaths(Swagger $swagger) {
65
		$paths = $swagger->getPaths();
66
		
67
		foreach ($this->packageService->getModule()->getActionNames() as $name) {
68
			$this->generateOperation($paths, $name);
69
		}
70
	}
71
	
72
	protected function generateOperation(Paths $paths, $actionName) {
73
		$this->logger->notice('Generating Operation for: ' . $actionName);
74
	
75
		$database = $this->modelService->getDatabase();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
76
		$action = $this->packageService->getAction($actionName);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
77
		$modelName = $this->modelService->getModelNameByAction($action);
0 ignored issues
show
Bug introduced by
It seems like $action defined by $this->packageService->getAction($actionName) on line 76 can be null; however, keeko\tools\services\Mod...:getModelNameByAction() 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...
78
		$tableName = $this->modelService->getTableName($modelName);
79
	
80
		if (!$database->hasTable($tableName)) {
81
			return $paths;
82
		}
83
	
84
		$type = $this->packageService->getActionType($actionName, $modelName);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 12 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
85
		$modelObjectName = $database->getTable($tableName)->getPhpName();
86
		$modelPluralName = NameUtils::pluralize($modelName);
87
	
88
		// find path branch
89
		switch ($type) {
90
			case 'list':
91
			case 'create':
92
				$endpoint = '/' . $modelPluralName;
93
				break;
94
	
95
			case 'read':
96
			case 'update':
97
			case 'delete':
98
				$endpoint = '/' . $modelPluralName . '/{id}';
99
				break;
100
	
101
			default:
102
				throw new \RuntimeException('type (%s) not found, can\'t continue.');
103
				break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
104
		}
105
	
106
	
107
		$path = $paths->get($endpoint);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
108
		$method = $this->getMethod($type);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
109
		$operation = $path->getOperation($method);
110
		$operation->setDescription($action->getTitle());
111
		$operation->setOperationId($action->getName());
112
		$operation->getProduces()->add('application/json');
113
	
114
		$params = $operation->getParameters();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
115
		$responses = $operation->getResponses();
116
	
117
		switch ($type) {
118
			case 'list':
119
				$ok = $responses->get('200');
120
				$ok->setDescription(sprintf('Array of %s', $modelPluralName));
121
				$ok->getSchema()->setRef('#/definitions/' . 'Paged' . NameUtils::pluralize($modelObjectName));
122
				break;
123
	
124
			case 'create':
125
				// params
126
				$body = $params->getByName('body');
127
				$body->setName('body');
128
				$body->setIn('body');
129
				$body->setDescription(sprintf('The new %s', $modelName));
130
				$body->setRequired(true);
131
				$body->getSchema()->setRef('#/definitions/Writable' . $modelObjectName);
132
	
133
				// response
134
				$ok = $responses->get('201');
135
				$ok->setDescription(sprintf('%s created', $modelName));
136
				break;
137
	
138 View Code Duplication
			case 'read':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
139
				// response
140
				$ok = $responses->get('200');
141
				$ok->setDescription(sprintf('gets the %s', $modelName));
142
				$ok->getSchema()->setRef('#/definitions/' . $modelObjectName);
143
				break;
144
	
145 View Code Duplication
			case 'update':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
146
				// response
147
				$ok = $responses->get('200');
148
				$ok->setDescription(sprintf('%s updated', $modelName));
149
				$ok->getSchema()->setRef('#/definitions/' . $modelObjectName);
150
				break;
151
	
152
			case 'delete':
153
				// response
154
				$ok = $responses->get('204');
155
				$ok->setDescription(sprintf('%s deleted', $modelName));
156
				break;
157
		}
158
	
159
		if ($type == 'read' || $type == 'update' || $type == 'delete') {
160
			// params
161
			$id = $params->getByName('id');
162
			$id->setIn('path');
163
			$id->setDescription(sprintf('The %s id', $modelName));
164
			$id->setRequired(true);
165
			$id->setType('integer');
166
	
167
			// response
168
			$invalid = $responses->get('400');
169
			$invalid->setDescription('Invalid ID supplied');
170
				
171
			$notfound = $responses->get('404');
172
			$notfound->setDescription(sprintf('No %s found', $modelName));
173
		}
174
	
175
		// response - @TODO Error model
176
	}
177
	
178
	private function getMethod($type) {
179
		$methods = [
180
			'list' => 'GET',
181
			'create' => 'POST',
182
			'read' => 'GET',
183
			'update' => 'PUT',
184
			'delete' => 'DELETE'
185
		];
186
	
187
		return $methods[$type];
188
	}
189
190
	protected function generateDefinitions(Swagger $swagger) {
191
		$definitions = $swagger->getDefinitions();
192
		
193
		// meta
194
		$this->generateMeta($definitions);
195
196
		// models
197
		$modelName = $this->modelService->getModelName();
198
		if ($modelName !== null) {
199
			$definitions = $this->generateDefinition($definitions, $modelName);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $definitions is correct as $this->generateDefinitio...efinitions, $modelName) (which targets keeko\tools\command\Gene...d::generateDefinition()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Unused Code introduced by
$definitions is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
200
		} else {
201
			foreach ($this->modelService->getModels() as $model) {
202
				$definitions = $this->generateDefinition($definitions, $model->getName());
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $definitions is correct as $this->generateDefinitio...ons, $model->getName()) (which targets keeko\tools\command\Gene...d::generateDefinition()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
It seems like $definitions defined by $this->generateDefinitio...ons, $model->getName()) on line 202 can be null; however, keeko\tools\command\Gene...d::generateDefinition() 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...
203
			}
204
		}
205
	}
206
	
207
	protected function generateMeta(Definitions $definitions) {
208
		$meta = $definitions->get('Meta');
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
209
		$props = $meta->getProperties();
210
		$names = ['total', 'first', 'next', 'previous', 'last'];
211
		
212
		foreach ($names as $name) {
213
			$props->get($name)->setType('integer');
214
		}
215
	}
216
217
	protected function generateDefinition(Definitions $definitions, $modelName) {
218
		$this->logger->notice('Generating Definition for: ' . $modelName);
219
		$model = $this->modelService->getModel($modelName);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 11 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
220
		$modelObjectName = $model->getPhpName();
221
		$modelPluralName = NameUtils::pluralize($model->getOriginCommonName());
222
		
223
		// paged model
224
		$pagedModel = 'Paged' . NameUtils::pluralize($modelObjectName);
225
		$paged = $definitions->get($pagedModel)->getProperties();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
226
		$paged->get($modelPluralName)
227
			->setType('array')
228
			->getItems()->setRef('#/definitions/' . $modelObjectName);
229
		$paged->get('meta')->setRef('#/definitions/Meta');
230
		
231
		// writable model
232
		$writable = $definitions->get('Writable' . $modelObjectName)->getProperties();
233
		$this->generateModelProperties($writable, $model, true);
1 ignored issue
show
Bug introduced by
It seems like $model defined by $this->modelService->getModel($modelName) on line 219 can be null; however, keeko\tools\command\Gene...nerateModelProperties() 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...
234
235
		// readable model
236
		$readable = $definitions->get($modelObjectName)->getProperties();
237
		$this->generateModelProperties($readable, $model, false);
1 ignored issue
show
Bug introduced by
It seems like $model defined by $this->modelService->getModel($modelName) on line 219 can be null; however, keeko\tools\command\Gene...nerateModelProperties() 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...
238
	}
239
	
240
	protected function generateModelProperties(Definitions $props, Table $model, $write = false) {
241
		$modelName = $model->getOriginCommonName();
242
		$filter = $write 
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
243
			? $this->codegenService->getCodegen()->getWriteFilter($modelName)
244
			: $this->codegenService->getCodegen()->getReadFilter($modelName);
245
		
246
		if ($write) {
247
			$filter = array_merge($filter, $this->codegenService->getComputedFields($model));
248
		}
249
		
250
		foreach ($model->getColumns() as $col) {
251
			$prop = $col->getName();
252
			
253
			if (!in_array($prop, $filter)) {
254
				$props->get($prop)->setType($col->getPhpType());
255
			}
256
		}
257
258
		return $props;
259
	}
260
261
}