Passed
Push — master ( 983aee...7c0677 )
by Bruno
08:11
created

FrontendGenerator::createMutation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 0
c 0
b 0
f 0
dl 0
loc 2
rs 10
cc 1
nc 1
nop 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\Blade\Framework as FrameworkBlade;
11
use Formularium\Frontend\HTML\Element\Button;
12
use Formularium\Frontend\HTML\Element\Table;
13
use Formularium\Frontend\Vue\Framework as FrameworkVue;
14
use Formularium\Renderable;
15
use Modelarium\GeneratedCollection;
16
use Modelarium\GeneratedItem;
17
use Modelarium\GeneratorInterface;
18
use Modelarium\GeneratorNameTrait;
19
20
use function Safe\file_get_contents;
21
use function Safe\json_encode;
22
23
class FrontendGenerator implements GeneratorInterface
24
{
25
    use GeneratorNameTrait;
26
27
    /**
28
     * @var FrameworkComposer
29
     */
30
    protected $composer = null;
31
32
    /**
33
     * @var Model
34
     */
35
    protected $model = null;
36
37
    /**
38
     * @var GeneratedCollection
39
     */
40
    protected $collection;
41
42
    /**
43
     *
44
     * @var string
45
     */
46
    protected $stubDir = __DIR__ . '/stubs';
47
48
    /**
49
     * String substitution
50
     *
51
     * @var string[]
52
     */
53
    protected $templateParameters = [];
54
55
    /**
56
     * Fields
57
     *
58
     * @var Field[]
59
     */
60
    protected $cardFields = [];
61
62
    public function __construct(FrameworkComposer $composer, Model $model)
63
    {
64
        $this->composer = $composer;
65
        $this->model = $model;
66
        $this->setBaseName($model->getName());
67
        $this->buildTemplateParameters();
68
    }
69
70
    public function generate(): GeneratedCollection
71
    {
72
        $this->collection = new GeneratedCollection();
73
74
        /**
75
         * @var FrameworkVue $vue
76
         */
77
        $vue = $this->composer->getByName('Vue');
78
        // $blade = FrameworkComposer::getByName('Blade');
79
80
        if ($vue !== null) {
81
            $cardFieldNames = array_map(function (Field $f) {
82
                return $f->getName();
83
            }, $this->cardFields);
84
85
            $vue->setFieldModelVariable('model.');
86
            $vue->setExtraProps([
0 ignored issues
show
Bug introduced by
The method setExtraProps() does not exist on Formularium\Frontend\Vue\Framework. ( Ignorable by Annotation )

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

86
            $vue->/** @scrutinizer ignore-call */ 
87
                  setExtraProps([

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...
87
                [
88
                    'name' => 'id',
89
                    'type' => 'String',
90
                    'required' => true
91
                ]
92
            ]);
93
            $this->makeJSModel();
94
            $this->makeVue($vue, 'Card', 'viewable', $cardFieldNames);
95
            $this->makeVue($vue, 'List', 'viewable');
96
            $this->makeVue($vue, 'Table', 'viewable');
97
            $this->makeVue($vue, 'TableItem', 'viewable', $cardFieldNames);
98
            $this->makeVue($vue, 'Show', 'viewable');
99
            $this->makeVue($vue, 'Edit', 'editable');
100
            $this->makeVue($vue, 'Form', 'editable');
101
            $this->makeVueRoutes();
102
            $this->makeVueIndex();
103
        }
104
105
        $this->makeGraphql();
106
107
        return $this->collection;
108
    }
109
110
    protected function buildTemplateParameters(): void
111
    {
112
        $this->cardFields = $this->model->filterField(
113
            function (Field $field) {
114
                return $field->getRenderable('card', false);
115
            }
116
        );
117
        $this->tableFields = $this->model->filterField(
0 ignored issues
show
Bug Best Practice introduced by
The property tableFields does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
118
            function (Field $field) {
119
                return $field->getRenderable('table', false);
120
            }
121
        );
122
        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...
123
            $this->tableFields = $this->cardFields;
124
        }
125
126
        $buttonCreate = $this->composer->nodeElement(
127
            'Button',
128
            [
129
                Button::TYPE => ($this->composer->getByName('Vue') ? 'router-link' : 'a'),
130
                Button::ATTRIBUTES => [':to' => "'/' + type + '/edit'"],
0 ignored issues
show
Bug introduced by
The constant Formularium\Frontend\HTM...ment\Button::ATTRIBUTES was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
131
            ]
132
        )->setContent(
133
            '<i class="fa fa-plus"></i>Add new',
134
            true,
135
            true
136
        )->getRenderHTML();
137
138
        /*
139
         * table
140
         */
141
        $table = $this->composer->nodeElement(
142
            'Table',
143
            [
144
                Table::ROW_NAMES => array_map(
145
                    function (Field $field) {
146
                        return $field->getRenderable(Renderable::LABEL, $field->getName());
147
                    },
148
                    $this->cardFields
149
                ),
150
            ]
151
        );
152
        /**
153
         * @var HTMLNode $tbody
154
         */
155
        $tbody = $table->get('tbody')[0];
156
        $tbody->setContent(
157
            '<' . $this->studlyName . 'TableItem v-for="l in list" :key="l.id" v-bind="l"></' . $this->studlyName . 'TableItem>',
158
            true,
159
            true
160
        );
161
        $titleFields = $this->model->filterField(
162
            function (Field $field) {
163
                return $field->getRenderable('title', false);
164
            }
165
        );
166
167
        $this->templateParameters = [
0 ignored issues
show
Documentation Bug introduced by
array('buttonSubmit' => ...($titleFields) ?: 'id') is of type array<string,array|mixed|string>, but the property $templateParameters was declared to be of type string[]. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
168
            'buttonSubmit' => $this->composer->element(
169
                'Button',
170
                [
171
                    Button::TYPE => 'submit',
172
                    Element::LABEL => 'Submit'
173
                ]
174
            ),
175
            'buttonCreate' => $buttonCreate,
176
            'tablelist' => $table->getRenderHTML(),
177
            'tableItemFields' => array_keys(array_map(function (Field $f) {
178
                return $f->getName();
179
            }, $this->tableFields)),
180
            'titleField' => array_key_first($titleFields) ?: 'id'
181
        ];
182
    }
183
184
    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

184
    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

184
    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...
185
    {
186
        $x = $this->templateFile(
187
            $stub,
188
            array_merge(
189
                $this->templateParameters,
190
                $data
191
            )
192
        );
193
        return $x;
194
    }
195
196
    protected function makeVue(FrameworkVue $vue, string $component, string $mode, $restrictFields = null): void
197
    {
198
        $path = $this->model->getName() . '/' .
199
            $this->model->getName() . $component . '.vue';
200
201
        $stub = $this->stubDir . "/Vue{$component}.mustache.vue";
202
203
        if ($mode == 'editable') {
204
            $vue->setEditableTemplate(
205
                function (FrameworkVue $vue, array $data, Model $m) use ($stub) {
206
                    return $this->templateCallback($stub, $vue, $data, $m);
207
                }
208
            );
209
210
            $this->collection->push(
211
                new GeneratedItem(
212
                    GeneratedItem::TYPE_FRONTEND,
213
                    $this->model->editable($this->composer, [], $restrictFields),
0 ignored issues
show
Unused Code introduced by
The call to Formularium\Model::editable() has too many arguments starting with $restrictFields. ( Ignorable by Annotation )

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

213
                    $this->model->/** @scrutinizer ignore-call */ 
214
                                  editable($this->composer, [], $restrictFields),

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
214
                    $path
215
                )
216
            );
217
        } else {
218
            $vue->setViewableTemplate(
219
                function (FrameworkVue $vue, array $data, Model $m) use ($stub) {
220
                    return $this->templateCallback($stub, $vue, $data, $m);
221
                }
222
            );
223
224
            $this->collection->push(
225
                new GeneratedItem(
226
                    GeneratedItem::TYPE_FRONTEND,
227
                    $this->model->viewable($this->composer, [], $restrictFields),
0 ignored issues
show
Unused Code introduced by
The call to Formularium\Model::viewable() has too many arguments starting with $restrictFields. ( Ignorable by Annotation )

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

227
                    $this->model->/** @scrutinizer ignore-call */ 
228
                                  viewable($this->composer, [], $restrictFields),

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
228
                    $path
229
                )
230
            );
231
        }
232
    }
233
234
    protected function getFilters(): array
235
    {
236
        $filters = [];
237
        return $filters;
238
    }
239
240
    protected function createMutation(): string
241
    {
242
    }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
243
244
    protected function makeGraphql(): void
245
    {
246
        $cardFieldNames = array_map(
247
            function (Field $field) {
248
                return $field->getName();
249
            },
250
            $this->cardFields
251
        );
252
        $cardFieldParameters = implode("\n", $cardFieldNames);
253
254
        $listQuery = <<<EOF
255
query (\$page: Int!) {
256
    {$this->lowerNamePlural}(page: \$page) {
257
        data {
258
            id
259
            $cardFieldParameters
260
        }
261
      
262
        paginatorInfo {
263
            currentPage
264
            perPage
265
            total
266
            lastPage
267
        }
268
    }
269
}
270
EOF;
271
272
        $this->collection->push(
273
            new GeneratedItem(
274
                GeneratedItem::TYPE_FRONTEND,
275
                $listQuery,
276
                $this->model->getName() . '/queryList.graphql'
277
            )
278
        );
279
280
        $graphqlQuery = $this->model->toGraphqlQuery();
0 ignored issues
show
Bug introduced by
The method toGraphqlQuery() 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

280
        /** @scrutinizer ignore-call */ 
281
        $graphqlQuery = $this->model->toGraphqlQuery();

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...
281
        $itemQuery = <<<EOF
282
query (\$id: ID!) {
283
    {$this->lowerName}(id: \$id) {
284
        id
285
        $graphqlQuery
286
    }
287
}
288
EOF;
289
290
        $this->collection->push(
291
            new GeneratedItem(
292
                GeneratedItem::TYPE_FRONTEND,
293
                $itemQuery,
294
                $this->model->getName() . '/queryItem.graphql'
295
            )
296
        );
297
298
        $createMutation = <<<EOF
299
mutation create(\${$this->lowerName}: Create{$this->studlyName}Input!) {
300
    create{$this->studlyName}(input: \${$this->lowerName}) {
301
        id
302
    }
303
}
304
EOF;
305
        $this->collection->push(
306
            new GeneratedItem(
307
                GeneratedItem::TYPE_FRONTEND,
308
                $createMutation,
309
                $this->model->getName() . '/mutationCreate.graphql'
310
            )
311
        );
312
    }
313
314
    protected function makeVueIndex(): void
315
    {
316
        $path = $this->model->getName() . '/index.js';
317
        $name = $this->studlyName;
318
319
        $items = [
320
            'Card',
321
            'Edit',
322
            'List',
323
            'Show',
324
            'Table',
325
        ];
326
327
        $import = array_map(
328
            function ($i) use ($name) {
329
                return "import {$name}$i from './{$name}$i.vue';";
330
            },
331
            $items
332
        );
333
334
        $export = array_map(
335
            function ($i) use ($name) {
336
                return "    {$name}$i,\n";
337
            },
338
            $items
339
        );
340
341
        $this->collection->push(
342
            new GeneratedItem(
343
                GeneratedItem::TYPE_FRONTEND,
344
                implode("\n", $import) . "\n" .
345
                "export {\n" .
346
                implode("\n", $export) . "\n};\n",
347
                $path
348
            )
349
        );
350
    }
351
352
    protected function makeVueRoutes(): void
353
    {
354
        $path = $this->model->getName() . '/routes.js';
355
356
        $this->collection->push(
357
            new GeneratedItem(
358
                GeneratedItem::TYPE_FRONTEND,
359
                $this->templateFile($this->stubDir . "/routes.mustache.js"),
360
                $path
361
            )
362
        );
363
    }
364
365
    protected function makeJSModel(): void
366
    {
367
        $path = $this->model->getName() . '/model.js';
368
        $modelValues = $this->model->getDefault();
369
        $modelValues['id'] = 0;
370
        $modelJS = 'const model = ' . json_encode($modelValues) .
371
            ";\n\nexport default model;\n";
372
        
373
        $this->collection->push(
374
            new GeneratedItem(
375
                GeneratedItem::TYPE_FRONTEND,
376
                $modelJS,
377
                $path
378
            )
379
        );
380
    }
381
}
382