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\helpers\ActionCommandHelperTrait; |
8
|
|
|
use keeko\tools\model\Relationship; |
9
|
|
|
use keeko\tools\ui\ActionUI; |
10
|
|
|
use phootwork\lang\Text; |
11
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
12
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
13
|
|
|
use Symfony\Component\Console\Input\InputOption; |
14
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
15
|
|
|
|
16
|
|
|
class GenerateActionCommand extends AbstractKeekoCommand { |
17
|
|
|
|
18
|
|
|
use ActionCommandHelperTrait; |
19
|
|
|
|
20
|
|
|
protected function configure() { |
21
|
|
|
$this |
22
|
|
|
->setName('generate:action') |
23
|
|
|
->setDescription('Generates an action') |
24
|
|
|
->addArgument( |
25
|
20 |
|
'name', |
26
|
20 |
|
InputArgument::OPTIONAL, |
27
|
20 |
|
'The name of the action, which should be generated. Typically in the form %nomen%-%verb% (e.g. user-create)' |
28
|
20 |
|
) |
29
|
20 |
|
->addOption( |
30
|
20 |
|
'classname', |
31
|
20 |
|
'c', |
32
|
|
|
InputOption::VALUE_OPTIONAL, |
33
|
20 |
|
'The main class name (If ommited, class name will be guessed from action name)', |
34
|
20 |
|
null |
35
|
20 |
|
) |
36
|
20 |
|
->addOption( |
37
|
20 |
|
'model', |
38
|
20 |
|
'm', |
39
|
|
|
InputOption::VALUE_OPTIONAL, |
40
|
20 |
|
'The model for which the actions should be generated, when there is no name argument (if ommited all models will be generated)' |
41
|
20 |
|
) |
42
|
20 |
|
->addOption( |
43
|
20 |
|
'title', |
44
|
20 |
|
'', |
45
|
|
|
InputOption::VALUE_OPTIONAL, |
46
|
20 |
|
'The title for the generated option' |
47
|
20 |
|
) |
48
|
20 |
|
->addOption( |
49
|
20 |
|
'type', |
50
|
20 |
|
'', |
51
|
|
|
InputOption::VALUE_OPTIONAL, |
52
|
20 |
|
'The type of this action (list|create|read|update|delete) (if ommited template is guessed from action name)' |
53
|
20 |
|
)->addOption( |
54
|
20 |
|
'acl', |
55
|
20 |
|
'', |
56
|
20 |
|
InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, |
57
|
|
|
'The acl\s for this action (guest, user and/or admin)' |
58
|
20 |
|
) |
59
|
20 |
|
; |
60
|
20 |
|
|
61
|
20 |
|
$this->configureGenerateOptions(); |
62
|
|
|
|
63
|
20 |
|
parent::configure(); |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
protected function initialize(InputInterface $input, OutputInterface $output) { |
67
|
|
|
parent::initialize($input, $output); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
1 |
|
* Checks whether actions can be generated at all by reading composer.json and verify |
72
|
|
|
* all required information are available |
73
|
20 |
|
*/ |
74
|
|
|
private function preCheck() { |
75
|
20 |
|
$module = $this->packageService->getModule(); |
76
|
20 |
|
if ($module === null) { |
77
|
|
|
throw new \DomainException('No module definition found in composer.json - please run `keeko init`.'); |
78
|
|
|
} |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
protected function interact(InputInterface $input, OutputInterface $output) { |
82
|
10 |
|
$this->preCheck(); |
83
|
10 |
|
|
84
|
10 |
|
$ui = new ActionUI($this); |
85
|
1 |
|
$ui->show(); |
86
|
4 |
|
} |
87
|
9 |
|
|
88
|
|
|
protected function execute(InputInterface $input, OutputInterface $output) { |
89
|
|
|
$this->preCheck(); |
90
|
|
|
|
91
|
|
|
$name = $input->getArgument('name'); |
92
|
|
|
$model = $input->getOption('model'); |
93
|
|
|
|
94
|
|
|
// generate a skeleton action (or model, if action name belongs to a model) |
95
|
|
|
if ($name) { |
96
|
|
|
// $action = $this->getAction($name); |
|
|
|
|
97
|
|
|
// if ($this->modelService->isModelAction($action)) { |
98
|
|
|
// $this->generateModel($this->modelService->getModelNameByAction($action)); |
99
|
|
|
// } else { |
100
|
|
|
$this->generateSkeleton($name); |
101
|
|
|
// } |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
// generate an action for a specific model |
105
|
|
|
else if ($model) { |
106
|
|
|
$this->generateModel($model); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
// generate actions for all models |
110
|
|
|
else { |
111
|
|
|
foreach ($this->modelService->getModelNames() as $modelName) { |
112
|
|
|
$this->generateModel($modelName); |
113
|
|
|
} |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
$this->packageService->savePackage(); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
private function generateModel($modelName) { |
120
|
|
|
$this->logger->info('Generate Actions from Model: ' . $modelName); |
121
|
|
|
$input = $this->io->getInput(); |
122
|
|
|
$model = $this->modelService->getModel($modelName); |
123
|
|
|
|
124
|
|
|
// generate action type(s) |
125
|
|
|
$typeDump = $input->getOption('type'); |
126
|
|
|
if ($typeDump !== null) { |
127
|
|
|
$types = [$typeDump]; |
128
|
|
|
} else { |
129
|
|
|
$types = ['create', 'read', 'list', 'update', 'delete']; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
foreach ($types as $type) { |
133
|
|
|
$input->setOption('acl', ['admin']); |
134
|
|
|
$input->setOption('type', $type); |
135
|
|
|
$actionName = $modelName . '-' . $type; |
136
|
|
|
|
137
|
|
|
if ($model->isReadOnly() && in_array($type, ['create', 'update', 'delete'])) { |
138
|
|
|
$this->logger->info(sprintf('Skip generate Action (%s), because Model (%s) is read-only', $actionName, $modelName)); |
139
|
|
|
continue; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
$action = $this->getAction($actionName); |
143
|
|
|
if (Text::create($action->getTitle())->isEmpty()) { |
144
|
|
|
$action->setTitle($this->getActionTitle($modelName, $type)); |
145
|
|
|
} |
146
|
|
|
$action = $this->generateAction($actionName); |
147
|
|
|
|
148
|
|
|
// generate code |
149
|
|
|
$generator = $this->factory->createModelActionGenerator($type); |
150
|
|
|
$class = $generator->generate($action); |
151
|
|
|
$this->codegenService->dumpStruct($class, true); |
152
|
10 |
|
} |
153
|
10 |
|
|
154
|
|
|
// generate relationship actions |
155
|
|
|
if (!$model->isReadOnly()) { |
156
|
|
|
$types = [ |
157
|
|
|
Relationship::ONE_TO_ONE => ['read', 'update'], |
158
|
|
|
Relationship::ONE_TO_MANY => ['read', 'add', 'update', 'remove'], |
159
|
9 |
|
Relationship::MANY_TO_MANY => ['read', 'add', 'update', 'remove'] |
160
|
9 |
|
]; |
161
|
|
|
$relationships = $this->modelService->getRelationships($model); |
162
|
|
|
foreach ($relationships->getAll() as $relationship) { |
163
|
9 |
|
foreach ($types[$relationship->getType()] as $type) { |
164
|
3 |
|
$this->generateRelationshipAction($relationship, $type); |
165
|
2 |
|
} |
166
|
|
|
} |
167
|
|
|
} |
168
|
6 |
|
|
169
|
2 |
|
$input->setOption('type', $typeDump); |
170
|
2 |
|
} |
171
|
|
|
|
172
|
|
|
private function getActionTitle($modelName, $type) { |
173
|
4 |
|
$name = NameUtils::dasherize($modelName); |
174
|
3 |
|
switch ($type) { |
175
|
3 |
|
case 'list': |
176
|
2 |
|
return 'List all ' . NameUtils::pluralize($name); |
177
|
2 |
|
|
178
|
2 |
|
case 'create': |
179
|
1 |
|
case 'read': |
180
|
|
|
case 'update': |
181
|
3 |
|
case 'delete': |
182
|
|
|
return ucfirst($type) . 's ' . (in_array($name[0], ['a', 'e', 'i', 'o', 'u']) ? 'an' : 'a') . ' ' . $name; |
183
|
|
|
} |
184
|
|
|
} |
185
|
1 |
|
|
186
|
1 |
|
/** |
187
|
1 |
|
* Generates an action. |
188
|
|
|
* |
189
|
|
|
* @param string $actionName |
190
|
8 |
|
*/ |
191
|
8 |
|
private function generateSkeleton($actionName) { |
192
|
|
|
$this->logger->info('Generate Skeleton Action: ' . $actionName); |
193
|
5 |
|
$input = $this->io->getInput(); |
194
|
5 |
|
|
195
|
5 |
|
// generate action |
196
|
5 |
|
$action = $this->generateAction($actionName); |
197
|
5 |
|
|
198
|
1 |
|
// generate code |
199
|
1 |
|
$generator = new SkeletonActionGenerator($this->service); |
200
|
4 |
|
$class = $generator->generate($action); |
201
|
|
|
$this->codegenService->dumpStruct($class, $input->getOption('force')); |
202
|
|
|
} |
203
|
5 |
|
|
204
|
5 |
|
/** |
205
|
5 |
|
* Generates the action for the package |
206
|
5 |
|
* |
207
|
5 |
|
* @param string $actionName |
208
|
5 |
|
* @throws \RuntimeException |
209
|
4 |
|
* @return ActionSchema |
210
|
5 |
|
*/ |
211
|
5 |
|
private function generateAction($actionName) { |
212
|
5 |
|
$input = $this->io->getInput(); |
213
|
|
|
|
214
|
5 |
|
// get action and create it if it doesn't exist |
215
|
5 |
|
$action = $this->getAction($actionName); |
216
|
|
|
|
217
|
4 |
|
if (($title = $input->getOption('title')) !== null) { |
218
|
|
|
$action->setTitle($title); |
219
|
4 |
|
} |
220
|
3 |
|
|
221
|
|
|
if (Text::create($action->getTitle())->isEmpty()) { |
222
|
4 |
|
throw new \RuntimeException(sprintf('Cannot create action %s, because I am missing a title for it', $actionName)); |
223
|
4 |
|
} |
224
|
4 |
|
|
225
|
4 |
|
if (($classname = $input->getOption('classname')) !== null) { |
226
|
4 |
|
$action->setClass($classname); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
// guess classname if there is none set yet |
230
|
|
|
if (Text::create($action->getClass())->isEmpty()) { |
231
|
|
|
$action->setClass($this->guessClassname($actionName)); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
// guess title if there is none set yet |
235
|
|
|
if (Text::create($action->getTitle())->isEmpty() |
236
|
|
|
&& $this->modelService->isModelAction($action) |
237
|
8 |
|
&& $this->modelService->isCrudAction($action)) { |
|
|
|
|
238
|
8 |
|
$modelName = $this->modelService->getModelNameByAction($action); |
239
|
8 |
|
$type = $this->modelService->getOperationByAction($action); |
|
|
|
|
240
|
|
|
$action->setTitle($this->getActionTitle($modelName, $type)); |
241
|
|
|
} |
242
|
8 |
|
|
243
|
|
|
// set acl |
244
|
8 |
|
$action->setAcl($this->getAcl($action)); |
245
|
2 |
|
|
246
|
2 |
|
return $action; |
247
|
|
|
} |
248
|
8 |
|
|
249
|
1 |
|
private function generateRelationshipAction(Relationship $relationship, $type) { |
250
|
|
|
$model = $relationship->getModel(); |
251
|
|
|
$module = $this->package->getKeeko()->getModule(); |
252
|
7 |
|
$relatedName = $relationship->getRelatedName(); |
253
|
2 |
|
$relatedActionName = NameUtils::toSnakeCase($relationship->getRelatedName()); |
254
|
2 |
|
$actionNamePrefix = sprintf('%s-to-%s-relationship', $model->getOriginCommonName(), $relatedActionName); |
255
|
|
|
|
256
|
|
|
$titles = [ |
257
|
7 |
|
'read' => 'Reads the relationship of {model} to {related}', |
258
|
4 |
|
'update' => 'Updates the relationship of {model} to {related}', |
259
|
4 |
|
'add' => 'Adds {related} as relationship to {model}', |
260
|
|
|
'remove' => 'Removes {related} as relationship of {model}' |
261
|
|
|
]; |
262
|
7 |
|
|
263
|
7 |
|
// generate fqcn |
264
|
7 |
|
$className = sprintf('%s%s%sAction', $model->getPhpName(), $relatedName, ucfirst($type)); |
265
|
|
|
$fqcn = $this->packageService->getNamespace() . '\\action\\' . $className; |
266
|
|
|
|
267
|
|
|
// generate action |
268
|
|
|
$action = new ActionSchema($actionNamePrefix . '-' . $type); |
269
|
|
|
$action->addAcl('admin'); |
270
|
|
|
$action->setClass($fqcn); |
271
|
7 |
|
$action->setTitle(str_replace( |
272
|
|
|
['{model}', '{related}'], |
273
|
|
|
[$model->getOriginCommonName(), $relatedActionName], |
274
|
7 |
|
$titles[$type] |
275
|
7 |
|
)); |
276
|
|
|
$module->addAction($action); |
277
|
4 |
|
|
278
|
4 |
|
// generate class |
279
|
4 |
|
$generator = $this->factory->createActionRelationshipGenerator($type, $relationship); |
280
|
|
|
$class = $generator->generate($action, $relationship); |
281
|
|
|
$this->codegenService->dumpStruct($class, true); |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
} |
285
|
|
|
|
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.