FrontendVueGenerator::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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

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

449
        $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...
450
            $dir = $basepath . '/' . $name;
451
            $import = [];
452
            $export = [];
453
            foreach (scandir($dir) as $i) {
454
                if (StringUtil::endsWith($i, '.vue')) {
455
                    $name = substr($i, 0, -4);
456
                    $import[] = "import $name from './$name.vue';";
457
                    $export[] = "    {$name},";
458
                }
459
            }
460
            return implode("\n", $import) . "\n\n" .
461
                "export {\n" .
462
                implode("\n", $export) . "\n};\n";
463
        };
464
465
        $this->getCollection()->push(
466
            new GeneratedItem(
467
                GeneratedItem::TYPE_FRONTEND,
468
                $contents,
469
                $path
470
            )
471
        );
472
    }
473
474
    protected function makeVueIndexDynamic(): void
475
    {
476
        $path = $this->generator->getModel()->getName() . '/index.dynamic.js';
477
        $name = $this->generator->getStudlyName();
478
479
        $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

479
        $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...
480
            $dir = $basepath . '/' . $name;
481
            $import = [];
482
            $export = [];
483
            foreach (scandir($dir) as $i) {
484
                if (StringUtil::endsWith($i, '.vue')) {
485
                    $componentName = substr($i, 0, -4);
486
                    $import[] = "const $componentName = () => import(/* webpackChunkName: \"$name\" */ './$componentName.vue');";
487
                    $export[] = "    {$componentName},";
488
                }
489
            }
490
            return implode("\n", $import) . "\n\n" .
491
                "export {\n" .
492
                implode("\n", $export) . "\n};\n";
493
        };
494
        $this->getCollection()->push(
495
            new GeneratedItem(
496
                GeneratedItem::TYPE_FRONTEND,
497
                $contents,
498
                $path
499
            )
500
        );
501
    }
502
503
    protected function makeVueRoutes(): void
504
    {
505
        $path = $this->generator->getModel()->getName() . '/routes.js';
506
507
        $this->getCollection()->push(
508
            new GeneratedItem(
509
                GeneratedItem::TYPE_FRONTEND,
510
                $this->generator->templateFile(
511
                    $this->getStubDir() . "/routes.mustache.js",
512
                    [
513
                        'routeName' => $this->generator->getRouteBase(),
514
                        'keyAttribute' => $this->generator->getKeyAttribute()
515
                    ]
516
                ),
517
                $path
518
            )
519
        );
520
    }
521
522
    protected function makeVueCrud(): void
523
    {
524
        $path = $this->generator->getModel()->getName() . '/crud.js';
525
526
        $this->getCollection()->push(
527
            new GeneratedItem(
528
                GeneratedItem::TYPE_FRONTEND,
529
                $this->generator->templateFile(
530
                    $this->getStubDir() . "/crud.mustache.js",
531
                    $this->generator->templateParameters
532
                ),
533
                $path
534
            )
535
        );
536
    }
537
}
538