Passed
Push — master ( 972703...55310a )
by Bruno
03:26
created

FrontendGenerator::makeGraphql()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 182
Code Lines 95

Duplication

Lines 0
Ratio 0 %

Importance

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

261
    public function templateCallback(string $stub, Framework $f, 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 $f 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

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