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

generateToOneRelationshipAction()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 36
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 36
ccs 0
cts 0
cp 0
rs 8.8571
cc 2
eloc 24
nc 2
nop 3
crap 6
1
<?php
2
namespace keeko\tools\command;
3
4
use gossi\codegen\model\PhpClass;
5
use gossi\codegen\model\PhpMethod;
6
use gossi\codegen\model\PhpProperty;
7
use gossi\codegen\model\PhpTrait;
8
use keeko\framework\schema\ActionSchema;
9
use keeko\framework\utils\NameUtils;
10
use keeko\tools\generator\action\BlankActionGenerator;
11
use keeko\tools\generator\action\NoopActionGenerator;
12
use keeko\tools\generator\action\ToManyRelationshipAddActionGenerator;
13
use keeko\tools\generator\action\ToManyRelationshipReadActionGenerator;
14
use keeko\tools\generator\action\ToManyRelationshipRemoveActionGenerator;
15
use keeko\tools\generator\action\ToManyRelationshipUpdateActionGenerator;
16
use keeko\tools\generator\action\ToOneRelationshipReadActionGenerator;
17
use keeko\tools\generator\action\ToOneRelationshipUpdateActionGenerator;
18
use keeko\tools\generator\GeneratorFactory;
19
use keeko\tools\generator\SerializerGenerator;
20
use keeko\tools\helpers\QuestionHelperTrait;
21
use keeko\tools\utils\NamespaceResolver;
22
use phootwork\file\File;
23
use phootwork\lang\Text;
24
use Propel\Generator\Model\ForeignKey;
25 20
use Propel\Generator\Model\Table;
26 20
use Symfony\Component\Console\Input\InputArgument;
27 20
use Symfony\Component\Console\Input\InputInterface;
28 20
use Symfony\Component\Console\Input\InputOption;
29 20
use Symfony\Component\Console\Output\OutputInterface;
30 20
use Symfony\Component\Console\Question\ConfirmationQuestion;
31 20
use Symfony\Component\Console\Question\Question;
32
33 20
class GenerateActionCommand extends AbstractGenerateCommand {
34 20
35 20
	use QuestionHelperTrait;
36 20
	
37 20
	private $twig;
38 20
39
	protected function configure() {
40 20
		$this
41 20
			->setName('generate:action')
42 20
			->setDescription('Generates an action')
43 20
			->addArgument(
44 20
				'name',
45
				InputArgument::OPTIONAL,
46 20
				'The name of the action, which should be generated. Typically in the form %nomen%-%verb% (e.g. user-create)'
47 20
			)
48 20
			->addOption(
49 20
				'classname',
50 20
				'c',
51
				InputOption::VALUE_OPTIONAL,
52 20
				'The main class name (If ommited, class name will be guessed from action name)',
53 20
				null
54 20
			)
55 20
			->addOption(
56 20
				'model',
57
				'm',
58 20
				InputOption::VALUE_OPTIONAL,
59 20
				'The model for which the actions should be generated, when there is no name argument (if ommited all models will be generated)'
60 20
			)
61 20
			->addOption(
62
				'title',
63 20
				'',
64
				InputOption::VALUE_OPTIONAL,
65
				'The title for the generated option'
66
			)
67
			->addOption(
68
				'type',
69
				'',
70
				InputOption::VALUE_OPTIONAL,
71 1
				'The type of this action (list|create|read|update|delete) (if ommited template is guessed from action name)'
72
			)->addOption(
73 20
				'acl',
74
				'',
75 20
				InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
76 20
				'The acl\s for this action (guest, user and/or admin)'
77
			)
78
// 			->addOption(
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% 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...
79
// 				'schema',
80
// 				's',
81
// 				InputOption::VALUE_OPTIONAL,
82 10
// 				'Path to the database schema (if ommited, database/schema.xml is used)',
83 10
// 				null
84 10
// 			)
85 1
		;
86 4
		
87 9
		$this->configureGenerateOptions();
88
		
89
		parent::configure();
90
	}
91
92
	protected function initialize(InputInterface $input, OutputInterface $output) {
93
		parent::initialize($input, $output);
94
95
		$loader = new \Twig_Loader_Filesystem($this->service->getConfig()->getTemplateRoot() . '/actions');
96
		$this->twig = new \Twig_Environment($loader);
97
	}
98
99
	/**
100
	 * Checks whether actions can be generated at all by reading composer.json and verify
101
	 * all required information are available
102
	 */
103
	private function preCheck() {
104
		$module = $this->packageService->getModule();
105
		if ($module === null) {
106
			throw new \DomainException('No module definition found in composer.json - please run `keeko init`.');
107
		}
108
	}
109
	
110
	protected function interact(InputInterface $input, OutputInterface $output) {
111
		$this->preCheck();
112
		
113
		// check if the dialog can be skipped
114
		$name = $input->getArgument('name');
115
		$model = $input->getOption('model');
116
		
117
		if ($model !== null) {
118
			return;
119
		} else if ($name !== null) {
120
			$generateModel = false;
121
		} else {
122
			$modelQuestion = new ConfirmationQuestion('Do you want to generate an action based off a model?');
123
			$generateModel = $this->askConfirmation($modelQuestion);
124
		}
125
		
126
		// ask questions for a model
127
		if ($generateModel /*&& !($this->package->getVendor() === 'keeko' && $this->modelService->isCoreSchema())*/) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% 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...
128
129
			$schema = str_replace(getcwd(), '', $this->modelService->getSchema());
130
			$allQuestion = new ConfirmationQuestion(sprintf('For all models in the schema (%s)?', $schema));
131
			$allModels = $this->askConfirmation($allQuestion);
132
133
			if (!$allModels) {
134
				$modelQuestion = new Question('Which model');
135
				$modelQuestion->setAutocompleterValues($this->modelService->getModelNames());
136
				$model = $this->askQuestion($modelQuestion);
137
				$input->setOption('model', $model);
138
			}
139
		} else if (!$generateModel) {
140
			$action = $this->getAction($name);
141
			
142
			// ask for title
143
			$pkgTitle = $action->getTitle();
144
			$title = $input->getOption('title');
145
			if ($title === null && !empty($pkgTitle)) {
146
				$title = $pkgTitle;
147
			}
148
			$titleQuestion = new Question('What\'s the title for your action?', $title);
149
			$title = $this->askQuestion($titleQuestion);
150
			$input->setOption('title', $title);
151
			
152 10
			// ask for classname
153 10
			$pkgClass = $action->getClass();
154
			$classname = $input->getOption('classname');
155
			if ($classname === null) {
156
				if (!empty($pkgClass)) {
157
					$classname = $pkgClass;
158
				} else {
159 9
					$classname = $this->guessClassname($name);
160 9
				}
161
			}
162
			$classname = $this->askQuestion(new Question('Classname', $classname));
163 9
			$input->setOption('classname', $classname);
164 3
			
165 2
			// ask for acl
166
			$acls = $this->getAcl($action);
167
			$aclQuestion = new Question('ACL (comma separated list, with these options: guest, user, admin)', implode(', ', $acls));
168 6
			$acls = $this->askQuestion($aclQuestion);
169 2
			$input->setOption('acl', $acls);
170 2
		}
171
	}
