Passed
Push — master ( aafe2b...6c8af3 )
by Bruno
07:44
created

FrontendGenerator::vueCodeItem()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
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\HTML\Element\Button;
11
use Formularium\Frontend\HTML\Element\Table;
12
use Formularium\Frontend\Vue\Element\Pagination as PaginationVue;
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
                ->setExtraProps([
98
                [
99
                    'name' => 'id',
100
                    'type' => 'String',
101
                    'required' => true
102
                ]
103
            ]);
104
            $this->vuePublish();
105
            $this->makeVuePaginationComponent();
106
            $this->makeJSModel();
107
108
            $this->vueCodeItem($vue);
109
            $this->makeVue($vue, 'Card', 'viewable', $cardFieldNames);
110
            $this->vueCodeItem($vue);
111
            $this->makeVue($vue, 'TableItem', 'viewable', $tableFieldNames);
112
            $this->makeVue($vue, 'List', 'viewable');
113
            $this->makeVue($vue, 'Table', 'viewable');
114
            $this->makeVue($vue, 'Show', 'viewable');
115
            $this->makeVue($vue, 'Edit', 'editable');
116
            $this->makeVue(
117
                $vue,
118
                'Form',
119
                'editable',
120
                function (Field $f) {
121
                    if (!$f->getExtradata('modelFillable')) {
122
                        return false;
123
                    }
124
                    return true;
125
                }
126
            );
127
            $this->makeVueRoutes();
128
            $this->makeVueIndex();
129
        }
130
131
        $this->makeGraphql();
132
133
        return $this->collection;
134
    }
135
136
    protected function vuePublish(): void
137
    {
138
        $this->collection->push(
139
            new GeneratedItem(
140
                GeneratedItem::TYPE_FRONTEND,
141
                file_get_contents(__DIR__ . "/Vue/Renderable/RelationshipAutocomplete.vue"),
142
                "Modelarium/RelationshipAutocomplete.vue"
143
            )
144
        );
145
        $this->collection->push(
146
            new GeneratedItem(
147
                GeneratedItem::TYPE_FRONTEND,
148
                file_get_contents(__DIR__ . "/Vue/Renderable/RelationshipSelect.vue"),
149
                "Modelarium/RelationshipSelect.vue"
150
            )
151
        );
152
        // $this->collection->push(
153
        //     new GeneratedItem(
154
        //         GeneratedItem::TYPE_FRONTEND,
155
        //         file_get_contents(__DIR__ . "/Vue/Renderable/RelationshipSelectMultiple.vue"),
156
        //         "Modelarium/RelationshipSelectMultiple.vue"
157
        //     )
158
        // );
159
    }
160
161
    protected function makeVuePaginationComponent(): void
162
    {
163
        $pagination = $this->composer->nodeElement(
164
            'Pagination',
165
            [
166
            ]
167
        );
168
        $html = $pagination->getRenderHTML();
169
        $script = PaginationVue::script();
170
171
        $this->collection->push(
172
            new GeneratedItem(
173
                GeneratedItem::TYPE_FRONTEND,
174
                "<template>\n$html\n</template>\n<script>\n$script\n</script>\n",
175
                "Modelarium/Pagination.vue"
176
            )
177
        );
178
    }
179
180
    protected function buildTemplateParameters(): void
181
    {
182
        $this->cardFields = $this->model->filterField(
183
            function (Field $field) {
184
                return $field->getRenderable('card', false);
185
            }
186
        );
187
        $this->tableFields = $this->model->filterField(
188
            function (Field $field) {
189
                return $field->getRenderable('table', false);
190
            }
191
        );
192
        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...
193
            $this->tableFields = $this->cardFields;
194
        }
195
196
        $buttonCreate = $this->composer->nodeElement(
197
            'Button',
198
            [
199
                Button::TYPE => 'a',
200
                Button::ATTRIBUTES => ['href' => "/{$this->lowerName}/edit" ],
201
            ]
202
        )->setContent(
203
            '<i class="fa fa-plus"></i> Add new',
204
            true,
205
            true
206
        )->getRenderHTML();
207
208
        $buttonEdit = $this->composer->nodeElement(
209
            'Button',
210
            [
211
                Button::TYPE => ($this->composer->getByName('Vue') ? 'router-link' : 'a'),
212
                Button::ATTRIBUTES => [':to' => "'/{$this->lowerName}/' + model.id + '/edit'"],
213
            ]
214
        )->setContent(
215
            '<i class="fa fa-pencil"></i> Edit',
216
            true,
217
            true
218
        )->getRenderHTML();
219
220
        $buttonDelete = $this->composer->nodeElement(
221
            'Button',
222
            [
223
                Button::TYPE => 'a',
224
                Button::COLOR => Button::COLOR_WARNING,
225
                Button::ATTRIBUTES => [
226
                    'href' => '#',
227
                    '@click.prevent' => 'remove'
228
                ],
229
            ]
230
        )->setContent(
231
            '<i class="fa fa-trash"></i> Delete',
232
            true,
233
            true
234
        )->getRenderHTML();
