Passed
Push — master ( 05064d...ef0947 )
by Bruno
04:00
created

FrontendGenerator::getRouteBase()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
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 GraphQL\Type\Definition\CustomScalarType;
17
use GraphQL\Type\Definition\FieldArgument;
18
use GraphQL\Type\Definition\NonNull;
19
use GraphQL\Type\Definition\ScalarType as DefinitionScalarType;
20
use Modelarium\Exception\Exception;
21
use Modelarium\Parser;
22
use Modelarium\GeneratedCollection;
23
use Modelarium\GeneratedItem;
24
use Modelarium\GeneratorInterface;
25
use Modelarium\GeneratorNameTrait;
26
use Modelarium\Types\ScalarType;
27
28
use function Safe\file_get_contents;
29
use function Safe\json_encode;
30
31
class FrontendGenerator implements GeneratorInterface
32
{
33
    use GeneratorNameTrait;
34
35
    /**
36
     * @var FrameworkComposer
37
     */
38
    protected $composer = null;
39
40
    /**
41
     * @var Model
42
     */
43
    protected $model = null;
44
45
    /**
46
     * @var Parser
47
     */
48
    protected $parser = null;
49
50
    /**
51
     * @var GeneratedCollection
52
     */
53
    protected $collection;
54
55
    /**
56
     *
57
     * @var string
58
     */
59
    protected $stubDir = __DIR__ . '/stubs';
60
61
    /**
62
     * Attributed used to fetch the item. It must be a unique key, and
63
     * defaults to 'id'.
64
     *
65
     * @var string
66
     */
67
    protected $keyAttribute = 'id';
68
69
    /**
70
     * Attributed used to fetch the item. It must be a unique key, and
71
     * defaults to lowerName.
72
     *
73
     * @var string
74
     */
75
    protected $routeBase = '';
76
77
    /**
78
     * String substitution
79
     *
80
     * @var array
81
     */
82
    protected $templateParameters = [];
83
84
    /**
85
     * Card fields
86
     *
87
     * @var Field[]
88
     */
89
    protected $cardFields = [];
90
91
    /**
92
     * Table fields
93
     *
94
     * @var Field[]
95
     */
96
    protected $tableFields = [];
97
98
    public function __construct(FrameworkComposer $composer, Model $model, Parser $parser)
99
    {
100
        $this->composer = $composer;
101
        $this->model = $model;
102
        // TODO: document keyAttribute renderable parameter
103
        $this->keyAttribute = $model->getRenderable('keyAttribute', 'id');
104
        $this->routeBase = $this->model->getRenderable('routeBase', $this->lowerName);
105
        $this->parser = $parser;
106
        $this->setBaseName($model->getName());
107
        $this->buildTemplateParameters();
108
    }
109
110
    public function generate(): GeneratedCollection
111
    {
112
        $this->collection = new GeneratedCollection();
113
        if ($this->model->getExtradata('frontendSkip')) {
114
            return $this->collection;
115
        }
116
117
        /**
118
         * @var FrameworkVue $vue
119
         */
120
        $vue = $this->composer->getByName('Vue');
121
        // $blade = FrameworkComposer::getByName('Blade');
122
123
        $this->makeJSModel();
124
125
        if ($vue !== null) {
126
            $vueGenerator = new FrontendVueGenerator($this);
127
            $vueGenerator->generate();
128
        }
129
130
        $this->makeGraphql();
131
132
        return $this->collection;
133
    }
134
135
    public function buildTemplateParameters(): void
136
    {
137
        $hasVue = $this->composer->getByName('Vue');
138
139
        $this->cardFields = $this->model->filterField(
140
            function (Field $field) {
141
                return $field->getRenderable('card', false);
142
            }
143
        );
144
        $this->tableFields = $this->model->filterField(
145
            function (Field $field) {
146
                return $field->getRenderable('table', false);
147
            }
148
        );
149
150
        $buttonCreate = $this->composer->nodeElement(
151
            'Button',
152
            [
153
                Button::TYPE => 'a',
154
                Button::ATTRIBUTES => [
155
                    'href' => "/{$this->routeBase}/edit"
156
                ] + ($hasVue ? [ "v-if" => 'can.create' ]: []),
157
            ]
158
        )->setContent(
159
            '<i class="fa fa-plus"></i> Add new',
160
            true,
161
            true
162
        )->getRenderHTML();
163
164
        $buttonEdit = $this->composer->nodeElement(
165
            'Button',
166
            [
167
                Button::TYPE => ($hasVue ? 'router-link' : 'a'),
168
                Button::ATTRIBUTES => [
169
                    ':to' => "'/{$this->lowerName}/' + model.{$this->keyAttribute} + '/edit'"
170
                ] + ($hasVue ? [ "v-if" => 'can.edit' ]: []),
171
            ]
172
        )->setContent(
173
            '<i class="fa fa-pencil"></i> Edit',
174
            true,
175
            true
176
        )->getRenderHTML();
177
178
        $buttonDelete = $this->composer->nodeElement(
179
            'Button',
180
            [
181
                Button::TYPE => 'a',
182
                Button::COLOR => Button::COLOR_WARNING,
183
                Button::ATTRIBUTES => [
184
                    'href' => '#',
185
                    '@click.prevent' => 'remove'
186
                ] + ($hasVue ? [ "v-if" => 'can.delete' ]: []),
187
            ]
188
        )->setContent(
189
            '<i class="fa fa-trash"></i> Delete',
190
            true,
191
            true
192
        )->getRenderHTML();
193
194
        /*
195
         * table
196
         */
197
        $table = $this->composer->nodeElement(
198
            'Table',
199
            [
200
                Table::ROW_NAMES => array_map(
201
                    function (Field $field) {
202
                        return $field->getRenderable(Renderable::LABEL, $field->getName());
203
                    },
204
                    $this->tableFields
205
                ),
206
                Table::STRIPED => true
207
            ]
208
        );
209
        /**
210
         * @var HTMLNode $tbody
211
         */
212
        $tbody = $table->get('tbody')[0];
213
        $tbody->setContent(
214
            '<' . $this->studlyName . 'TableItem v-for="l in list" :key="l.id" v-bind="l"></' . $this->studlyName . 'TableItem>',
215
            true,
216
            true
217
        );
218
        $titleFields = $this->model->filterField(
219
            function (Field $field) {
220
                return $field->getRenderable('title', false);
221
            }
222
        );
223
224
        $spinner = $this->composer->nodeElement('Spinner')
225
        ->addAttribute(
226
            'v-show',
227
            'isLoading'
228
        )->getRenderHTML();
229
        $this->templateParameters = [
230
            'buttonSubmit' => $this->composer->element(
231
                'Button',
232
                [
233
                    Button::TYPE => 'submit',
234
                    Element::LABEL => 'Submit'
235
                ]
236
            ),
237
            'buttonCreate' => $buttonCreate,
238
            'buttonEdit' => $buttonEdit,
239
            'buttonDelete' => $buttonDelete,
240
            'filters' => $this->getFilters(),
241
            // TODO 'hasCan' => $this->model
242
            'keyAttribute' => $this->keyAttribute,
243
            'spinner' => $spinner,
244
            'tablelist' => $table->getRenderHTML(),
245
            'tableItemFields' => array_keys(array_map(function (Field $f) {
246
                return $f->getName();
247
            }, $this->tableFields)),
248
            'typeTitle' => $this->studlyName,
249
            'titleField' => array_key_first($titleFields) ?: 'id'
250
        ];
251
    }
252
253
    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

253
    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

253
    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...
254
    {
255
        $x = $this->templateFile(
256
            $stub,
257
            array_merge(
258
                $this->templateParameters,
259
                $data
260
            )
261
        );
262
        return $x;
263
    }
264
265
    /**
266
     * Filters for query, which might be used by component for rendering and props
267
     *
268
     * @return array
269
     */
270
    protected function getFilters(): array
271
    {
272
        $query = $this->parser->getSchema()->getQueryType();
273
        $filters = [];
274
        // find the query that matches our pagination model
275
        foreach ($query->getFields() as $field) {
276
            if ($field->name === $this->lowerNamePlural) {
277
                // found. parse its parameters.
278
279
                /**
280
                 * @var FieldArgument $arg
281
                 */
282
                foreach ($field->args as $arg) {
283
                    // if you need to parse directives: $directives = $arg->astNode->directives;
284
285
                    $type = $arg->getType();
286
287
                    $required = false;
288
                    if ($type instanceof NonNull) {
289
                        $type = $type->getWrappedType();
290
                        $required = true;
291
                    }
292
293
                    if ($type instanceof CustomScalarType) {
294
                        $typename = $type->astNode->name->value;
295
                    } elseif ($type instanceof DefinitionScalarType) {
296
                        $typename = $type->name;
297
                    }
298
                    // } elseif ($type instanceof Input with @spread) {
299
                    else {
300
                        // TODO throw new Exception("Unsupported type {$arg->name} in query filter generation for {$this->baseName} " . get_class($type));
301
                        continue;
302
                    }
303
304
                    $filters[] = [
305
                        'name' => $arg->name,
306
                        'type' => $typename,
307
                        'required' => $required,
308
                        'requiredJSBoolean' => $required ? 'true' : 'false'
309
                    ];
310
                }
311
                break;
312
            }
313
        }
314
        return $filters;
315
    }
316
317
    protected function makeGraphql(): void
318
    {
319
        /*
320
         * card
321
         */
322
        $cardFieldNames = array_map(
323
            function (Field $field) {
324
                return $field->getName();
325
            },
326
            $this->cardFields
327
        );
328
        $cardFieldParameters = implode("\n", $cardFieldNames);
329
330
        // generate filters for query
331
        $filters = $this->templateParameters['filters'] ?? [];
332
        if ($filters) {
333
            $filtersQuery = ', ' . join(
334
                ', ',
335
                array_map(
336
                    function ($item) {
337
                        return '$' . $item['name']  . ': ' . $item['type'] . ($item['required'] ? '!' : '');
338
                    },
339
                    $filters
340
                )
341
            );
342
            $filtersParams = ', ' . join(
343
                ', ',
344
                array_map(
345
                    function ($item) {
346
                        return $item['name'] . ': $' . $item['name'];
347
                    },
348
                    $filters
349
                )
350
            );
351
        } else {
352
            $filtersQuery = $filtersParams = '';
353
        }
354
355
        $listQuery = <<<EOF
356
query (\$page: Int!$filtersQuery) {
357
    {$this->lowerNamePlural}(page: \$page$filtersParams) {
358
        data {
359
            id
360
            $cardFieldParameters
361
        }
362
      
363
        paginatorInfo {
364
            currentPage
365
            perPage
366
            total
367
            lastPage
368
        }
369
    }
370
}
371
EOF;
372
        $this->collection->push(
373
            new GeneratedItem(
374
                GeneratedItem::TYPE_FRONTEND,
375
                $listQuery,
376
                $this->model->getName() . '/queryList.graphql'
377
            )
378
        );
379
380
        /*
381
         * table
382
         */
383
        $tableFieldNames = array_map(
384
            function (Field $field) {
385
                return $field->getName();
386
            },
387
            $this->tableFields
388
        );
389
        $tableFieldParameters = implode("\n", $tableFieldNames);
390
391
        $tableQuery = <<<EOF
392
query (\$page: Int!$filtersQuery) {
393
    {$this->lowerNamePlural}(page: \$page$filtersParams) {
394
        data {
395
            id
396
            $tableFieldParameters
397
        }
398
      
399
        paginatorInfo {
400
            currentPage
401
            perPage
402
            total
403
            lastPage
404
        }
405
    }
406
}
407
EOF;
408
        $this->collection->push(
409
            new GeneratedItem(
410
                GeneratedItem::TYPE_FRONTEND,
411
                $tableQuery,
412
                $this->model->getName() . '/queryTable.graphql'
413
            )
414
        );
415
416
        /*
417
         * item
418
         */
419
        $graphqlQuery = $this->model->mapFields(
420
            function (Field $f) {
421
                return \Modelarium\Frontend\Util::fieldShow($f) ? $f->toGraphqlQuery() : null;
422
            }
423
        );
424
        $graphqlQuery = join("\n", array_filter($graphqlQuery));
425
426
        $hasCan = method_exists($this->model, 'getCanAttribute');
427
        $canAttribute = $hasCan ? 'can' : '';
428
        if ($this->keyAttribute === 'id') {
429
            $keyAttributeType = 'ID';
430
        } else {
431
            $keyAttributeType = $this->model->getField($this->keyAttribute)->getDatatype()->getGraphqlType();
432
        }
433
434
        $itemQuery = <<<EOF
435
query (\${$this->keyAttribute}: {$keyAttributeType}!) {
436
    {$this->lowerName}({$this->keyAttribute}: \${$this->keyAttribute}) {
437
        id
438
        $graphqlQuery
439
        $canAttribute
440
    }
441
}
442
EOF;
443
444
        $this->collection->push(
445
            new GeneratedItem(
446
                GeneratedItem::TYPE_FRONTEND,
447
                $itemQuery,
448
                $this->model->getName() . '/queryItem.graphql'
449
            )
450
        );
451
452
        $upsertMutation = <<<EOF
453
mutation upsert(\${$this->lowerName}: {$this->studlyName}Input!) {
454
    upsert{$this->studlyName}(input: \${$this->lowerName}) {
455
        id
456
    }
457
}
458
EOF;
459
        $this->collection->push(
460
            new GeneratedItem(
461
                GeneratedItem::TYPE_FRONTEND,
462
                $upsertMutation,
463
                $this->model->getName() . '/mutationUpsert.graphql'
464
            )
465
        );
466
467
        $deleteMutation = <<<EOF
468
mutation delete(\$id: ID!) {
469
    delete{$this->studlyName}(id: \$id) {
470
        id
471
    }
472
}
473
EOF;
474
        $this->collection->push(
475
            new GeneratedItem(
476
                GeneratedItem::TYPE_FRONTEND,
477
                $deleteMutation,
478
                $this->model->getName() . '/mutationDelete.graphql'
479
            )
480
        );
481
    }
482
    
483
    protected function makeJSModel(): void
484
    {
485
        $path = $this->model->getName() . '/model.js';
486
        $modelValues = $this->model->getDefault();
487
        $modelValues['id'] = 0;
488
        $modelJS = 'const model = ' . json_encode($modelValues) .
489
            ";\n\nexport default model;\n";
490
        
491
        $this->collection->push(
492
            new GeneratedItem(
493
                GeneratedItem::TYPE_FRONTEND,
494
                $modelJS,
495
                $path
496
            )
497
        );
498
    }
499
500
    /**
501
     * Get the value of composer
502
     *
503
     * @return  FrameworkComposer
504
     */
505
    public function getComposer(): FrameworkComposer
506
    {
507
        return $this->composer;
508
    }
509
510
    /**
511
     * Get the value of collection
512
     *
513
     * @return  GeneratedCollection
514
     */
515
    public function getCollection(): GeneratedCollection
516
    {
517
        return $this->collection;
518
    }
519
520
    /**
521
     * Get card fields
522
     *
523
     * @return  Field[]
524
     */
525
    public function getCardFields(): array
526
    {
527
        return $this->cardFields;
528
    }
529
530
    /**
531
     * Get table fields
532
     *
533
     * @return  Field[]
534
     */
535
    public function getTableFields(): array
536
    {
537
        return $this->tableFields;
538
    }
539
540
    /**
541
     * Get defaults to 'id'.
542
     *
543
     * @return  string
544
     */
545
    public function getKeyAttribute(): string
546
    {
547
        return $this->keyAttribute;
548
    }
549
550
    /**
551
     * Get defaults to lowerName.
552
     *
553
     * @return  string
554
     */
555
    public function getRouteBase(): string
556
    {
557
        return $this->routeBase;
558
    }
559
560
    /**
561
     * Get the value of model
562
     *
563
     * @return  Model
564
     */ 
565
    public function getModel()
566
    {
567
        return $this->model;
568
    }
569
570
    /**
571
     * Get the value of stubDir
572
     *
573
     * @return  string
574
     */ 
575
    public function getStubDir()
576
    {
577
        return $this->stubDir;
578
    }
579
}
580