Passed
Push — master ( dbc8ed...a2b831 )
by Bruno
07:33
created

FrontendGenerator::makeJSModel()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 10
c 0
b 0
f 0
dl 0
loc 13
ccs 0
cts 0
cp 0
rs 9.9332
cc 1
nc 1
nop 0
crap 2
1
<?php declare(strict_types=1);
2
3
namespace Modelarium\Frontend;
4
5
use Formularium\Datatype;
6
use Formularium\Element;
7
use Formularium\Field;
8
use Formularium\Model;
9
use Formularium\FrameworkComposer;
10
use Formularium\Frontend\Blade\Framework as FrameworkBlade;
11
use Formularium\Frontend\HTML\Element\Button;
12
use Formularium\Frontend\HTML\Element\Table;
13
use Formularium\Frontend\Vue\Framework as FrameworkVue;
14
use Formularium\HTMLNode;
15
use Formularium\Renderable;
16
use Modelarium\GeneratedCollection;
17
use Modelarium\GeneratedItem;
18
use Modelarium\GeneratorInterface;
19
use Modelarium\GeneratorNameTrait;
20
21
use function Safe\file_get_contents;
22
use function Safe\json_encode;
23
24
class FrontendGenerator implements GeneratorInterface
25
{
26
    use GeneratorNameTrait;
27
28
    /**
29
     * @var FrameworkComposer
30
     */
31
    protected $composer = null;
32
33
    /**
34
     * @var Model
35
     */
36
    protected $model = null;
37
38
    /**
39
     * @var GeneratedCollection
40
     */
41
    protected $collection;
42
43
    /**
44
     *
45
     * @var string
46
     */
47
    protected $stubDir = __DIR__ . '/stubs';
48
49
    /**
50
     * String substitution
51
     *
52
     * @var array
53
     */
54
    protected $templateParameters = [];
55
56
    /**
57
     * Card fields
58
     *
59
     * @var Field[]
60
     */
61
    protected $cardFields = [];
62
63
    /**
64
     * Table fields
65
     *
66
     * @var Field[]
67
     */
68
    protected $tableFields = [];
69
70
    public function __construct(FrameworkComposer $composer, Model $model)
71
    {
72
        $this->composer = $composer;
73
        $this->model = $model;
74
        $this->setBaseName($model->getName());
75
        $this->buildTemplateParameters();
76
    }
77
78
    public function generate(): GeneratedCollection
79
    {
80
        $this->collection = new GeneratedCollection();
81
82
        /**
83
         * @var FrameworkVue $vue
84
         */
85
        $vue = $this->composer->getByName('Vue');
86
        // $blade = FrameworkComposer::getByName('Blade');
87
88
        if ($vue !== null) {
89
            $cardFieldNames = array_map(function (Field $f) {
90
                return $f->getName();
91
            }, $this->cardFields);
92
            $tableFieldNames = array_map(function (Field $f) {
93
                return $f->getName();
94
            }, $this->tableFields);
95
96
            $vue->setFieldModelVariable('model.');
97
            $vue->setExtraProps([
0 ignored issues
show
Bug introduced by
The method setExtraProps() does not exist on Formularium\Frontend\Vue\Framework. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

97
            $vue->/** @scrutinizer ignore-call */ 
98
                  setExtraProps([

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...
98
                [
99
                    'name' => 'id',
100
                    'type' => 'String',
101
                    'required' => true
102
                ]
103
            ]);
104
            $this->makeJSModel();
105
            $this->makeVue($vue, 'Card', 'viewable', $cardFieldNames);
106
            $this->makeVue($vue, 'List', 'viewable');
107
            $this->makeVue($vue, 'Table', 'viewable');
108
            $this->makeVue($vue, 'TableItem', 'viewable', $tableFieldNames);
109
            $this->makeVue($vue, 'Show', 'viewable');
110
            $this->makeVue($vue, 'Edit', 'editable');
111
            $this->makeVue($vue, 'Form', 'editable');
112
            $this->makeVueRoutes();
113
            $this->makeVueIndex();
114
        }
115
116
        $this->makeGraphql();
117
118
        return $this->collection;
119
    }
120
121
    protected function buildTemplateParameters(): void
122
    {
123
        $this->cardFields = $this->model->filterField(
124
            function (Field $field) {
125
                return $field->getRenderable('card', false);
126
            }
127
        );
128
        $this->tableFields = $this->model->filterField(
129
            function (Field $field) {
130
                return $field->getRenderable('table', false);
131
            }
132
        );
133
        if (!$this->tableFields) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->tableFields of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
134
            $this->tableFields = $this->cardFields;
135
        }
136
137
        $buttonCreate = $this->composer->nodeElement(
138
            'Button',
139
            [
140
                Button::TYPE => 'a',
141
                Button::ATTRIBUTES => ['href' => "/{$this->lowerName}/edit" ],
0 ignored issues
show
Bug introduced by
The constant Formularium\Frontend\HTM...ment\Button::ATTRIBUTES was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
142
            ]
143
        )->setContent(
144
            '<i class="fa fa-plus"></i> Add new',
145
            true,
146
            true
147
        )->getRenderHTML();
148
149
        $buttonEdit = $this->composer->nodeElement(
150
            'Button',
151
            [
152
                Button::TYPE => ($this->composer->getByName('Vue') ? 'router-link' : 'a'),
153
                Button::ATTRIBUTES => [':to' => "'/{$this->lowerName}/' + model.id + '/edit'"],
154
            ]
155
        )->setContent(
156
            '<i class="fa fa-pencil"></i> Edit',
157
            true,
158
            true
159
        )->getRenderHTML();
160
161
        $buttonDelete = $this->composer->nodeElement(
162
            'Button',
163
            [
164
                Button::TYPE => 'a',
165
                Button::COLOR => Button::COLOR_WARNING,
166
                Button::ATTRIBUTES => [
167
                    'href' => '#',
168
                    '@click.prevent' => 'remove'
169
                ],
170
            ]
171
        )->setContent(
172
            '<i class="fa fa-trash"></i> Delete',
173
            true,
174
            true
175
        )->getRenderHTML();
176
177
        /*
178
         * table
179
         */
180
        $table = $this->composer->nodeElement(
181
            'Table',
182
            [
183
                Table::ROW_NAMES => array_map(
184
                    function (Field $field) {
185
                        return $field->getRenderable(Renderable::LABEL, $field->getName());
186
                    },
187
                    $this->tableFields
188
                ),
189
            ]
190
        );
191
        /**
192
         * @var HTMLNode $tbody
193
         */
194
        $tbody = $table->get('tbody')[0];
195
        $tbody->setContent(
196
            '<' . $this->studlyName . 'TableItem v-for="l in list" :key="l.id" v-bind="l"></' . $this->studlyName . 'TableItem>',
197
            true,
198
            true
199
        );
200
        $titleFields = $this->model->filterField(
201
            function (Field $field) {
202
                return $field->getRenderable('title', false);
203
            }
204
        );
205
206
        $this->templateParameters = [
207
            'buttonSubmit' => $this->composer->element(
208
                'Button',
209
                [
210
                    Button::TYPE => 'submit',
211
                    Element::LABEL => 'Submit'
212
                ]
213
            ),
214
            'buttonCreate' => $buttonCreate,
215
            'buttonEdit' => $buttonEdit,
216
            'buttonDelete' => $buttonDelete,
217
            // TODO 'hasCan' => $this->model
218
            'tablelist' => $table->getRenderHTML(),
219
            'tableItemFields' => array_keys(array_map(function (Field $f) {
220
                return $f->getName();
221
            }, $this->tableFields)),
222
            'titleField' => array_key_first($titleFields) ?: 'id'
223
        ];
224
    }
225
226
    public function templateCallback(string $stub, FrameworkVue $vue, array $data, Model $m): string
0 ignored issues
show
Unused Code introduced by
The parameter $m is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

226
    public function templateCallback(string $stub, FrameworkVue $vue, array $data, /** @scrutinizer ignore-unused */ Model $m): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $vue is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

226
    public function templateCallback(string $stub, /** @scrutinizer ignore-unused */ FrameworkVue $vue, array $data, Model $m): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
227
    {
228
        $x = $this->templateFile(
229
            $stub,
230
            array_merge(
231
                $this->templateParameters,
232
                $data
233
            )
234
        );
235
        return $x;
236
    }
237
238
    protected function makeVue(FrameworkVue $vue, string $component, string $mode, array $restrictFields = null): void
239
    {
240
        $path = $this->model->getName() . '/' .
241
            $this->model->getName() . $component . '.vue';
242
243
        $stub = $this->stubDir . "/Vue{$component}.mustache.vue";
244
245
        if ($mode == 'editable') {
246
            $vue->setEditableTemplate(
247
                function (FrameworkVue $vue, array $data, Model $m) use ($stub) {
248
                    return $this->templateCallback($stub, $vue, $data, $m);
249
                }
250
            );
251
252
            $this->collection->push(
253
                new GeneratedItem(
254
                    GeneratedItem::TYPE_FRONTEND,
255
                    $this->model->editable($this->composer, [], $restrictFields),
0 ignored issues
show
Unused Code introduced by
The call to Formularium\Model::editable() has too many arguments starting with $restrictFields. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

255
                    $this->model->/** @scrutinizer ignore-call */ 
256
                                  editable($this->composer, [], $restrictFields),

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
256
                    $path
257
                )
258
            );
259
        } else {
260
            $vue->setViewableTemplate(
261
                function (FrameworkVue $vue, array $data, Model $m) use ($stub) {
262
                    return $this->templateCallback($stub, $vue, $data, $m);
263
                }
264
            );
265
266
            $this->collection->push(
267
                new GeneratedItem(
268
                    GeneratedItem::TYPE_FRONTEND,
269
                    $this->model->viewable($this->composer, [], $restrictFields),
0 ignored issues
show
Unused Code introduced by
The call to Formularium\Model::viewable() has too many arguments starting with $restrictFields. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

269
                    $this->model->/** @scrutinizer ignore-call */ 
270
                                  viewable($this->composer, [], $restrictFields),

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
270
                    $path
271
                )
272
            );
273
        }
274
    }
275
276
    protected function getFilters(): array
277
    {
278
        $filters = [];
279
        return $filters;
280
    }
281
282
    protected function makeGraphql(): void
283
    {
284
        /*
285
         * card
286
         */
287
        $cardFieldNames = array_map(
288
            function (Field $field) {
289
                return $field->getName();
290
            },
291
            $this->cardFields
292
        );
293
        $cardFieldParameters = implode("\n", $cardFieldNames);
294
295
        $listQuery = <<<EOF
296
query (\$page: Int!) {
297
    {$this->lowerNamePlural}(page: \$page) {
298
        data {
299
            id
300
            $cardFieldParameters
301
        }
302
      
303
        paginatorInfo {
304
            currentPage
305
            perPage
306
            total
307
            lastPage
308
        }
309
    }
310
}
311
EOF;
312
        $this->collection->push(
313
            new GeneratedItem(
314
                GeneratedItem::TYPE_FRONTEND,
315
                $listQuery,
316
                $this->model->getName() . '/queryList.graphql'
317
            )
318
        );
319
320
        /*
321
         * table
322
         */
323
        $tableFieldNames = array_map(
324
            function (Field $field) {
325
                return $field->getName();
326
            },
327
            $this->tableFields
328
        );
329
        $tableFieldParameters = implode("\n", $tableFieldNames);
330
331
        $tableQuery = <<<EOF
332
query (\$page: Int!) {
333
    {$this->lowerNamePlural}(page: \$page) {
334
        data {
335
            id
336
            $tableFieldParameters
337
        }
338
      
339
        paginatorInfo {
340
            currentPage
341
            perPage
342
            total
343
            lastPage
344
        }
345
    }
346
}
347
EOF;
348
        $this->collection->push(
349
            new GeneratedItem(
350
                GeneratedItem::TYPE_FRONTEND,
351
                $tableQuery,
352
                $this->model->getName() . '/queryTable.graphql'
353
            )
354
        );
355
356
        /*
357
         * item
358
         */
359
        $graphqlQuery = $this->model->toGraphqlQuery();
0 ignored issues
show
Bug introduced by
The method toGraphqlQuery() does not exist on Formularium\Model. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

359
        /** @scrutinizer ignore-call */ 
360
        $graphqlQuery = $this->model->toGraphqlQuery();

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...
360
        $itemQuery = <<<EOF
361
query (\$id: ID!) {
362
    {$this->lowerName}(id: \$id) {
363
        id
364
        $graphqlQuery
365
    }
366
}
367
EOF;
368
369
        $this->collection->push(
370
            new GeneratedItem(
371
                GeneratedItem::TYPE_FRONTEND,
372
                $itemQuery,
373
                $this->model->getName() . '/queryItem.graphql'
374
            )
375
        );
376
377
        $upsertMutation = <<<EOF
378
mutation upsert(\${$this->lowerName}: {$this->studlyName}Input!) {
379
    upsert{$this->studlyName}(input: \${$this->lowerName}) {
380
        id
381
    }
382
}
383
EOF;
384
        $this->collection->push(
385
            new GeneratedItem(
386
                GeneratedItem::TYPE_FRONTEND,
387
                $upsertMutation,
388
                $this->model->getName() . '/mutationUpsert.graphql'
389
            )
390
        );
391
392
        $deleteMutation = <<<EOF
393
mutation delete(\$id: ID!) {
394
    delete{$this->studlyName}(id: \$id) {
395
        id
396
    }
397
}
398
EOF;
399
        $this->collection->push(
400
            new GeneratedItem(
401
                GeneratedItem::TYPE_FRONTEND,
402
                $deleteMutation,
403
                $this->model->getName() . '/mutationDelete.graphql'
404
            )
405
        );
406
    }
407
408
    protected function makeVueIndex(): void
409
    {
410
        $path = $this->model->getName() . '/index.js';
411
        $name = $this->studlyName;
412
413
        $items = [
414
            'Card',
415
            'Edit',
416
            'List',
417
            'Show',
418
            'Table',
419
        ];
420
421
        $import = array_map(
422
            function ($i) use ($name) {
423
                return "import {$name}$i from './{$name}$i.vue';";
424
            },
425
            $items
426
        );
427
428
        $export = array_map(
429
            function ($i) use ($name) {
430
                return "    {$name}$i,\n";
431
            },
432
            $items
433
        );
434
435
        $this->collection->push(
436
            new GeneratedItem(
437
                GeneratedItem::TYPE_FRONTEND,
438
                implode("\n", $import) . "\n" .
439
                "export {\n" .
440
                implode("\n", $export) . "\n};\n",
441
                $path
442
            )
443
        );
444
    }
445
446
    protected function makeVueRoutes(): void
447
    {
448
        $path = $this->model->getName() . '/routes.js';
449
450
        $this->collection->push(
451
            new GeneratedItem(
452
                GeneratedItem::TYPE_FRONTEND,
453
                $this->templateFile($this->stubDir . "/routes.mustache.js"),
454
                $path
455
            )
456
        );
457
    }
458
459
    protected function makeJSModel(): void
460
    {
461
        $path = $this->model->getName() . '/model.js';
462
        $modelValues = $this->model->getDefault();
463
        $modelValues['id'] = 0;
464
        $modelJS = 'const model = ' . json_encode($modelValues) .
465
            ";\n\nexport default model;\n";
466
        
467
        $this->collection->push(
468
            new GeneratedItem(
469
                GeneratedItem::TYPE_FRONTEND,
470
                $modelJS,
471
                $path
472
            )
473
        );
474
    }
475
}
476