172
173 4
	protected function execute(InputInterface $input, OutputInterface $output) {
174 3
		$this->preCheck();
175 3
		
176 2
		// 1. find out which action(s) to generate
177 2
		// 2. generate the information in the package
178 2
		// 3. generate the code for the action
179 1
		
180
		$name = $input->getArgument('name');
181 3
		$model = $input->getOption('model');
182
183
		// only a specific action
184
		if ($name) {
185 1
			$this->generateAction($name);
186 1
		}
187 1
188
		// create action(s) from a model
189
		else if ($model) {
190 8
			$this->generateModel($model);
191 8
		}
192
		
193 5
		// if this is a core-module, find the related model
194 5
// 		else /*if ($this->package->getVendor() == 'keeko' && $this->modelService->isCoreSchema()) */ {
195 5
// 			$model = $this->package->getName();
196 5
// 			if ($this->modelService->hasModel($model)) {
197 5
// 				$input->setOption('model', $model);
198 1
// 				$this->generateModel($model);
199 1
// 			} else {
200 4
// 				$this->logger->error('Tried to find model on my own, wasn\'t lucky - please provide model with the --model option');
201
// 			}
202
// 		}
203 5
204 5
		// anyway, generate all
205 5
		else {
206 5
			foreach ($this->modelService->getModels() as $model) {
207 5
				$modelName = $model->getOriginCommonName();
208 5
				$input->setOption('model', $modelName);
209 4
				$this->generateModel($modelName);
210 5
			}
211 5
		}
212 5
		
213
		$this->packageService->savePackage();
214 5
	}
