1
|
|
|
<?php |
2
|
|
|
namespace keeko\tools\command; |
3
|
|
|
|
4
|
|
|
use keeko\framework\schema\ActionSchema; |
5
|
|
|
use keeko\framework\utils\NameUtils; |
6
|
|
|
use keeko\tools\generator\action\SkeletonActionGenerator; |
7
|
|
|
use keeko\tools\generator\action\ToManyRelationshipAddActionGenerator; |
8
|
|
|
use keeko\tools\generator\action\ToManyRelationshipReadActionGenerator; |
9
|
|
|
use keeko\tools\generator\action\ToManyRelationshipRemoveActionGenerator; |
10
|
|
|
use keeko\tools\generator\action\ToManyRelationshipUpdateActionGenerator; |
11
|
|
|
use keeko\tools\generator\action\ToOneRelationshipReadActionGenerator; |
12
|
|
|
use keeko\tools\generator\action\ToOneRelationshipUpdateActionGenerator; |
13
|
|
|
use keeko\tools\generator\GeneratorFactory; |
14
|
|
|
use keeko\tools\helpers\ActionCommandHelperTrait; |
15
|
|
|
use keeko\tools\model\ManyRelationship; |
16
|
|
|
use keeko\tools\model\Relationship; |
17
|
|
|
use keeko\tools\ui\ActionUI; |
18
|
|
|
use phootwork\lang\Text; |
19
|
|
|
use Propel\Generator\Model\Table; |
20
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
21
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
22
|
|
|
use Symfony\Component\Console\Input\InputOption; |
23
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
24
|
|
|
|
25
|
20 |
|
class GenerateActionCommand extends AbstractKeekoCommand { |
26
|
20 |
|
|
27
|
20 |
|
use ActionCommandHelperTrait; |
28
|
20 |
|
|
29
|
20 |
|
protected function configure() { |
30
|
20 |
|
$this |
31
|
20 |
|
->setName('generate:action') |
32
|
|
|
->setDescription('Generates an action') |
33
|
20 |
|
->addArgument( |
34
|
20 |
|
'name', |
35
|
20 |
|
InputArgument::OPTIONAL, |
36
|
20 |
|
'The name of the action, which should be generated. Typically in the form %nomen%-%verb% (e.g. user-create)' |
37
|
20 |
|
) |
38
|
20 |
|
->addOption( |
39
|
|
|
'classname', |
40
|
20 |
|
'c', |
41
|
20 |
|
InputOption::VALUE_OPTIONAL, |
42
|
20 |
|
'The main class name (If ommited, class name will be guessed from action name)', |
43
|
20 |
|
null |
44
|
20 |
|
) |
45
|
|
|
->addOption( |
46
|
20 |
|
'model', |
47
|
20 |
|
'm', |
48
|
20 |
|
InputOption::VALUE_OPTIONAL, |
49
|
20 |
|
'The model for which the actions should be generated, when there is no name argument (if ommited all models will be generated)' |
50
|
20 |
|
) |
51
|
|
|
->addOption( |
52
|
20 |
|
'title', |
53
|
20 |
|
'', |
54
|
20 |
|
InputOption::VALUE_OPTIONAL, |
55
|
20 |
|
'The title for the generated option' |
56
|
20 |
|
) |
57
|
|
|
->addOption( |
58
|
20 |
|
'type', |
59
|
20 |
|
'', |
60
|
20 |
|
InputOption::VALUE_OPTIONAL, |
61
|
20 |
|
'The type of this action (list|create|read|update|delete) (if ommited template is guessed from action name)' |
62
|
|
|
)->addOption( |
63
|
20 |
|
'acl', |
64
|
|
|
'', |
65
|
|
|
InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, |
66
|
|
|
'The acl\s for this action (guest, user and/or admin)' |
67
|
|
|
) |
68
|
|
|
; |
69
|
|
|
|
70
|
|
|
$this->configureGenerateOptions(); |
71
|
1 |
|
|
72
|
|
|
parent::configure(); |
73
|
20 |
|
} |
74
|
|
|
|
75
|
20 |
|
protected function initialize(InputInterface $input, OutputInterface $output) { |
76
|
20 |
|
parent::initialize($input, $output); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Checks whether actions can be generated at all by reading composer.json and verify |
81
|
|
|
* all required information are available |
82
|
10 |
|
*/ |
83
|
10 |
|
private function preCheck() { |
84
|
10 |
|
$module = $this->packageService->getModule(); |
85
|
1 |
|
if ($module === null) { |
86
|
4 |
|
throw new \DomainException('No module definition found in composer.json - please run `keeko init`.'); |
87
|
9 |
|
} |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
protected function interact(InputInterface $input, OutputInterface $output) { |
91
|
|
|
$this->preCheck(); |
92
|
|
|
|
93
|
|
|
$ui = new ActionUI($this); |
94
|
|
|
$ui->show(); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
protected function execute(InputInterface $input, OutputInterface $output) { |
98
|
|
|
$this->preCheck(); |
99
|
|
|
|
100
|
|
|
$name = $input->getArgument('name'); |
101
|
|
|
$model = $input->getOption('model'); |
102
|
|
|
|
103
|
|
|
// generate a skeleton action (or model, if action name belongs to a model) |
104
|
|
|
if ($name) { |
105
|
|
|
// $action = $this->getAction($name); |
|
|
|
|
106
|
|
|
// if ($this->modelService->isModelAction($action)) { |
107
|
|
|
// $this->generateModel($this->modelService->getModelNameByAction($action)); |
108
|
|
|
// } else { |
109
|
|
|
$this->generateSkeleton($name); |
110
|
|
|
// } |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
// generate an action for a specific model |
114
|
|
|
else if ($model) { |
115
|
|
|
$this->generateModel($model); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
// generate actions for all models |
119
|
|
|
else { |
120
|
|
|
foreach ($this->modelService->getModels() as $model) { |
121
|
|
|
$modelName = $model->getOriginCommonName(); |
122
|
|
|
$input->setOption('model', $modelName); |
123
|
|
|
$this->generateModel($modelName); |
124
|
|
|
} |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
$this->packageService->savePackage(); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
private function generateModel($modelName) { |
131
|
|
|
$this->logger->info('Generate Action from Model: ' . $modelName); |
132
|
|
|
$input = $this->io->getInput(); |
133
|
|
|
$model = $this->modelService->getModel($modelName); |
134
|
|
|
|
135
|
|
|
// generate domain + serializer |
136
|
|
|
$this->generateDomain($model); |
137
|
|
|
$this->generateSerializer($model); |
138
|
|
|
|
139
|
|
|
// generate action type(s) |
140
|
|
|
$typeDump = $input->getOption('type'); |
141
|
|
|
if ($typeDump !== null) { |
142
|
|
|
$types = [$typeDump]; |
143
|
|
|
} else { |
144
|
|
|
$types = ['create', 'read', 'list', 'update', 'delete']; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
foreach ($types as $type) { |
148
|
|
|
$input->setOption('acl', ['admin']); |
149
|
|
|
$input->setOption('type', $type); |
150
|
|
|
$actionName = $modelName . '-' . $type; |
151
|
|
|
|
152
|
10 |
|
if ($model->isReadOnly() && in_array($type, ['create', 'update', 'delete'])) { |
153
|
10 |
|
$this->logger->info(sprintf('Skip generate Action (%s), because Model (%s) is read-only', $actionName, $modelName)); |
154
|
|
|
continue; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
$action = $this->getAction($actionName); |
158
|
|
|
if (Text::create($action->getTitle())->isEmpty()) { |
159
|
9 |
|
$action->setTitle($this->getActionTitle($modelName, $type)); |
160
|
9 |
|
} |
161
|
|
|
$action = $this->generateAction($actionName); |
162
|
|
|
|
163
|
9 |
|
// generate code |
164
|
3 |
|
$generator = GeneratorFactory::createModelActionGenerator($type, $this->service); |
165
|
2 |
|
$class = $generator->generate($action); |
166
|
|
|
$this->codegenService->dumpStruct($class, true); |
167
|
|
|
} |
168
|
6 |
|
|
169
|
2 |
|
// generate relationship actions |
170
|
2 |
|
if (!$model->isReadOnly()) { |
171
|
|
|
$relationships = $this->modelService->getRelationships($model); |
172
|
|
|
|
173
|
4 |
|
// to-one relationships |
174
|
3 |
|
foreach ($relationships->getOne() as $one) { |
175
|
3 |
|
$this->generateToOneRelationshipActions($one); |
176
|
2 |
|
} |
177
|
2 |
|
|
178
|
2 |
|
// to-many relationships |
179
|
1 |
|
foreach ($relationships->getMany() as $many) { |
180
|
|
|
$this->generateToManyRelationshipActions($many); |
181
|
3 |
|
} |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
$input->setOption('type', $typeDump); |
185
|
1 |
|
} |
186
|
1 |
|
|
187
|
1 |
|
private function getActionTitle($modelName, $type) { |
188
|
|
|
$name = NameUtils::dasherize($modelName); |
189
|
|
|
switch ($type) { |
190
|
8 |
|
case 'list': |
191
|
8 |
|
return 'List all ' . NameUtils::pluralize($name); |
192
|
|
|
|
193
|
5 |
|
case 'create': |
194
|
5 |
|
case 'read': |
195
|
5 |
|
case 'update': |
196
|
5 |
|
case 'delete': |
197
|
5 |
|
return ucfirst($type) . 's ' . (in_array($name[0], ['a', 'e', 'i', 'o', 'u']) ? 'an' : 'a') . ' ' . $name; |
198
|
1 |
|
} |
199
|
1 |
|
} |
200
|
4 |
|
|
201
|
|
|
/** |
202
|
|
|
* Generates a domain with trait for the given model |
203
|
5 |
|
* |
204
|
5 |
|
* @param Table $model |
205
|
5 |
|
*/ |
206
|
5 |
|
private function generateDomain(Table $model) { |
207
|
5 |
|
$this->runCommand('generate:domain', [ |
208
|
5 |
|
'--model' => $model->getOriginCommonName() |
209
|
4 |
|
]); |
210
|
5 |
|
} |
211
|
5 |
|
|
212
|
5 |
|
/** |
213
|
|
|
* Generates a serializer for the given model |
214
|
5 |
|
* |
215
|
5 |
|
* @param Table $model |
216
|
|
|
*/ |
217
|
4 |
|
private function generateSerializer(Table $model) { |
218
|
|
|
$this->runCommand('generate:serializer', [ |
219
|
4 |
|
'--model' => $model->getOriginCommonName() |
220
|
3 |
|
]); |
221
|
|
|
} |
222
|
4 |
|
|
223
|
4 |
|
/** |
224
|
4 |
|
* Generates an action. |
225
|
4 |
|
* |
226
|
4 |
|
* @param string $actionName |
227
|
|
|
*/ |
228
|
|
|
private function generateSkeleton($actionName) { |
229
|
|
|
$this->logger->info('Generate Skeleton Action: ' . $actionName); |
230
|
|
|
$input = $this->io->getInput(); |
231
|
|
|
|
232
|
|
|
// generate action |
233
|
|
|
$action = $this->generateAction($actionName); |
234
|
|
|
|
235
|
|
|
// generate code |
236
|
|
|
$generator = new SkeletonActionGenerator($this->service); |
237
|
8 |
|
$class = $generator->generate($action); |
238
|
8 |
|
$this->codegenService->dumpStruct($class, $input->getOption('force')); |
239
|
8 |
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
8 |
|
* Generates the action for the package |
243
|
|
|
* |
244
|
8 |
|
* @param string $actionName |
245
|
2 |
|
* @throws \RuntimeException |
246
|
2 |
|
* @return ActionSchema |
247
|
|
|
*/ |
248
|
8 |
|
private function generateAction($actionName) { |
249
|
1 |
|
$input = $this->io->getInput(); |
250
|
|
|
|
251
|
|
|
// get action and create it if it doesn't exist |
252
|
7 |
|
$action = $this->getAction($actionName); |
253
|
2 |
|
|
254
|
2 |
|
if (($title = $input->getOption('title')) !== null) { |
255
|
|
|
$action->setTitle($title); |
256
|
|
|
} |
257
|
7 |
|
|
258
|
4 |
|
if (Text::create($action->getTitle())->isEmpty()) { |
259
|
4 |
|
throw new \RuntimeException(sprintf('Cannot create action %s, because I am missing a title for it', $actionName)); |
260
|
|
|
} |
261
|
|
|
|
262
|
7 |
|
if (($classname = $input->getOption('classname')) !== null) { |
263
|
7 |
|
$action->setClass($classname); |
264
|
7 |
|
} |
265
|
|
|
|
266
|
|
|
// guess classname if there is none set yet |
267
|
|
|
if (Text::create($action->getClass())->isEmpty()) { |
268
|
|
|
$action->setClass($this->guessClassname($actionName)); |
269
|
|
|
} |
270
|
|
|
|
271
|
7 |
|
// guess title if there is none set yet |
272
|
|
|
if (Text::create($action->getTitle())->isEmpty() |
273
|
|
|
&& $this->modelService->isModelAction($action) |
274
|
7 |
|
&& $this->modelService->isCrudAction($action)) { |
275
|
7 |
|
$modelName = $this->modelService->getModelNameByAction($action); |
276
|
|
|
$type = $this->modelService->getOperationByAction($action); |
277
|
4 |
|
$action->setTitle($this->getActionTitle($modelName, $type)); |
278
|
4 |
|
} |
279
|
4 |
|
|
280
|
|
|
// set acl |
281
|
|
|
$action->setAcl($this->getAcl($action)); |
282
|
|
|
|
283
|
|
|
return $action; |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
private function generateToOneRelationshipActions(Relationship $relationship) { |
287
|
8 |
|
$model = $relationship->getModel(); |
288
|
8 |
|
$foreign = $relationship->getForeign(); |
289
|
8 |
|
$module = $this->package->getKeeko()->getModule(); |
290
|
7 |
|
$fkModelName = $foreign->getPhpName(); |
291
|
7 |
|
$actionNamePrefix = sprintf('%s-to-%s-relationship', $model->getOriginCommonName(), $foreign->getOriginCommonName()); |
292
|
7 |
|
|
293
|
7 |
|
$generators = [ |
294
|
8 |
|
'read' => new ToOneRelationshipReadActionGenerator($this->service), |
295
|
|
|
'update' => new ToOneRelationshipUpdateActionGenerator($this->service) |
296
|
|
|
]; |
297
|
7 |
|
$titles = [ |
298
|
7 |
|
'read' => 'Reads the relationship of {model} to {foreign}', |
299
|
7 |
|
'update' => 'Updates the relationship of {model} to {foreign}' |
300
|
7 |
|
]; |
301
|
7 |
|
|
302
|
2 |
|
foreach (array_keys($generators) as $type) { |
303
|
2 |
|
// generate fqcn |
304
|
7 |
|
$className = sprintf('%s%s%sAction', $model->getPhpName(), $fkModelName, ucfirst($type)); |
305
|
7 |
|
$fqcn = $this->packageService->getNamespace() . '\\action\\' . $className; |
306
|
1 |
|
|
307
|
1 |
|
// generate action |
308
|
1 |
|
$action = new ActionSchema($actionNamePrefix . '-' . $type); |
309
|
1 |
|
$action->addAcl('admin'); |
310
|
1 |
|
$action->setClass($fqcn); |
311
|
6 |
|
$action->setTitle(str_replace( |
312
|
|
|
['{model}', '{foreign}'], |
313
|
7 |
|
[$model->getOriginCommonName(), $foreign->getoriginCommonName()], |
314
|
|
|
$titles[$type] |
315
|
7 |
|
)); |
316
|
|
|
$module->addAction($action); |
317
|
|
|
|
318
|
|
|
// generate class |
319
|
|
|
$generator = $generators[$type]; |
320
|
|
|
$class = $generator->generate($action, $relationship); |
321
|
|
|
$this->codegenService->dumpStruct($class, true); |
322
|
|
|
} |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
private function generateToManyRelationshipActions(ManyRelationship $relationship) { |
326
|
|
|
$model = $relationship->getModel(); |
327
|
|
|
$foreign = $relationship->getForeign(); |
328
|
|
|
$module = $this->package->getKeeko()->getModule(); |
329
|
|
|
$fkModelName = $foreign->getPhpName(); |
330
|
|
|
$actionNamePrefix = sprintf('%s-to-%s-relationship', $model->getOriginCommonName(), $foreign->getOriginCommonName()); |
331
|
7 |
|
|
332
|
7 |
|
$generators = [ |
333
|
7 |
|
'read' => new ToManyRelationshipReadActionGenerator($this->service), |
334
|
|
|
'update' => new ToManyRelationshipUpdateActionGenerator($this->service), |
335
|
|
|
'add' => new ToManyRelationshipAddActionGenerator($this->service), |
336
|
7 |
|
'remove' => new ToManyRelationshipRemoveActionGenerator($this->service) |
337
|
7 |
|
]; |
338
|
7 |
|
$titles = [ |
339
|
7 |
|
'read' => 'Reads the relationship of {model} to {foreign}', |
340
|
7 |
|
'update' => 'Updates the relationship of {model} to {foreign}', |
341
|
|
|
'add' => 'Adds {foreign} as relationship to {model}', |
342
|
|
|
'remove' => 'Removes {foreign} as relationship of {model}' |
343
|
7 |
|
]; |
344
|
|
|
|
345
|
1 |
|
foreach (array_keys($generators) as $type) { |
346
|
1 |
|
// generate fqcn |
347
|
|
|
$className = sprintf('%s%s%sAction', $model->getPhpName(), $fkModelName, ucfirst($type)); |
348
|
1 |
|
$fqcn = $this->packageService->getNamespace() . '\\action\\' . $className; |
349
|
1 |
|
|
350
|
1 |
|
// generate action |
351
|
|
|
$action = new ActionSchema($actionNamePrefix . '-' . $type); |
352
|
|
|
$action->addAcl('admin'); |
353
|
1 |
|
$action->setClass($fqcn); |
354
|
1 |
|
$action->setTitle(str_replace( |
355
|
1 |
|
['{model}', '{foreign}'], |
356
|
|
|
[$model->getOriginCommonName(), $foreign->getoriginCommonName()], |
357
|
|
|
$titles[$type] |
358
|
|
|
)); |
359
|
6 |
|
$module->addAction($action); |
360
|
6 |
|
|
361
|
6 |
|
// generate class |
362
|
6 |
|
$generator = $generators[$type]; |
363
|
6 |
|
$class = $generator->generate($action, $relationship); |
364
|
|
|
$this->codegenService->dumpStruct($class, true); |
365
|
|
|
} |
366
|
|
|
} |
367
|
7 |
|
|
368
|
|
|
} |
369
|
|
|
|
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.