Passed
Push — master ( f2a1d0...268a2f )
by Bruno
07:04
created

FrontendGenerator::makeGraphql()   B

Complexity

Conditions 2
Paths 1

Size

Total Lines 129
Code Lines 62

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 62
nc 1
nop 0
dl 0
loc 129
rs 8.829
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
        if ($this->model->getName() !== 'Region') {
82
            return $this->collection;
83
        }
84
85
        /**
86
         * @var FrameworkVue $vue
87
         */
88
        $vue = $this->composer->getByName('Vue');
89
        // $blade = FrameworkComposer::getByName('Blade');
90
91
        if ($vue !== null) {
92
            $vueCode = $vue->getVueCode();
93
            $cardFieldNames = array_map(function (Field $f) {
94
                return $f->getName();
95
            }, $this->cardFields);
96
            $tableFieldNames = array_map(function (Field $f) {
97
                return $f->getName();
98
            }, $this->tableFields);
99
100
            $vue->setFieldModelVariable('model.');
101
            $extraprops = [
102
                [
103
                    'name' => 'id',
104
                    'type' => 'String',
105
                    'required' => true
106
                ]
107
            ];
108
            $vueCode->setExtraProps($extraprops);
109
            $this->vuePublish();
110
            $this->makeVuePaginationComponent();
111
            $this->makeJSModel();
112
113
            $this->vueCodeItem($vue);
114
115
            // card
116
            foreach ($this->cardFields as $f) {
117
                $vueCode->appendExtraProp([
118
                    'name' => $f->getName(),
119
                    'type' => $vueCode->mapTypeToJs($f->getDatatype()),
0 ignored issues
show
Bug introduced by
The method mapTypeToJs() does not exist on Formularium\Frontend\Vue\VueCode. Did you maybe mean mapType()? ( Ignorable by Annotation )

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

119
                    'type' => $vueCode->/** @scrutinizer ignore-call */ mapTypeToJs($f->getDatatype()),

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...
120
                    'required' => true
121
                ]);
122
            }
123
            $this->makeVue($vue, 'Card', 'viewable', $cardFieldNames);
124
            // reset props
125
            $vueCode->setExtraProps($extraprops);
126
127
            // table
128
            foreach ($this->tableFields as $f) {
129
                $vueCode->appendExtraProp([
130
                    'name' => $f->getName(),
131
                    'type' => $vueCode->mapTypeToJs($f->getDatatype()),
132
                    'required' => true
133
                ]);
134
            }
135
            $this->makeVue($vue, 'TableItem', 'viewable', $tableFieldNames);
136
137
            // reset props
138
            $vueCode->setExtraProps($extraprops);
139
            $this->makeVue($vue, 'List', 'viewable');
140
            // $this->makeVue($vue, 'Table', 'viewable');
141
            // $this->makeVue($vue, 'Show', 'viewable');
142
            // $this->makeVue($vue, 'Edit', 'editable');
143
            // $this->makeVue(
144
            //     $vue,
145
            //     'Form',
146
            //     'editable',
147
            //     function (Field $f) {
148
            //         if (!$f->getExtradata('modelFillable')) {
149
            //             return false;
150
            //         }
151
            //         return true;
152
            //     }
153
            // );
154
            $this->makeVueRoutes();
155
            $this->makeVueIndex();
156
        }
157
158
        $this->makeGraphql();
159
160
        return $this->collection;
161
    }
162
163
    /**
164
     * Publishes the Vue component dependencies
165
     *
166
     * @return void
167
     */
168
    protected function vuePublish(): void
169
    {
170
        $this->collection->push(
171
            new GeneratedItem(
172
                GeneratedItem::TYPE_FRONTEND,
173
                file_get_contents(__DIR__ . "/Vue/Renderable/RelationshipAutocomplete.vue"),
174
                "Modelarium/RelationshipAutocomplete.vue"
175
            )
176
        );
177
        $this->collection->push(
178
            new GeneratedItem(
179
                GeneratedItem::TYPE_FRONTEND,
180
                file_get_contents(__DIR__ . "/Vue/Renderable/RelationshipSelect.vue"),
181
                "Modelarium/RelationshipSelect.vue"
182
            )
183
        );
184
        // $this->collection->push(
185
        //     new GeneratedItem(
186
        //         GeneratedItem::TYPE_FRONTEND,
187
        //         file_get_contents(__DIR__ . "/Vue/Renderable/RelationshipSelectMultiple.vue"),
188
        //         "Modelarium/RelationshipSelectMultiple.vue"
189
        //     )
190
        // );
191
    }