215 5
216
	private function generateModel($modelName) {
217 4
		$this->logger->info('Generate Action from Model: ' . $modelName);
218
		$input = $this->io->getInput();
219 4
		$model = $this->modelService->getModel($modelName);
220 3
		$typeDump = $input->getOption('type');
221
		if ($typeDump !== null) {
222 4
			$types = [$typeDump];
223 4
		} else {
224 4
			$types = ['create', 'read', 'list', 'update', 'delete'];
225 4
		}
226 4
227
		foreach ($types as $type) {
228
			$input->setOption('acl', ['admin']);
1 ignored issue
show
Documentation introduced by
array('admin') is of type array<integer,string,{"0":"string"}>, but the function expects a string|boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
229
			$input->setOption('type', $type);
230
			$actionName = $modelName . '-' . $type;
231
			
232
			if ($model->isReadOnly() && in_array($type, ['create', 'update', 'delete'])) {
233
				$this->logger->info(sprintf('Skip generate Action (%s), because Model (%s) is read-only', $actionName, $modelName));
234
				continue;
235
			}
236
			
237 8
			$action = $this->getAction($actionName);
238 8
			if (Text::create($action->getTitle())->isEmpty()) {
239 8
				$action->setTitle($this->getActionTitle($modelName, $type));
240
			}
241
			$this->generateAction($actionName);
242 8
		}
243
		
244 8
		// prepare model for API usage
245 2
		$this->prepareModelForApi($model);
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...d::prepareModelForApi() 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 2
		
247
		// generate relationship actions
248 8
		if (!$model->isReadOnly()) {
249 1
			$relationships = $this->modelService->getRelationships($model);
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\services\Mod...ice::getRelationships() 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...
250
				
251
			// to-one relationships
252 7
			foreach ($relationships['one'] as $one) {
253 2
				$fk = $one['fk'];
254 2
				$this->generateToOneRelationshipAction($model, $fk->getForeignTable(), $fk);
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...OneRelationshipAction() 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...
255
			}
256
			
257 7
			// to-many relationships
258 4
			foreach ($relationships['many'] as $many) {
259 4
				$fk = $many['fk'];
260
				$cfk = $many['cfk'];
261
				$this->generateToManyRelationshipAction($model, $fk->getForeignTable(), $cfk->getMiddleTable());
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...anyRelationshipAction() 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...
262 7
			}
263 7
		}
264 7
		
265
		$input->setOption('type', $typeDump);
266
	}
