Passed
Push — master ( 2310af...cdf82f )
by Bruno
07:53
created

FrontendVueGenerator   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 481
Duplicated Lines 0 %

Importance

Changes 7
Bugs 2 Features 0
Metric Value
eloc 267
c 7
b 2
f 0
dl 0
loc 481
rs 8.96
wmc 43

18 Methods

Rating   Name   Duplication   Size   Complexity  
A getCollection() 0 3 1
A __construct() 0 4 1
A generate() 0 33 1
A getOptions() 0 3 1
A getStubDir() 0 3 1
A vueCodeLinkItem() 0 6 1
A makeVue() 0 38 2
C buildTemplateParameters() 0 73 11
A vuePublish() 0 24 1
A makeVueRoutes() 0 15 1
A makeVuePaginationComponent() 0 16 1
A makeVueIndex() 0 26 3
A vueCard() 0 30 2
A vueLink() 0 33 4
A vueForm() 0 32 4
A vueTable() 0 3 1
A vueTableItem() 0 37 4
A makeVueIndexDynamic() 0 25 3

How to fix   Complexity   

Complex Class

Complex classes like FrontendVueGenerator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FrontendVueGenerator, and based on these observations, apply Extract Interface, too.

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 Modelarium\GeneratedCollection;
15
use Modelarium\GeneratedItem;
16
use Modelarium\Options;
17
use Formularium\StringUtil;
18
19
use function Safe\file_get_contents;
20
use function Safe\scandir;
21
use function Safe\substr;
22
23
class FrontendVueGenerator
24
{
25
    /**
26
     * @var FrontendGenerator
27
     */
28
    protected $generator = null;
29
30
    public function __construct(FrontendGenerator $generator)
31
    {
32
        $this->generator = $generator;
33
        $this->buildTemplateParameters();
34
    }
35
36
    public function getOptions(): Options
37
    {
38
        return $this->generator->getOptions();
39
    }
40
41
    public function getStubDir()
42
    {
43
        return $this->generator->getStubDir() . '/Vue/';
44
    }
45
46
    protected function getCollection(): GeneratedCollection
47
    {
48
        return $this->generator->getCollection();
49
    }
50
51
    public function generate(): void
52
    {
53
        /**
54
         * @var FrameworkVue $vue
55
         */
56
        $vue = $this->generator->getComposer()->getByName('Vue');
57
        $vueCode = $vue->getVueCode();
58
59
        // set basic data for vue
60
        $extraprops = [
61
            [
62
                'name' => 'id',
63
                'type' => 'String',
64
                'required' => true
65
            ]
66
        ];
67
        $vueCode->setExtraProps($extraprops);
68
69
        // build basic vue components
70
        $this->vuePublish();
71
        $this->makeVuePaginationComponent();
72
73
        $this->vueCard($vue);
74
        $this->vueLink($vue);
75
        $this->vueTableItem($vue);
76
        $this->vueTable($vue);
77
78
        $this->makeVue($vue, 'List', 'viewable');
79
        $this->makeVue($vue, 'Show', 'viewable');
80
        $this->makeVue($vue, 'Edit', 'editable');
81
        $this->vueForm($vue);
82
        $this->makeVueRoutes();
83
        $this->makeVueIndex();
84
    }
85
86
    public function buildTemplateParameters(): void
87
    {
88
        $hasVueRouter = $this->generator != null; //TODO: temporary true condition while we don't have a setting for this
89
90
        $hasCan = $this->generator->getModel()->getExtradataValue('hasCan', 'value', false);
91
        $routeBase = $this->generator->getRouteBase();
92
        $keyAttribute = $this->generator->getKeyAttribute();
93
        $targetAttribute = $hasVueRouter ? 'to' : 'href';
94
95
        $buttonCreate = $this->generator->getComposer()->nodeElement(
96
            'Button',
97
            [
98
                Button::TYPE => ($hasVueRouter ? 'router-link' : 'a'),
99
                Button::ATTRIBUTES => [
100
                    $targetAttribute => "/{$routeBase}/edit",
101
                ] + ($hasCan ? [ "v-if" => 'can(\'create\')' ]: []),
102
            ]
103
        )->setContent(
104
            '<i class="fa fa-plus"></i> Add new',
105
            true,
106
            true
107
        )->getRenderHTML();
108
109
        $buttonEdit = $this->generator->getComposer()->nodeElement(
110
            'Button',
111
            [
112
                Button::TYPE => ($hasVueRouter ? 'router-link' : 'a'),
113
                Button::ATTRIBUTES => [
114
                    $targetAttribute => "'/{$routeBase}/' + model.{$keyAttribute} + '/edit'",
115
                ] + ($hasCan ? [ "v-if" => 'can(\'edit\')' ]: []),
116
            ]
117
        )->setContent(
118
            '<i class="fa fa-pencil"></i> Edit',
119
            true,
120
            true
121
        )->getRenderHTML();
122
123
        $buttonDelete = $this->generator->getComposer()->nodeElement(
124
            'Button',
125
            [
126
                Button::TYPE => 'a',
127
                Button::COLOR => Button::COLOR_WARNING,
128
                Button::ATTRIBUTES => [
129
                    'href' => '#',
130
                    '@click.prevent' => 'remove',
131
                ] + ($hasCan ? [ "v-if" => 'can(\'delete\')' ]: []),
132
            ]
133
        )->setContent(
134
            '<i class="fa fa-trash"></i> Delete',
135
            true,
136
            true
137
        )->getRenderHTML();
138
139
        if (!$hasCan && $this->getOptions()->getOption('vue', 'actionButtonsNoCan') === false) {
140
            $this->generator->templateParameters['buttonCreate'] = '';
141
            $this->generator->templateParameters['buttonEdit'] = '';
142
            $this->generator->templateParameters['buttonDelete'] = '';
143
        } else {
144
            $this->generator->templateParameters['buttonCreate'] = $buttonCreate;
145
            $this->generator->templateParameters['buttonEdit'] = $buttonEdit;
146
            $this->generator->templateParameters['buttonDelete'] = $buttonDelete;
147
        }
148
        $this->generator->templateParameters['options'] = $this->getOptions()->options;
149
150
        $this->generator->templateParameters['tableItemFields'] =
151
            array_values(array_map(function (Field $f) {
152
                $required = $f->getValidator('required', false);
153
                if ($f->getDatatype()->getBasetype() === 'relationship') {
154
                    $name = $f->getName();
155
                    return "<{$name}-link " . ($required ? '' : "v-if=\"{$name}\"") . "v-bind=\"{$name}\"></{$name}-link>";
156
                }
157
                return '{{ ' . $f->getName() . ' }}';
158
            }, $this->generator->getTableFields()));
159
    }
160
161
    /**
162
     * Publishes the Vue component dependencies
163
     *
164
     * @return void
165
     */
166
    protected function vuePublish(): void
167
    {
168
        $this->getCollection()->push(
169
            new GeneratedItem(
170
                GeneratedItem::TYPE_FRONTEND,
171
                $this->generator->templateCallback(
172
                    __DIR__ . "/Vue/Renderable/RelationshipAutocomplete.vue",
173
                    $this->generator->getComposer()->getByName('Vue'),
0 ignored issues
show
Bug introduced by
It seems like $this->generator->getComposer()->getByName('Vue') can also be of type null; however, parameter $f of Modelarium\Frontend\Fron...tor::templateCallback() does only seem to accept Formularium\Framework, maybe add an additional type check? ( Ignorable by Annotation )

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

173
                    /** @scrutinizer ignore-type */ $this->generator->getComposer()->getByName('Vue'),
Loading history...
174
                    [],
175
                    $this->generator->getModel()
176
                ),
177
                "Modelarium/RelationshipAutocomplete.vue"
178
            )
179
        );
180
        $this->getCollection()->push(
181
            new GeneratedItem(
182
                GeneratedItem::TYPE_FRONTEND,
183
                $this->generator->templateCallback(
184
                    __DIR__ . "/Vue/Renderable/RelationshipSelect.vue",
185
                    $this->generator->getComposer()->getByName('Vue'),
186
                    [],
187
                    $this->generator->getModel()
188
                ),
189
                "Modelarium/RelationshipSelect.vue"
190
            )
191
        );
192
        // $this->getCollection()->push(
193
        //     new GeneratedItem(
194
        //         GeneratedItem::TYPE_FRONTEND,
195
        //         file_get_contents(__DIR__ . "/Vue/Renderable/RelationshipSelectMultiple.vue"),
196
        //         "Modelarium/RelationshipSelectMultiple.vue"
197
        //     )
198
        // );
199
    }
200
201
    protected function makeVuePaginationComponent(): void
202
    {
203
        // TODO: this is called once per type
204
        $pagination = $this->generator->getComposer()->nodeElement(
205
            'Pagination',
206
            [
207
            ]
208
        );
209
        $html = $pagination->getRenderHTML();
210
        $script = PaginationVue::script();
211
212
        $this->getCollection()->push(
213
            new GeneratedItem(
214
                GeneratedItem::TYPE_FRONTEND,
215
                "<template>\n$html\n</template>\n<script>\n$script\n</script>\n",
216
                "Modelarium/Pagination.vue"
217
            )
218
        );
219
    }
220
221
    /**
222
     * Appends computed item
223
     *
224
     * @param FrameworkVue $vue
225
     * @return void
226
     */
227
    protected function vueCodeLinkItem(FrameworkVue $vue): void
228
    {
229
        $vue->getVueCode()->appendComputed(
230
            'link',
231
            'return "/' . $this->generator->getRouteBase() .
232
                '/" + this.escapeIdentifier(this.' . $this->generator->getKeyAttribute() . ')'
233
        );
234
    }
235
236
    protected function vueCard(FrameworkVue $vue): void
237
    {
238
        $vueCode = $vue->getVueCode();
239
        // set basic data for vue
240
        $extraprops = [
241
            [
242
                'name' => 'id',
243
                'type' => 'String',
244
                'required' => true
245
            ]
246
        ];
247
        $cardFieldNames = array_map(function (Field $f) {
248
            return $f->getName();
249
        }, $this->generator->getCardFields());
250
        $vueCode->setExtraProps($extraprops);
251
        $this->vueCodeLinkItem($vue);
252
253
        foreach ($this->generator->getCardFields() as $f) {
254
            $vueCode->appendExtraProp($f->getName(), [
255
                'name' => $f->getName(),
256
                'type' => $vueCode->mapTypeToJs($f->getDatatype()),
257
                'required' => true
258
            ]);
259
        }
260
        $vueCode->appendMethod(
261
            'escapeIdentifier(identifier)',
262
            $this->getOptions()->getOption('vue', 'escapeIdentifierBody')
263
        );
264
265
        $this->makeVue($vue, 'Card', 'viewable', $cardFieldNames);
266
    }
267
268
    protected function vueLink(FrameworkVue $vue): void
269
    {
270
        $vueCode = $vue->getVueCode();
271
        // set basic data for vue
272
        $vueCode->setExtraProps([]);
273
        $cardFieldNames = array_map(function (Field $f) {
274
            return $f->getName();
275
        }, $this->generator->getCardFields());
276
        foreach ($this->generator->getCardFields() as $f) {
277
            $vueCode->appendExtraProp($f->getName(), [
278
                'name' => $f->getName(),
279
                'type' => $vueCode->mapTypeToJs($f->getDatatype()),
280
                'required' => true
281
            ]);
282
        }
283
        foreach ($this->generator->getTitleFields() as $f) {
284
            $vueCode->appendExtraProp($f->getName(), [
285
                'name' => $f->getName(),
286
                'type' => $vueCode->mapTypeToJs($f->getDatatype()),
287
                'required' => true
288
            ]);
289
        }
290
291
        if (!$vueCode->getExtraProps()) {
292
            $vueCode->appendExtraProp('id', [
293
                'name' => 'id',
294
                'type' => 'String',
295
                'required' => true
296
            ]);
297
        }
298
299
        $this->vueCodeLinkItem($vue);
300
        $this->makeVue($vue, 'Link', 'viewable', $cardFieldNames);
301
    }
302
303
    public function vueTableItem(FrameworkVue $vue): void
304
    {
305
        $vueCode = $vue->getVueCode();
306
        $tableFieldNames = array_map(function (Field $f) {
307
            return $f->getName();
308
        }, $this->generator->getTableFields());
309
        $vueCode->setExtraProps([]);
310
        $vueCode->appendExtraProp(
311
            'id',
312
            [
313
                'name' => 'id',
314
                'type' => 'String',
315
                'required' => true
316
            ]
317
        );
318
319
        foreach ($this->generator->getTableFields() as $f) {
320
            /**
321
             * @var Field $f
322
             */
323
            $required = $f->getValidator('required', false);
324
            $prop = [
325
                'name' => $f->getName(),
326
                'type' => $vueCode->mapTypeToJs($f->getDatatype()),
327
                'required' => $required
328
            ];
329
            if (!$required) {
330
                if ($f->getDatatype()->getBasetype() === 'relationship') {
331
                    $prop['default'] = '() => null';
332
                } else {
333
                    $prop['default'] = $f->getDatatype()->getDefault;
0 ignored issues
show
Bug introduced by
The property getDefault does not seem to exist on Formularium\Datatype.
Loading history...
334
                }
335
            }
336
337
            $vueCode->appendExtraProp($f->getName(), $prop);
338
        }
339
        $this->makeVue($vue, 'TableItem', 'viewable', $tableFieldNames);
340
    }
341
342
    public function vueTable(FrameworkVue $vue): void
343
    {
344
        $this->makeVue($vue, 'Table', 'viewable');
345
    }
346
347
    public function vueForm(FrameworkVue $vue): void
348
    {
349
        $vueCode = $vue->getVueCode();
350
        $vueCode->setExtraProps([]);
351
352
        $createGraphqlVariables = $this->generator->getModel()->mapFields(
353
            function (Field $f) {
354
                if (!$f->getRenderable('form', true)) {
355
                    return null;
356
                }
357
                $d = $f->getDatatype();
358
                if ($d->getBasetype() == 'relationship') {
359
                    return $f->getName() . ": {connect: this.model." . $f->getName() . '}';
360
                }
361
                return $f->getName() . ": this.model." . $f->getName();
362
            }
363
        );
364
        $createGraphqlVariables = join(",\n", array_filter($createGraphqlVariables));
365
366
        $this->generator->templateParameters['createGraphqlVariables'] = $createGraphqlVariables;
367
        // they're the same now
368
        $this->generator->templateParameters['updateGraphqlVariables'] = $createGraphqlVariables;
369
370
        $this->makeVue(
371
            $vue,
372
            'Form',
373
            'editable',
374
            function (Field $f) {
375
                if (!$f->getExtradata('modelFillable')) {
376
                    return false;
377
                }
378
                return $f->getRenderable('form', true);
379
            }
380
        );
381
    }
382
383
    /**
384
     * @param FrameworkVue $vue
385
     * @param string $component
386
     * @param string $mode
387
     * @param string[]|callable $restrictFields
388
     * @return void
389
     */
390
    protected function makeVue(FrameworkVue $vue, string $component, string $mode, $restrictFields = null): void
391
    {
392
        $path = $this->generator->getModel()->getName() . '/' .
393
            $this->generator->getModel()->getName() . $component . '.vue';
394
395
        $stub = $this->getStubDir() . "/Vue{$component}.mustache.vue";
396
397
        if ($mode == 'editable') {
398
            $vue->setEditableTemplate(
399
                function (FrameworkVue $vue, array $data, Model $m) use ($stub) {
400
                    return $this->generator->templateCallback($stub, $vue, $data, $m);
401
                }
402
            );
403
404
            $this->getCollection()->push(
405
                new GeneratedItem(
406
                    GeneratedItem::TYPE_FRONTEND,
407
                    $this->generator->getModel()->editable($this->generator->getComposer(), [], $restrictFields),
408
                    $path
409
                )
410
            );
411
        } else {
412
            $vue->setViewableTemplate(
413
                function (FrameworkVue $vue, array $data, Model $m) use ($stub) {
414
                    return $this->generator->templateCallback($stub, $vue, $data, $m);
415
                }
416
            );
417
418
            $this->getCollection()->push(
419
                new GeneratedItem(
420
                    GeneratedItem::TYPE_FRONTEND,
421
                    $this->generator->getModel()->viewable($this->generator->getComposer(), [], $restrictFields),
422
                    $path
423
                )
424
            );
425
        }
426
        $vue->resetVueCode();
427
        $vue->getVueCode()->setFieldModelVariable('model.');
428
    }
429
430
    protected function makeVueIndex(): void
431
    {
432
        $path = $this->generator->getModel()->getName() . '/index.js';
433
        $name = $this->generator->getStudlyName();
434
435
        $contents = function ($basepath, $element) use ($name) {
0 ignored issues
show
Unused Code introduced by
The parameter $element 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

435
        $contents = function ($basepath, /** @scrutinizer ignore-unused */ $element) use ($name) {

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...
436
            $dir = $basepath . '/' . $name;
437
            $import = [];
438
            $export = [];
439
            foreach (scandir($dir) as $i) {
440
                if (StringUtil::endsWith($i, '.vue')) {
441
                    $name = substr($i, 0, -4);
442
                    $import[] = "import $name from './$name.vue';";
443
                    $export[] = "    {$name},";
444
                }
445
            }
446
            return implode("\n", $import) . "\n\n" .
447
                "export default {\n" .
448
                implode("\n", $export) . "\n};\n";
449
        };
450
451
        $this->getCollection()->push(
452
            new GeneratedItem(
453
                GeneratedItem::TYPE_FRONTEND,
454
                $contents,
455
                $path
456
            )
457
        );
458
    }
459
460
    protected function makeVueIndexDynamic(): void
461
    {
462
        $path = $this->generator->getModel()->getName() . '/index.dynamic.js';
463
        $name = $this->generator->getStudlyName();
464
465
        $contents = function ($basepath, $element) use ($name) {
0 ignored issues
show
Unused Code introduced by
The parameter $element 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

465
        $contents = function ($basepath, /** @scrutinizer ignore-unused */ $element) use ($name) {

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...
466
            $dir = $basepath . '/' . $name;
467
            $import = [];
468
            $export = [];
469
            foreach (scandir($dir) as $i) {
470
                if (StringUtil::endsWith($i, '.vue')) {
471
                    $name = substr($i, 0, -4);
472
                    $import[] = "const $name = () => import('./$name.vue');";
473
                    $export[] = "    {$name},";
474
                }
475
            }
476
            return implode("\n", $import) . "\n\n" .
477
                "export default {\n" .
478
                implode("\n", $export) . "\n};\n";
479
        };
480
        $this->getCollection()->push(
481
            new GeneratedItem(
482
                GeneratedItem::TYPE_FRONTEND,
483
                $contents,
484
                $path
485
            )
486
        );
487
    }
488
489
    protected function makeVueRoutes(): void
490
    {
491
        $path = $this->generator->getModel()->getName() . '/routes.js';
492
493
        $this->getCollection()->push(
494
            new GeneratedItem(
495
                GeneratedItem::TYPE_FRONTEND,
496
                $this->generator->templateFile(
497
                    $this->getStubDir() . "/routes.mustache.js",
498
                    [
499
                        'routeName' => $this->generator->getRouteBase(),
500
                        'keyAttribute' => $this->generator->getKeyAttribute()
501
                    ]
502
                ),
503
                $path
504
            )
505
        );
506
    }
507
}
508