192
193
    protected function makeVuePaginationComponent(): void
194
    {
195
        $pagination = $this->composer->nodeElement(
196
            'Pagination',
197
            [
198
            ]
199
        );
200
        $html = $pagination->getRenderHTML();
201
        $script = PaginationVue::script();
202
203
        $this->collection->push(
204
            new GeneratedItem(
205
                GeneratedItem::TYPE_FRONTEND,
206
                "<template>\n$html\n</template>\n<script>\n$script\n</script>\n",
207
                "Modelarium/Pagination.vue"
208
            )
209
        );
210
    }
211
212
    protected function buildTemplateParameters(): void
213
    {
214
        $hasVue = $this->composer->getByName('Vue');
215
216
        $this->cardFields = $this->model->filterField(
217
            function (Field $field) {
218
                return $field->getRenderable('card', false);
219
            }
220
        );
221
        $this->tableFields = $this->model->filterField(
222
            function (Field $field) {
223
                return $field->getRenderable('table', false);
224
            }
225
        );
226
227
        $buttonCreate = $this->composer->nodeElement(
228
            'Button',
229
            [
230
                Button::TYPE => 'a',
231
                Button::ATTRIBUTES => [
232
                    'href' => "/{$this->lowerName}/edit"
233
                ] + ($hasVue ? [ "v-if" => 'can.create' ]: []),
234
            ]
235
        )->setContent(
236
            '<i class="fa fa-plus"></i> Add new',
237
            true,
238
            true
239
        )->getRenderHTML();
240
241
        $buttonEdit = $this->composer->nodeElement(
242
            'Button',
243
            [
244
                Button::TYPE => ($hasVue ? 'router-link' : 'a'),
245
                Button::ATTRIBUTES => [
246
                    ':to' => "'/{$this->lowerName}/' + model.id + '/edit'"
247
                ] + ($hasVue ? [ "v-if" => 'can.edit' ]: []),
248
            ]
249
        )->setContent(
250
            '<i class="fa fa-pencil"></i> Edit',
251
            true,
252
            true
253
        )->getRenderHTML();
254
255
        $buttonDelete = $this->composer->nodeElement(
256
            'Button',
257
            [
258
                Button::TYPE => 'a',
259
                Button::COLOR => Button::COLOR_WARNING,
260
                Button::ATTRIBUTES => [
261
                    'href' => '#',
262
                    '@click.prevent' => 'remove'
263
                ] + ($hasVue ? [ "v-if" => 'can.delete' ]: []),
264
            ]
265
        )->setContent(
266
            '<i class="fa fa-trash"></i> Delete',
267
            true,
268
            true
269
        )->getRenderHTML();
270
271
        /*
272
         * table
273
         */
274
        $table = $this->composer->nodeElement(
275
            'Table',
276
            [
277
                Table::ROW_NAMES => array_map(
278
                    function (Field $field) {
279
                        return $field->getRenderable(Renderable::LABEL, $field->getName());
280
                    },
281
                    $this->tableFields
282
                ),
283
                Table::STRIPED => true
284
            ]
285
        );
286
        /**
287
         * @var HTMLNode $tbody
288
         */
289
        $tbody = $table->get('tbody')[0];
290
        $tbody->setContent(
291
            '<' . $this->studlyName . 'TableItem v-for="l in list" :key="l.id" v-bind="l"></' . $this->studlyName . 'TableItem>',
292
            true,
293
            true
294
        );
295
        $titleFields = $this->model->filterField(
296
            function (Field $field) {
297
                return $field->getRenderable('title', false);
298
            }
299
        );
300
301
        $spinner = $this->composer->nodeElement('Spinner')
302
        ->addAttribute(
303
            'v-show',
304
            'isLoading'
305
        )->getRenderHTML();
306
        $this->templateParameters = [
307
            'buttonSubmit' => $this->composer->element(
308
                'Button',
309
                [
310
                    Button::TYPE => 'submit',
311
                    Element::LABEL => 'Submit'
312
                ]
313
            ),
314
            'buttonCreate' => $buttonCreate,
315
            'buttonEdit' => $buttonEdit,
316
            'buttonDelete' => $buttonDelete,
317
            // TODO 'hasCan' => $this->model
318
            'spinner' => $spinner,
319
            'tablelist' => $table->getRenderHTML(),
320
            'tableItemFields' => array_keys(array_map(function (Field $f) {
321
                return $f->getName();
322
            }, $this->tableFields)),
323
            'titleField' => array_key_first($titleFields) ?: 'id'
324
        ];
325
    }