235
236
        /*
237
         * table
238
         */
239
        $table = $this->composer->nodeElement(
240
            'Table',
241
            [
242
                Table::ROW_NAMES => array_map(
243
                    function (Field $field) {
244
                        return $field->getRenderable(Renderable::LABEL, $field->getName());
245
                    },
246
                    $this->tableFields
247
                ),
248
                Table::STRIPED => true
249
            ]
250
        );
251
        /**
252
         * @var HTMLNode $tbody
253
         */
254
        $tbody = $table->get('tbody')[0];
255
        $tbody->setContent(
256
            '<' . $this->studlyName . 'TableItem v-for="l in list" :key="l.id" v-bind="l"></' . $this->studlyName . 'TableItem>',
257
            true,
258
            true
259
        );
260
        $titleFields = $this->model->filterField(
261
            function (Field $field) {
262
                return $field->getRenderable('title', false);
263
            }
264
        );
265
266
        $spinner = $this->composer->nodeElement('Spinner')
267
        ->addAttribute(
268
            'v-show',
269
            'isLoading'
270
        )->getRenderHTML();
271
        $this->templateParameters = [
272
            'buttonSubmit' => $this->composer->element(
273
                'Button',
274
                [
275
                    Button::TYPE => 'submit',
276
                    Element::LABEL => 'Submit'
277
                ]
278
            ),
279
            'buttonCreate' => $buttonCreate,
280
            'buttonEdit' => $buttonEdit,
281
            'buttonDelete' => $buttonDelete,
282
            // TODO 'hasCan' => $this->model
283
            'spinner' => $spinner,
284
            'tablelist' => $table->getRenderHTML(),
285
            'tableItemFields' => array_keys(array_map(function (Field $f) {
286
                return $f->getName();
287
            }, $this->tableFields)),
288
            'titleField' => array_key_first($titleFields) ?: 'id'
289
        ];
290
    }
291
292
    protected function vueCodeItem(FrameworkVue $vue)
293
    {
294
        $vue->getVueCode()->appendComputed('link', 'return "/' . $this->lowerName . '/" + this.id');
0 ignored issues
show
Bug introduced by
The method getVueCode() 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

294
        $vue->/** @scrutinizer ignore-call */ 
295
              getVueCode()->appendComputed('link', 'return "/' . $this->lowerName . '/" + this.id');

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...
295
    }
296
297
    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

297
    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

297
    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...
298
    {
299
        $x = $this->templateFile(
300
            $stub,
301
            array_merge(
302
                $this->templateParameters,
303
                $data
304
            )
305
        );
306
        return $x;
307
    }
308
309
    /**
310
     * @param FrameworkVue $vue
311
     * @param string $component
312
     * @param string $mode
313
     * @param string[]|callable $restrictFields
314
     * @return void
315
     */
316
    protected function makeVue(FrameworkVue $vue, string $component, string $mode, $restrictFields = null): void
317
    {
318
        $path = $this->model->getName() . '/' .
319
            $this->model->getName() . $component . '.vue';
320
321
        $stub = $this->stubDir . "/Vue{$component}.mustache.vue";
322
323
        if ($mode == 'editable') {
324
            $vue->setEditableTemplate(
325
                function (FrameworkVue $vue, array $data, Model $m) use ($stub) {
326
                    return $this->templateCallback($stub, $vue, $data, $m);
327
                }
328
            );
329
330
            $this->collection->push(
331
                new GeneratedItem(
332
                    GeneratedItem::TYPE_FRONTEND,
333
                    $this->model->editable($this->composer, [], $restrictFields),
334
                    $path
335
                )
336
            );
337
        } else {
338
            $vue->setViewableTemplate(
339
                function (FrameworkVue $vue, array $data, Model $m) use ($stub) {
340
                    return $this->templateCallback($stub, $vue, $data, $m);
341
                }
342
            );
343
344
            $this->collection->push(
345
                new GeneratedItem(
346
                    GeneratedItem::TYPE_FRONTEND,
347
                    $this->model->viewable($this->composer, [], $restrictFields),
348
                    $path
349
                )
350
            );
351
        }
352
        $vue->resetVueCode();
0 ignored issues
show
Bug introduced by
The method resetVueCode() 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

352
        $vue->/** @scrutinizer ignore-call */ 
353
              resetVueCode();

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...
353
    }
354
355
    protected function getFilters(): array
356
    {
357
        $filters = [];
358
        return $filters;
359
    }
360
361
    protected function makeGraphql(): void