267
268
	private function getActionTitle($modelName, $type) {
269
		$name = NameUtils::dasherize($modelName);
1 ignored issue
show
Bug introduced by
The method dasherize() cannot be called from this context as it is declared private in class keeko\framework\utils\NameUtils.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
270
		switch ($type) {
271 7
			case 'list':
272
				return 'List all ' . NameUtils::pluralize($name);
273
274 7
			case 'create':
275 7
			case 'read':
276
			case 'update':
277 4
			case 'delete':
278 4
				return ucfirst($type) . 's ' . (in_array($name[0], ['a', 'e', 'i', 'o', 'u']) ? 'an' : 'a') . ' ' . $name;
279 4
		}
280
	}
281
282
	
283
	/**
284
	 * Generates an action.
285
	 *  
286
	 * @param string $actionName
287 8
	 * @param ActionSchema $action the action node from composer.json
0 ignored issues
show
Documentation introduced by
There is no parameter named $action. Did you maybe mean $actionName?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
288 8
	 */
289 8
	private function generateAction($actionName) {
290 7
		$this->logger->info('Generate Action: ' . $actionName);
291 7
		$input = $this->io->getInput();
292 7
		
293 7
		// get action and create it if it doesn't exist
294 8
		$action = $this->getAction($actionName);
295
		
296
		if (($title = $input->getOption('title')) !== null) {
297 7
			$action->setTitle($title);
298 7
		}
299 7
300 7
		if (Text::create($action->getTitle())->isEmpty()) {
301 7
			throw new \RuntimeException(sprintf('Cannot create action %s, because I am missing a title for it', $actionName));
302 2
		}
303 2
304 7
		if (($classname = $input->getOption('classname')) !== null) {
305 7
			$action->setClass($classname);
306 1
		}
307 1
		
308 1
		// guess classname if there is none set yet
309 1
		if (Text::create($action->getClass())->isEmpty()) {
310 1
			$action->setClass($this->guessClassname($actionName));
311 6
		}
312
		
313 7
		// guess title if there is none set yet
314
		if (Text::create($action->getTitle())->isEmpty() 
315 7
				&& $this->modelService->isModelAction($action)
316
				&& $this->modelService->isCrudAction($action)) {
317
			$modelName = $this->modelService->getModelNameByAction($action);
318
			$type = $this->modelService->getOperationByAction($action);
319
			$action->setTitle($this->getActionTitle($modelName, $type));
320
		}
321
		
322
		// set acl
323
		$action->setAcl($this->getAcl($action));
324
		
325
		// generate code
326
		$this->generateCode($action);
327
	}
328
	
329
	private function guessClassname($name) {
330
		$namespace = NamespaceResolver::getNamespace('src/action', $this->package);
331 7
		return $namespace . '\\' . NameUtils::toStudlyCase($name) . 'Action';
332 7
	}
333 7
	
334
	/**
335
	 * 
336 7
	 * @param string $actionName
337 7
	 * @return ActionSchema
338 7
	 */
339 7
	private function getAction($actionName) {
340 7
		$action = $this->packageService->getAction($actionName);
341
		if ($action == null) {
342
			$action = new ActionSchema($actionName);
343 7
			$module = $this->packageService->getModule();
344
			$module->addAction($action);
345 1
		}
346 1
		return $action;
347
	}
348 1
	
349 1
	private function getAcl(ActionSchema $action) {
350 1
		$acls = [];
351
		$acl = $this->io->getInput()->getOption('acl');
352
		if ($acl !== null && count($acl) > 0) {
353 1
			if (!is_array($acl)) {
354 1
				$acl = [$acl];
355 1
			}
356
			foreach ($acl as $group) {
357
				if (strpos($group, ',') !== false) {
358
					$groups = explode(',', $group);
359 6
					foreach ($groups as $g) {
360 6
						$acls[] = trim($g);
361 6
					}
362 6
				} else {
363 6
					$acls[] = $group;
364
				}
365
			}
366
			
367 7
			return $acls;
368 5
		}
369 1
		
370 1
		// read default from package
371 1
		if (!$action->getAcl()->isEmpty()) {
372 5
			return $action->getAcl()->toArray();
373 5
		}
374
375 5
		return $acls;
376 5
	}