326
327
    /**
328
     * Appends computed item
329
     *
330
     * @param FrameworkVue $vue
331
     * @return void
332
     */
333
    protected function vueCodeItem(FrameworkVue $vue): void
334
    {
335
        $vue->getVueCode()->appendComputed(
336
            'link',
337
            'return "/' . $this->model->getRenderable('routeBase', $this->lowerName) . '/" + this.id'
338
        );
339
    }
340
341
    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

341
    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

341
    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...
342
    {
343
        $x = $this->templateFile(
344
            $stub,
345
            array_merge(
346
                $this->templateParameters,
347
                $data
348
            )
349
        );
350
        return $x;
351
    }
352
353
    /**
354
     * @param FrameworkVue $vue
355
     * @param string $component
356
     * @param string $mode
357
     * @param string[]|callable $restrictFields
358
     * @return void
359
     */
360
    protected function makeVue(FrameworkVue $vue, string $component, string $mode, $restrictFields = null): void
361
    {
362
        $path = $this->model->getName() . '/' .
363
            $this->model->getName() . $component . '.vue';
364
365
        $stub = $this->stubDir . "/Vue{$component}.mustache.vue";
366
367
        if ($mode == 'editable') {
368
            $vue->setEditableTemplate(
369
                function (FrameworkVue $vue, array $data, Model $m) use ($stub) {
370
                    return $this->templateCallback($stub, $vue, $data, $m);
371
                }
372
            );
373
374
            $this->collection->push(
375
                new GeneratedItem(
376
                    GeneratedItem::TYPE_FRONTEND,
377
                    $this->model->editable($this->composer, [], $restrictFields),
378
                    $path
379
                )
380
            );
381
        } else {
382
            $vue->setViewableTemplate(
383
                function (FrameworkVue $vue, array $data, Model $m) use ($stub) {
384
                    return $this->templateCallback($stub, $vue, $data, $m);
385
                }
386
            );
387
388
            $this->collection->push(
389
                new GeneratedItem(
390
                    GeneratedItem::TYPE_FRONTEND,
391
                    $this->model->viewable($this->composer, [], $restrictFields),
392
                    $path
393
                )
394
            );
395
        }
396
        $vue->resetVueCode();
397
    }
398
399
    protected function getFilters(): array
400
    {
401
        $filters = [];
402
        return $filters;
403
    }
404
405
    protected function makeGraphql(): void
