Passed
Push — master ( 2fa866...3020a3 )
by Bruno
07:44
created

FrontendGenerator::generate()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 53
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 35
nc 2
nop 0
dl 0
loc 53
rs 9.36
c 1
b 0
f 0
ccs 0
cts 29
cp 0
crap 12

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\Blade\Framework as FrameworkBlade;
11
use Formularium\Frontend\HTML\Element\Button;
12
use Formularium\Frontend\HTML\Element\Table;
13
use Formularium\Frontend\Vue\Element\Pagination as PaginationVue;
14
use Formularium\Frontend\Vue\Framework as FrameworkVue;
15
use Formularium\HTMLNode;
16
use Formularium\Renderable;
17
use Modelarium\GeneratedCollection;
18
use Modelarium\GeneratedItem;
19
use Modelarium\GeneratorInterface;
20
use Modelarium\GeneratorNameTrait;
21
22
use function Safe\file_get_contents;
23
use function Safe\json_encode;
24
25
class FrontendGenerator implements GeneratorInterface
26
{
27
    use GeneratorNameTrait;
28
29
    /**
30
     * @var FrameworkComposer
31
     */
32
    protected $composer = null;
33
34
    /**
35
     * @var Model
36
     */
37
    protected $model = null;
38
39
    /**
40
     * @var GeneratedCollection
41
     */
42
    protected $collection;
43
44
    /**
45
     *
46
     * @var string
47
     */
48
    protected $stubDir = __DIR__ . '/stubs';
49
50
    /**
51
     * String substitution
52
     *
53
     * @var array
54
     */
55
    protected $templateParameters = [];
56
57
    /**
58
     * Card fields
59
     *
60
     * @var Field[]
61
     */
62
    protected $cardFields = [];
63
64
    /**
65
     * Table fields
66
     *
67
     * @var Field[]
68
     */
69
    protected $tableFields = [];
70
71
    public function __construct(FrameworkComposer $composer, Model $model)
72
    {
73
        $this->composer = $composer;
74
        $this->model = $model;
75
        $this->setBaseName($model->getName());
76
        $this->buildTemplateParameters();
77
    }
78
79
    public function generate(): GeneratedCollection
80
    {
81
        $this->collection = new GeneratedCollection();
82
83
        /**
84
         * @var FrameworkVue $vue
85
         */
86
        $vue = $this->composer->getByName('Vue');
87
        // $blade = FrameworkComposer::getByName('Blade');
88
89
        if ($vue !== null) {
90
            $cardFieldNames = array_map(function (Field $f) {
91
                return $f->getName();
92
            }, $this->cardFields);
93
            $tableFieldNames = array_map(function (Field $f) {
94
                return $f->getName();
95
            }, $this->tableFields);
96
97
            $vue->setFieldModelVariable('model.');
98
            $vue->setExtraProps([
99
                [
100
                    'name' => 'id',
101
                    'type' => 'String',
102
                    'required' => true
103
                ]
104
            ]);
105
            $this->vuePublish();
106
            $this->makeVuePaginationComponent();
107
            $this->makeJSModel();
108
            $this->makeVue($vue, 'Card', 'viewable', $cardFieldNames);
109
            $this->makeVue($vue, 'List', 'viewable');
110
            $this->makeVue($vue, 'Table', 'viewable');
111
            $this->makeVue($vue, 'TableItem', 'viewable', $tableFieldNames);
112
            $this->makeVue($vue, 'Show', 'viewable');
113
            $this->makeVue($vue, 'Edit', 'editable');
114
            $this->makeVue(
115
                $vue,
116
                'Form',
117
                'editable',
118
                function (Field $f) {
119
                    if (!$f->getExtradata('modelFillable', false)) {
0 ignored issues
show
Bug introduced by
The method getExtradata() does not exist on Formularium\Field. ( Ignorable by Annotation )

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

119
                    if (!$f->/** @scrutinizer ignore-call */ getExtradata('modelFillable', false)) {

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
                        return false;
121
                    }
122
                    return true;
123
                }
124
            );
125
            $this->makeVueRoutes();
126
            $this->makeVueIndex();
127
        }
128
129
        $this->makeGraphql();
130
131
        return $this->collection;
132
    }
133
134
    protected function vuePublish(): void
135
    {
136
        $this->collection->push(
137
            new GeneratedItem(
138
                GeneratedItem::TYPE_FRONTEND,
139
                file_get_contents(__DIR__ . "/Vue/Renderable/RelationshipAutocomplete.vue"),
140
                "Modelarium/RelationshipAutocomplete.vue"
141
            )
142
        );
143
        $this->collection->push(
144
            new GeneratedItem(
145
                GeneratedItem::TYPE_FRONTEND,
146
                file_get_contents(__DIR__ . "/Vue/Renderable/RelationshipSelect.vue"),
147
                "Modelarium/RelationshipSelect.vue"
148
            )
149
        );
150
        // $this->collection->push(
151
        //     new GeneratedItem(
152
        //         GeneratedItem::TYPE_FRONTEND,
153
        //         file_get_contents(__DIR__ . "/Vue/Renderable/RelationshipSelectMultiple.vue"),
154
        //         "Modelarium/RelationshipSelectMultiple.vue"
155
        //     )
156
        // );
157
    }
158
159
    protected function makeVuePaginationComponent(): void
160
    {
161
        $pagination = $this->composer->nodeElement(
162
            'Pagination',
163
            [
164
            ]
165
        );
166
        $html = $pagination->getRenderHTML();
167
        $script = PaginationVue::script();
168
169
        $this->collection->push(
170
            new GeneratedItem(
171
                GeneratedItem::TYPE_FRONTEND,
172
                "<template>\n$html\n</template>\n<script>\n$script\n</script>\n",
173
                "Modelarium/Pagination.vue"
174
            )
175
        );
176
    }
177
178
    protected function buildTemplateParameters(): void
179
    {
180
        $this->cardFields = $this->model->filterField(
181
            function (Field $field) {
182
                return $field->getRenderable('card', false);
183
            }
184
        );
185
        $this->tableFields = $this->model->filterField(
186
            function (Field $field) {
187
                return $field->getRenderable('table', false);
188
            }
189
        );
190
        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...
191
            $this->tableFields = $this->cardFields;
192
        }
193
194
        $buttonCreate = $this->composer->nodeElement(
195
            'Button',
196
            [
197
                Button::TYPE => 'a',
198
                Button::ATTRIBUTES => ['href' => "/{$this->lowerName}/edit" ],
199
            ]
200
        )->setContent(
201
            '<i class="fa fa-plus"></i> Add new',
202
            true,
203
            true
204
        )->getRenderHTML();
205
206
        $buttonEdit = $this->composer->nodeElement(
207
            'Button',
208
            [
209
                Button::TYPE => ($this->composer->getByName('Vue') ? 'router-link' : 'a'),
210
                Button::ATTRIBUTES => [':to' => "'/{$this->lowerName}/' + model.id + '/edit'"],
211
            ]
212
        )->setContent(
213
            '<i class="fa fa-pencil"></i> Edit',
214
            true,
215
            true
216
        )->getRenderHTML();
217
218
        $buttonDelete = $this->composer->nodeElement(
219
            'Button',
220
            [
221
                Button::TYPE => 'a',
222
                Button::COLOR => Button::COLOR_WARNING,
223
                Button::ATTRIBUTES => [
224
                    'href' => '#',
225
                    '@click.prevent' => 'remove'
226
                ],
227
            ]
228
        )->setContent(
229
            '<i class="fa fa-trash"></i> Delete',
230
            true,
231
            true
232
        )->getRenderHTML();
233
234
        /*
235
         * table
236
         */
237
        $table = $this->composer->nodeElement(
238
            'Table',
239
            [
240
                Table::ROW_NAMES => array_map(
241
                    function (Field $field) {
242
                        return $field->getRenderable(Renderable::LABEL, $field->getName());
243
                    },
244
                    $this->tableFields
245
                ),
246
                Table::STRIPED => true
247
            ]
248
        );
249
        /**
250
         * @var HTMLNode $tbody
251
         */
252
        $tbody = $table->get('tbody')[0];
253
        $tbody->setContent(
254
            '<' . $this->studlyName . 'TableItem v-for="l in list" :key="l.id" v-bind="l"></' . $this->studlyName . 'TableItem>',
255
            true,
256
            true
257
        );
258
        $titleFields = $this->model->filterField(
259
            function (Field $field) {
260
                return $field->getRenderable('title', false);
261
            }
262
        );
263
264
        $spinner = $this->composer->nodeElement('Spinner')
265
        ->addAttribute(
266
            'v-if',
267
            'isLoading'
268
        )->getRenderHTML();
269
        $this->templateParameters = [
270
            'buttonSubmit' => $this->composer->element(
271
                'Button',
272
                [
273
                    Button::TYPE => 'submit',
274
                    Element::LABEL => 'Submit'
275
                ]
276
            ),
277
            'buttonCreate' => $buttonCreate,
278
            'buttonEdit' => $buttonEdit,
279
            'buttonDelete' => $buttonDelete,
280
            // TODO 'hasCan' => $this->model
281
            'spinner' => $spinner,
282
            'tablelist' => $table->getRenderHTML(),
283
            'tableItemFields' => array_keys(array_map(function (Field $f) {
284
                return $f->getName();
285
            }, $this->tableFields)),
286
            'titleField' => array_key_first($titleFields) ?: 'id'
287
        ];
288
    }
289
290
    public function templateCallback(string $stub, FrameworkVue $vue, array $data, Model $m): string
0 ignored issues
show
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

290
    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...
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

290
    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...
291
    {
292
        $x = $this->templateFile(
293
            $stub,
294
            array_merge(
295
                $this->templateParameters,
296
                $data
297
            )
298
        );
299
        return $x;
300
    }
301
302
    /**
303
     * @param FrameworkVue $vue
304
     * @param string $component
305
     * @param string $mode
306
     * @param string[]|callable $restrictFields
307
     * @return void
308
     */
309
    protected function makeVue(FrameworkVue $vue, string $component, string $mode, $restrictFields = null): void
310
    {
311
        $path = $this->model->getName() . '/' .
312
            $this->model->getName() . $component . '.vue';
313
314
        $stub = $this->stubDir . "/Vue{$component}.mustache.vue";
315
316
        if ($mode == 'editable') {
317
            $vue->setEditableTemplate(
318
                function (FrameworkVue $vue, array $data, Model $m) use ($stub) {
319
                    return $this->templateCallback($stub, $vue, $data, $m);
320
                }
321
            );
322
323
            $this->collection->push(
324
                new GeneratedItem(
325
                    GeneratedItem::TYPE_FRONTEND,
326
                    $this->model->editable($this->composer, [], $restrictFields),
327
                    $path
328
                )
329
            );
330
        } else {
331
            $vue->setViewableTemplate(
332
                function (FrameworkVue $vue, array $data, Model $m) use ($stub) {
333
                    return $this->templateCallback($stub, $vue, $data, $m);
334
                }
335
            );
336
337
            $this->collection->push(
338
                new GeneratedItem(
339
                    GeneratedItem::TYPE_FRONTEND,
340
                    $this->model->viewable($this->composer, [], $restrictFields),
341
                    $path
342
                )
343
            );
344
        }
345
    }
346
347
    protected function getFilters(): array
348
    {
349
        $filters = [];
350
        return $filters;
351
    }
352
353
    protected function makeGraphql(): void
354
    {
355
        /*
356
         * card
357
         */
358
        $cardFieldNames = array_map(
359
            function (Field $field) {
360
                return $field->getName();
361
            },
362
            $this->cardFields
363
        );
364
        $cardFieldParameters = implode("\n", $cardFieldNames);
365
366
        $listQuery = <<<EOF
367
query (\$page: Int!) {
368
    {$this->lowerNamePlural}(page: \$page) {
369
        data {
370
            id
371
            $cardFieldParameters
372
        }
373
      
374
        paginatorInfo {
375
            currentPage
376
            perPage
377
            total
378
            lastPage
379
        }
380
    }
381
}
382
EOF;
383
        $this->collection->push(
384
            new GeneratedItem(
385
                GeneratedItem::TYPE_FRONTEND,
386
                $listQuery,
387
                $this->model->getName() . '/queryList.graphql'
388
            )
389
        );
390
391
        /*
392
         * table
393
         */
394
        $tableFieldNames = array_map(
395
            function (Field $field) {
396
                return $field->getName();
397
            },
398
            $this->tableFields
399
        );
400
        $tableFieldParameters = implode("\n", $tableFieldNames);
401
402
        $tableQuery = <<<EOF
403
query (\$page: Int!) {
404
    {$this->lowerNamePlural}(page: \$page) {
405
        data {
406
            id
407
            $tableFieldParameters
408
        }
409
      
410
        paginatorInfo {
411
            currentPage
412
            perPage
413
            total
414
            lastPage
415
        }
416
    }
417
}
418
EOF;
419
        $this->collection->push(
420
            new GeneratedItem(
421
                GeneratedItem::TYPE_FRONTEND,
422
                $tableQuery,
423
                $this->model->getName() . '/queryTable.graphql'
424
            )
425
        );
426
427
        /*
428
         * item
429
         */
430
        $graphqlQuery = $this->model->mapFields(
0 ignored issues
show
Bug introduced by
The method mapFields() 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

430
        /** @scrutinizer ignore-call */ 
431
        $graphqlQuery = $this->model->mapFields(

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...
431
            function (Field $f) {
432
                // TODO, hidden is not available. we don't have the directives here.
433
                if ($f->getRenderable('modelHidden', false) !== false) {
434
                    return null;
435
                }
436
                return $f->toGraphqlQuery();
437
            }
438
        );
439
        $graphqlQuery = join("\n", $graphqlQuery);
440
441
        $itemQuery = <<<EOF
442
query (\$id: ID!) {
443
    {$this->lowerName}(id: \$id) {
444
        id
445
        $graphqlQuery
446
    }
447
}
448
EOF;
449
450
        $this->collection->push(
451
            new GeneratedItem(
452
                GeneratedItem::TYPE_FRONTEND,
453
                $itemQuery,
454
                $this->model->getName() . '/queryItem.graphql'
455
            )
456
        );
457
458
        $upsertMutation = <<<EOF
459
mutation upsert(\${$this->lowerName}: {$this->studlyName}Input!) {
460
    upsert{$this->studlyName}(input: \${$this->lowerName}) {
461
        id
462
    }
463
}
464
EOF;
465
        $this->collection->push(
466
            new GeneratedItem(
467
                GeneratedItem::TYPE_FRONTEND,
468
                $upsertMutation,
469
                $this->model->getName() . '/mutationUpsert.graphql'
470
            )
471
        );
472
473
        $deleteMutation = <<<EOF
474
mutation delete(\$id: ID!) {
475
    delete{$this->studlyName}(id: \$id) {
476
        id
477
    }
478
}
479
EOF;
480
        $this->collection->push(
481
            new GeneratedItem(
482
                GeneratedItem::TYPE_FRONTEND,
483
                $deleteMutation,
484
                $this->model->getName() . '/mutationDelete.graphql'
485
            )
486
        );
487
    }
488
489
    protected function makeVueIndex(): void
490
    {
491
        $path = $this->model->getName() . '/index.js';
492
        $name = $this->studlyName;
493
494
        $items = [
495
            'Card',
496
            'Edit',
497
            'List',
498
            'Show',
499
            'Table',
500
        ];
501
502
        $import = array_map(
503
            function ($i) use ($name) {
504
                return "import {$name}$i from './{$name}$i.vue';";
505
            },
506
            $items
507
        );
508
509
        $export = array_map(
510
            function ($i) use ($name) {
511
                return "    {$name}$i,\n";
512
            },
513
            $items
514
        );
515
516
        $this->collection->push(
517
            new GeneratedItem(
518
                GeneratedItem::TYPE_FRONTEND,
519
                implode("\n", $import) . "\n" .
520
                "export {\n" .
521
                implode("\n", $export) . "\n};\n",
522
                $path
523
            )
524
        );
525
    }
526
527
    protected function makeVueRoutes(): void
528
    {
529
        $path = $this->model->getName() . '/routes.js';
530
531
        $this->collection->push(
532
            new GeneratedItem(
533
                GeneratedItem::TYPE_FRONTEND,
534
                $this->templateFile($this->stubDir . "/routes.mustache.js"),
535
                $path
536
            )
537
        );
538
    }
539
540
    protected function makeJSModel(): void
541
    {
542
        $path = $this->model->getName() . '/model.js';
543
        $modelValues = $this->model->getDefault();
544
        $modelValues['id'] = 0;
545
        $modelJS = 'const model = ' . json_encode($modelValues) .
546
            ";\n\nexport default model;\n";
547
        
548
        $this->collection->push(
549
            new GeneratedItem(
550
                GeneratedItem::TYPE_FRONTEND,
551
                $modelJS,
552
                $path
553
            )
554
        );
555
    }
556
}
557