377
	
378 5
	/**
379 4
	 * Generates code for an action
380 4
	 * 
381 4
	 * @param ActionSchema $action
382 5
	 */
383
	private function generateCode(ActionSchema $action) {
384 2
		$input = $this->io->getInput();
385 2
		$trait = null;
0 ignored issues
show
Unused Code introduced by
$trait 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...
386 2
387 2
		// class
388 2
		$class = new PhpClass($action->getClass());
389
		$filename = $this->codegenService->getFilename($class);
390
		$traitNs = $class->getNamespace() . '\\base';
391 7
		$traitName = $class->getName() . 'Trait';
392 7
		$overwrite = false;
393
		
394
		// load from file, when class exists
395
		if (file_exists($filename)) {
396
			// load trait
397
			$trait = new PhpTrait($traitNs . '\\' . $traitName);
398
			$traitFile = new File($this->codegenService->getFilename($trait));
399
400
			if ($traitFile->exists()) {
401
				$trait = PhpTrait::fromFile($traitFile->getPathname());
0 ignored issues
show
Unused Code introduced by
$trait 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...
402
			}
403
		
404
			// load class
405
			$class = PhpClass::fromFile($filename);
406
		}
407
		
408
		// anyway seed class information
409
		else {
410
			$overwrite = true;
411
			$class->setParentClassName('AbstractAction');
412
			$class->setDescription($action->getTitle());
413
			$class->setLongDescription($action->getDescription());
414
			$this->codegenService->addAuthors($class, $this->package);
415
		}
416
		
417
		// create base trait
418
		$modelName = $input->getOption('model');
419
		if ($modelName !== null) {
420
			$type = $this->packageService->getActionType($action->getName(), $modelName);
421
			$generator = GeneratorFactory::createActionTraitGenerator($type, $this->service);
422
			$trait = $generator->generate($traitNs . '\\' . $traitName, $action);
423
424
			$this->codegenService->addAuthors($trait, $this->package);
425
			$this->codegenService->dumpStruct($trait, true);
426
			
427
			if (!$class->hasTrait($trait)) {
428
				$class->addTrait($trait);
429
				$overwrite = true;
430
			}
431
		}
432
		
433
		// create class generator
434
		if ($modelName === null && !$class->hasMethod('run')) {
435
			$overwrite = true;
436
			$generator = new BlankActionGenerator($this->service);
437
		} else {
438
			$generator = new NoopActionGenerator($this->service);
439
		}
440
441
		$class = $generator->generate($class);
442
		$overwrite = $overwrite || $input->getOption('force');
443
444
		$this->codegenService->dumpStruct($class, $overwrite);
445
	}
446
	
447
	private function prepareModelForApi(Table $model) {
448
		$class = new PhpClass(str_replace('\\\\', '\\', $model->getNamespace() . '\\' . $model->getPhpName()));
449
		$file = new File($this->codegenService->getFilename($class));
450
			
451
		if ($file->exists()) {
452
			// generate serializer
453
			$generator = new SerializerGenerator($this->service);
454
			$serializer = $generator->generate($model);
455
			$this->codegenService->dumpStruct($serializer, true);
456
			
457
			// add serializer + APIModelInterface
458
			$class = PhpClass::fromFile($this->codegenService->getFilename($class));
459
			if (!$class->hasInterface('ApiModelInterface')) {
460
				$class
461
					->addUseStatement($serializer->getQualifiedName())
462
					->addUseStatement('keeko\\framework\\model\\ApiModelInterface')
463
					->addInterface('ApiModelInterface')
464
					->setProperty(PhpProperty::create('serializer')
465
						->setStatic(true)
466
						->setVisibility('private')
467
					)
468
					->setMethod(PhpMethod::create('getSerializer')
469
						->setStatic(true)
470
						->setBody($this->twig->render('get-serializer.twig', [
471
							'class' => $class->getName()
472
						]))
473
					)
474
				;
475
		
476
				$this->codegenService->dumpStruct($class, true);
477
			}
478
		}
479
	}