406
    {
407
        /*
408
         * card
409
         */
410
        $cardFieldNames = array_map(
411
            function (Field $field) {
412
                return $field->getName();
413
            },
414
            $this->cardFields
415
        );
416
        $cardFieldParameters = implode("\n", $cardFieldNames);
417
418
        $listQuery = <<<EOF
419
query (\$page: Int!) {
420
    {$this->lowerNamePlural}(page: \$page) {
421
        data {
422
            id
423
            $cardFieldParameters
424
        }
425
      
426
        paginatorInfo {
427
            currentPage
428
            perPage
429
            total
430
            lastPage
431
        }
432
    }
433
}
434
EOF;
435
        $this->collection->push(
436
            new GeneratedItem(
437
                GeneratedItem::TYPE_FRONTEND,
438
                $listQuery,
439
                $this->model->getName() . '/queryList.graphql'
440
            )
441
        );
442
443
        /*
444
         * table
445
         */
446
        $tableFieldNames = array_map(
447
            function (Field $field) {
448
                return $field->getName();
449
            },
450
            $this->tableFields
451
        );
452
        $tableFieldParameters = implode("\n", $tableFieldNames);
453
454
        $tableQuery = <<<EOF
455
query (\$page: Int!) {
456
    {$this->lowerNamePlural}(page: \$page) {
457
        data {
458
            id
459
            $tableFieldParameters
460
        }
461
      
462
        paginatorInfo {
463
            currentPage
464
            perPage
465
            total
466
            lastPage
467
        }
468
    }
469
}
470
EOF;
471
        $this->collection->push(
472
            new GeneratedItem(
473
                GeneratedItem::TYPE_FRONTEND,
474
                $tableQuery,
475
                $this->model->getName() . '/queryTable.graphql'
476
            )
477
        );
478
479
        /*
480
         * item
481
         */
482
        $graphqlQuery = $this->model->mapFields(
483
            function (Field $f) {
484
                return \Modelarium\Frontend\Util::fieldShow($f) ? $f->toGraphqlQuery() : null;
485
            }
486
        );
487
        $graphqlQuery = join("\n", array_filter($graphqlQuery));
488
489
        $itemQuery = <<<EOF
490
query (\$id: ID!) {
491
    {$this->lowerName}(id: \$id) {
492
        id
493
        $graphqlQuery
494
        can
495
    }
496
}
497
EOF;
498
499
        $this->collection->push(
500
            new GeneratedItem(
501
                GeneratedItem::TYPE_FRONTEND,
502
                $itemQuery,
503
                $this->model->getName() . '/queryItem.graphql'
504
            )
505
        );
506
507
        $upsertMutation = <<<EOF
508
mutation upsert(\${$this->lowerName}: {$this->studlyName}Input!) {
509
    upsert{$this->studlyName}(input: \${$this->lowerName}) {
510
        id
511
    }
512
}
513
EOF;
514
        $this->collection->push(
515
            new GeneratedItem(
516
                GeneratedItem::TYPE_FRONTEND,
517
                $upsertMutation,
518
                $this->model->getName() . '/mutationUpsert.graphql'
519
            )
520
        );
521
522
        $deleteMutation = <<<EOF
523
mutation delete(\$id: ID!) {
524
    delete{$this->studlyName}(id: \$id) {
525
        id
526
    }
527
}
528
EOF;
529
        $this->collection->push(
530
            new GeneratedItem(
531
                GeneratedItem::TYPE_FRONTEND,
532
                $deleteMutation,
533
                $this->model->getName() . '/mutationDelete.graphql'
534
            )
535
        );
536
    }
537
538
    protected function makeVueIndex(): void
539
    {
540
        $path = $this->model->getName() . '/index.js';
541
        $name = $this->studlyName;
542
543
        $items = [
544
            'Card',
545
            'Edit',
546
            'List',
547
            'Show',
548
            'Table',
549
        ];
550
551
        $import = array_map(
552
            function ($i) use ($name) {
553
                return "import {$name}$i from './{$name}$i.vue';";
554
            },
555
            $items
556
        );
557
558
        $export = array_map(
559
            function ($i) use ($name) {
560
                return "    {$name}$i,\n";
561
            },
562
            $items
563
        );
564
565
        $this->collection->push(
566
            new GeneratedItem(
567
                GeneratedItem::TYPE_FRONTEND,
568
                implode("\n", $import) . "\n" .
569
                "export {\n" .
570
                implode("\n", $export) . "\n};\n",
571
                $path
572
            )
573
        );
574
    }
575
576
    protected function makeVueRoutes(): void
577
    {
578
        $path = $this->model->getName() . '/routes.js';
579
580
        $this->collection->push(
581
            new GeneratedItem(
582
                GeneratedItem::TYPE_FRONTEND,
583
                $this->templateFile(
584
                    $this->stubDir . "/routes.mustache.js",
585
                    // TODO: document routeBase renderable parameter
586
                    [ 'routeName' => $this->model->getRenderable('routeBase', $this->lowerName) ]
587
                ),
588
                $path
589
            )
590
        );
591
    }
592
593
    protected function makeJSModel(): void
594
    {
595
        $path = $this->model->getName() . '/model.js';
596
        $modelValues = $this->model->getDefault();
597
        $modelValues['id'] = 0;
598
        $modelJS = 'const model = ' . json_encode($modelValues) .
599
            ";\n\nexport default model;\n";
600
        
601
        $this->collection->push(
602
            new GeneratedItem(
603
                GeneratedItem::TYPE_FRONTEND,
604
                $modelJS,
605
                $path
606
            )
607
        );
608
    }
609
}
610