362
    {
363
        /*
364
         * card
365
         */
366
        $cardFieldNames = array_map(
367
            function (Field $field) {
368
                return $field->getName();
369
            },
370
            $this->cardFields
371
        );
372
        $cardFieldParameters = implode("\n", $cardFieldNames);
373
374
        $listQuery = <<<EOF
375
query (\$page: Int!) {
376
    {$this->lowerNamePlural}(page: \$page) {
377
        data {
378
            id
379
            $cardFieldParameters
380
        }
381
      
382
        paginatorInfo {
383
            currentPage
384
            perPage
385
            total
386
            lastPage
387
        }
388
    }
389
}
390
EOF;
391
        $this->collection->push(
392
            new GeneratedItem(
393
                GeneratedItem::TYPE_FRONTEND,
394
                $listQuery,
395
                $this->model->getName() . '/queryList.graphql'
396
            )
397
        );
398
399
        /*
400
         * table
401
         */
402
        $tableFieldNames = array_map(
403
            function (Field $field) {
404
                return $field->getName();
405
            },
406
            $this->tableFields
407
        );
408
        $tableFieldParameters = implode("\n", $tableFieldNames);
409
410
        $tableQuery = <<<EOF
411
query (\$page: Int!) {
412
    {$this->lowerNamePlural}(page: \$page) {
413
        data {
414
            id
415
            $tableFieldParameters
416
        }
417
      
418
        paginatorInfo {
419
            currentPage
420
            perPage
421
            total
422
            lastPage
423
        }
424
    }
425
}
426
EOF;
427
        $this->collection->push(
428
            new GeneratedItem(
429
                GeneratedItem::TYPE_FRONTEND,
430
                $tableQuery,
431
                $this->model->getName() . '/queryTable.graphql'
432
            )
433
        );
434
435
        /*
436
         * item
437
         */
438
        $graphqlQuery = $this->model->mapFields(
439
            function (Field $f) {
440
                return \Modelarium\Frontend\Util::fieldShow($f) ? $f->toGraphqlQuery() : null;
441
            }
442
        );
443
        $graphqlQuery = join("\n", array_filter($graphqlQuery));
444
445
        $itemQuery = <<<EOF
446
query (\$id: ID!) {
447
    {$this->lowerName}(id: \$id) {
448
        id
449
        $graphqlQuery
450
        can
451
    }
452
}
453
EOF;
454
455
        $this->collection->push(
456
            new GeneratedItem(
457
                GeneratedItem::TYPE_FRONTEND,
458
                $itemQuery,
459
                $this->model->getName() . '/queryItem.graphql'
460
            )
461
        );
462
463
        $upsertMutation = <<<EOF
464
mutation upsert(\${$this->lowerName}: {$this->studlyName}Input!) {
465
    upsert{$this->studlyName}(input: \${$this->lowerName}) {
466
        id
467
    }
468
}
469
EOF;
470
        $this->collection->push(
471
            new GeneratedItem(
472
                GeneratedItem::TYPE_FRONTEND,
473
                $upsertMutation,
474
                $this->model->getName() . '/mutationUpsert.graphql'
475
            )
476
        );
477
478
        $deleteMutation = <<<EOF
479
mutation delete(\$id: ID!) {
480
    delete{$this->studlyName}(id: \$id) {
481
        id
482
    }
483
}
484
EOF;
485
        $this->collection->push(
486
            new GeneratedItem(
487
                GeneratedItem::TYPE_FRONTEND,
488
                $deleteMutation,
489
                $this->model->getName() . '/mutationDelete.graphql'
490
            )
491
        );
492
    }
493
494
    protected function makeVueIndex(): void
495
    {
496
        $path = $this->model->getName() . '/index.js';
497
        $name = $this->studlyName;
498
499
        $items = [
500
            'Card',
501
            'Edit',
502
            'List',
503
            'Show',
504
            'Table',
505
        ];
506
507
        $import = array_map(
508
            function ($i) use ($name) {
509
                return "import {$name}$i from './{$name}$i.vue';";
510
            },
511
            $items
512
        );
513
514
        $export = array_map(
515
            function ($i) use ($name) {
516
                return "    {$name}$i,\n";
517
            },
518
            $items
519
        );
520
521
        $this->collection->push(
522
            new GeneratedItem(
523
                GeneratedItem::TYPE_FRONTEND,
524
                implode("\n", $import) . "\n" .
525
                "export {\n" .
526
                implode("\n", $export) . "\n};\n",
527
                $path
528
            )
529
        );
530
    }
531
532
    protected function makeVueRoutes(): void
533
    {
534
        $path = $this->model->getName() . '/routes.js';
535
536
        $this->collection->push(
537
            new GeneratedItem(
538
                GeneratedItem::TYPE_FRONTEND,
539
                $this->templateFile($this->stubDir . "/routes.mustache.js"),
540
                $path
541
            )
542
        );
543
    }
544
545
    protected function makeJSModel(): void
546
    {
547
        $path = $this->model->getName() . '/model.js';
548
        $modelValues = $this->model->getDefault();
549
        $modelValues['id'] = 0;
550
        $modelJS = 'const model = ' . json_encode($modelValues) .
551
            ";\n\nexport default model;\n";
552
        
553
        $this->collection->push(
554
            new GeneratedItem(
555
                GeneratedItem::TYPE_FRONTEND,
556
                $modelJS,
557
                $path
558
            )
559
        );
560
    }
561
}
562