480
	
481
	private function generateToOneRelationshipAction(Table $model, Table $foreign, ForeignKey $fk) {
482
		$module = $this->package->getKeeko()->getModule();
483
		$fkModelName = $foreign->getPhpName();
484
		$actionNamePrefix = sprintf('%s-to-%s-relationship', $model->getOriginCommonName(), $foreign->getOriginCommonName());
485
	
486
		$generators = [
487
			'read' => new ToOneRelationshipReadActionGenerator($this->service),
488
			'update' => new ToOneRelationshipUpdateActionGenerator($this->service)
489
		];
490
		$titles = [
491
			'read' => 'Reads the relationship of {model} to {foreign}',
492
			'update' => 'Updates the relationship of {model} to {foreign}'
493
		];
494
	
495
		foreach (array_keys($generators) as $type) {
496
			// generate fqcn
497
			$className = sprintf('%s%s%sAction', $model->getPhpName(), $fkModelName, ucfirst($type));
498
			$fqcn = $this->modelService->getRootNamespace() . '\\action\\' . $className;
499
	
500
			// generate action
501
			$action = new ActionSchema($actionNamePrefix . '-' . $type);
502
			$action->addAcl('admin');
503
			$action->setClass($fqcn);
504
			$action->setTitle(str_replace(
505
				['{model}', '{foreign}'],
506
				[$model->getOriginCommonName(), $foreign->getoriginCommonName()],
507
				$titles[$type])
508
			);
509
			$module->addAction($action);
510
	
511
			// generate class
512
			$generator = $generators[$type];
513
			$class = $generator->generate(new PhpClass($fqcn), $model, $foreign, $fk);
514
			$this->codegenService->dumpStruct($class, true);
515
		}
516
	}
517
	
518
	private function generateToManyRelationshipAction(Table $model, Table $foreign, Table $middle) {
519
		$module = $this->package->getKeeko()->getModule();
520
		$fkModelName = $foreign->getPhpName();
521
		$actionNamePrefix = sprintf('%s-to-%s-relationship', $model->getOriginCommonName(), $foreign->getOriginCommonName());
522
		
523
		$generators = [
524
			'read' => new ToManyRelationshipReadActionGenerator($this->service),
525
			'update' => new ToManyRelationshipUpdateActionGenerator($this->service),
526
			'add' => new ToManyRelationshipAddActionGenerator($this->service),
527
			'remove' => new ToManyRelationshipRemoveActionGenerator($this->service)
528
		];
529
		$titles = [
530
			'read' => 'Reads the relationship of {model} to {foreign}',
531
			'update' => 'Updates the relationship of {model} to {foreign}',
532
			'add' => 'Adds {foreign} as relationship to {model}',
533
			'remove' => 'Removes {foreign} as relationship of {model}'
534
		];
535
	
536
		foreach (array_keys($generators) as $type) {
537
			// generate fqcn
538
			$className = sprintf('%s%s%sAction', $model->getPhpName(), $fkModelName, ucfirst($type));
539
			$fqcn = $this->modelService->getRootNamespace() . '\\action\\' . $className;
540
	
541
			// generate action
542
			$action = new ActionSchema($actionNamePrefix . '-' . $type);
543
			$action->addAcl('admin');
544
			$action->setClass($fqcn);
545
			$action->setTitle(str_replace(
546
				['{model}', '{foreign}'],
547
				[$model->getOriginCommonName(), $foreign->getoriginCommonName()],
548
				$titles[$type])
549
			);
550
			$module->addAction($action);
551
	
552
			// generate class
553
			$generator = $generators[$type];
554
			$class = $generator->generate(new PhpClass($fqcn), $model, $foreign, $middle);
555
			$this->codegenService->dumpStruct($class, true);
556
		}
557
	}
558
559
}
560