Test Failed
Push — master ( 4e233c...99015e )
by Bruno
05:23
created

expandJS()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 10
rs 10
cc 3
nc 1
nop 1
1
<?php declare(strict_types=1);
2
3
namespace Formularium\Frontend\Vue;
4
5
use Formularium\Datatype;
6
use Formularium\Datatype\Datatype_bool;
7
use Formularium\Datatype\Datatype_number;
8
use Formularium\Exception\Exception;
9
use Formularium\Field;
10
use Formularium\HTMLNode;
11
use Formularium\Model;
12
13
/**
14
 * Converts an array to a JS object. Unlike JSON this does serialize
15
 * data to strings, and allow functions, etc. If you need to add
16
 * strings they are expected to be previously quoted with " or '
17
 *
18
 * @param array $data
19
 * @return array
20
 */
21
function expandJS(array $data): array
22
{
23
    return array_map(function ($key, $value) {
24
        return "$key" .
25
            (
26
                is_array($value) ?
27
                ': {' . implode(",\n", expandJS($value)) . '}' :
28
                ($value ? ':' . $value : '')
29
            );
30
    }, array_keys($data), $data);
31
}
32
33
class VueCode
34
{
35
36
    /**
37
     * Appended to the field variable names to handle models stored in an object field.
38
     *
39
     * Allows you to declare the model like this:
40
     *
41
     * data() {
42
     *   return {
43
     *       model: model,
44
     *   };
45
     * },
46
     *
47
     * @var string
48
     */
49
    protected $fieldModelVariable = '';
50
51
    /**
52
     * Extra props.
53
     *
54
     * @var array
55
     */
56
    protected $extraProps = [];
57
58
    /**
59
     * extra data fields
60
     *
61
     * @var string[]
62
     */
63
    protected $extraData = [];
64
65
    /**
66
     * The list of imports to add: import 'key' from 'value'
67
     *
68
     * @var string[]
69
     */
70
    protected $imports = [];
71
    
72
    /**
73
     * @var string[]
74
     */
75
    protected $computed = [];
76
77
    /**
78
     * @var string[]
79
     */
80
    protected $methods = [];
81
82
    /**
83
     * @var string[]
84
     */
85
    protected $other = [];
86
87
    /**
88
     * @param string $name
89
     * @param string $code
90
     * @return self
91
     */
92
    public function appendMethod($name, $code): self
93
    {
94
        $this->methods[$name] = $code;
95
        return $this;
96
    }
97
98
    /**
99
     * @param string $name
100
     * @param string $code
101
     * @return self
102
     */
103
    public function appendOther($name, $code): self
104
    {
105
        $this->other[$name] = $code;
106
        return $this;
107
    }
108
109
    /**
110
     * @return array
111
     */
112
    public function getExtraProps(): array
113
    {
114
        return $this->extraProps;
115
    }
116
117
    /**
118
     *
119
     * @param array $extraProps
120
     *
121
     * @return  self
122
     */
123
    public function setExtraProps(array $extraProps): self
124
    {
125
        $this->extraProps = $extraProps;
126
127
        return $this;
128
    }
129
130
    /**
131
     *
132
     * @param array $extra Array of props. 'name' and 'type' keys are required for each element.
133
     *
134
     * @return  self
135
     */
136
    public function appendExtraProp(array $extra): self
137
    {
138
        $this->extraProps[] = $extra;
139
140
        return $this;
141
    }
142
143
    /**
144
     * Appends to the `data` field.
145
     *
146
     * @param string $name
147
     * @param string $value
148
     * @return self
149
     */
150
    public function appendExtraData(string $name, string $value): self
151
    {
152
        $this->extraData[$name] = $value;
153
        return $this;
154
    }
155
156
    /**
157
     * The list of imports to add: import 'key' from 'value'
158
     *
159
     * @param string $key
160
     * @param string $value
161
     * @return self
162
     */
163
    public function appendImport(string $key, string $value): self
164
    {
165
        $this->imports[$key] = $value;
166
167
        return $this;
168
    }
169
170
    /**
171
     * The list of computed to add: $ke() => $code
172
     *
173
     * @param string $key
174
     * @param string $code
175
     * @return self
176
     */
177
    public function appendComputed(string $key, string $code): self
178
    {
179
        $this->computed[$key] = $code;
180
181
        return $this;
182
    }
183
184
    /**
185
     * Get appended to the field variable names to handle models stored in an object field.
186
     *
187
     * @return  string
188
     */
189
    public function getFieldModelVariable(): string
190
    {
191
        return $this->fieldModelVariable;
192
    }
193
194
    /**
195
     * Set appended to the field variable names to handle models stored in an object field.
196
     *
197
     * @param  string  $fieldModelVariable  Appended to the field variable names to handle models stored in an object field.
198
     *
199
     * @return  self
200
     */
201
    public function setFieldModelVariable(string $fieldModelVariable): self
202
    {
203
        $this->fieldModelVariable = $fieldModelVariable;
204
205
        return $this;
206
    }
207
208
    /**
209
     * Converts a Datatype to a JS type
210
     *
211
     * @param Datatype $type
212
     * @return string
213
     */
214
    public function mapType(Datatype $type): string
215
    {
216
        if ($type instanceof Datatype_number) {
217
            return 'Number';
218
        } elseif ($type instanceof Datatype_bool) {
219
            return 'Boolean';
220
        }
221
        return 'String';
222
    }
223
224
    public function props(Model $m): array
225
    {
226
        $props = [];
227
        foreach ($m->getFields() as $field) {
228
            /**
229
             * @var Field $field
230
             */
231
            if ($field->getRenderable(Framework::VUE_PROP, false)) {
232
                $p = [
233
                    'name' => $field->getName(),
234
                    'type' => $this->mapType($field->getDatatype()),
235
                ];
236
                if ($field->getRenderable(Datatype::REQUIRED, false)) {
237
                    $p['required'] = true;
238
                }
239
                $props[] = $p;
240
            }
241
        }
242
        foreach ($this->extraProps as $p) {
243
            if (!array_key_exists('name', $p)) {
244
                throw new Exception('Missing prop name');
245
            }
246
            $props[] = $p;
247
        }
248
        
249
        return $props;
250
    }
251
252
    /**
253
     * Generates valid JS code for the props.
254
     *
255
     * @param array $props
256
     * @return string
257
     */
258
    protected function serializeProps(array $props): string
259
    {
260
        $s = array_map(function ($p) {
261
            return "'{$p['name']}': { 'type': {$p['type']}" . ($p['required'] ?? false ? ", 'required': true" : '') . " } ";
262
        }, $props);
263
        return "{\n        " . implode(",\n        ", $s) . "\n    }";
264
    }
265
266
    /**
267
     * Generates template data for rendering
268
     *
269
     * @param Model $m
270
     * @param HTMLNode[] $elements $elements
271
     * @return array
272
     */
273
    protected function getTemplateData(Model $m, array $elements): array
0 ignored issues
show
Unused Code introduced by
The parameter $elements 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

273
    protected function getTemplateData(Model $m, /** @scrutinizer ignore-unused */ array $elements): array

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...
274
    {
275
        $data = array_merge($m->getDefault(), $m->getData());
276
        $jsonData = json_encode($data);
277
        $props = $this->props($m);
278
        $propsBind = array_map(
279
            function ($p) {
280
                return 'v-bind:' . $p . '="model.' . $p . '"';
281
            },
282
            array_keys($props)
283
        );
284
285
        $templateData = [
286
            'jsonData' => $jsonData,
287
            'propsCode' => $this->serializeProps($props),
288
            'propsBind' => implode(' ', $propsBind),
289
            'imports' => implode(
290
                "\n",
291
                array_map(function ($key, $value) {
292
                    // TODO: array
293
                    return "import $key from \"$value\";";
294
                }, array_keys($this->imports), $this->imports)
295
            ),
296
            'computedCode' => implode(
297
                "\n",
298
                array_map(function ($key, $value) {
299
                    // TODO: array
300
                    return "$key() { $value },";
301
                }, array_keys($this->computed), $this->computed)
302
            ),
303
            'otherData' => implode(
304
                ",\n",
305
                expandJS($this->other)
306
            ) . ",\n",
307
            'methodsCode' => '{}', // TODO
308
            'extraData' => implode(
309
                "\n",
310
                array_map(function ($key, $value) {
311
                    return "  $key: $value,";
312
                }, array_keys($this->extraData), $this->extraData)
313
            )
314
        ];
315
316
        return $templateData;
317
    }
318
319
    protected function fillTemplate(string $template, array $data, Model $m): string
320
    {
321
        foreach ($data as $name => $value) {
322
            $template = str_replace(
323
                '{{' . $name . '}}',
324
                $value,
325
                $template
326
            );
327
        }
328
329
        $template = str_replace(
330
            '{{modelName}}',
331
            $m->getName(),
332
            $template
333
        );
334
        $template = str_replace(
335
            '{{modelNameLower}}',
336
            mb_strtolower($m->getName()),
337
            $template
338
        );
339
        return $template;
340
    }
341
342
    /**
343
     * Generates the javascript code.
344
     *
345
     * @param Model $m
346
     * @param HTMLNode[] $elements
347
     * @return string
348
     */
349
    public function toScript(Model $m, array $elements)
350
    {
351
        $templateData = $this->getTemplateData($m, $elements);
352
353
        $viewableTemplate = <<<EOF
354
{{imports}}
355
356
export default {
357
    {{otherData}}
358
    data: function () {
359
        return {{jsonData}};
360
    },
361
    computed: { {{computedCode}} },
362
    props: {{propsCode}},
363
    methods: {{methodsCode}}
364
};
365
EOF;
366
            
367
        return $this->fillTemplate(
368
            $viewableTemplate,
369
            $templateData,
370
            $m
371
        );
372
    }
373
374
    /**
375
     * Generates the javascript code.
376
     *
377
     * @param Model $m
378
     * @param HTMLNode[] $elements
379
     * @return string
380
     */
381
    public function toVariable(Model $m, array $elements)
382
    {
383
        $templateData = $this->getTemplateData($m, $elements);
384
385
        $viewableTemplate = <<<EOF
386
    {{otherData}}
387
    data() {
388
        return {{jsonData}};
389
    },
390
    computed: { {{computedCode}} },
391
    props: {{propsCode}},
392
    methods: {{methodsCode}}
393
EOF;
394
            
395
        return $this->fillTemplate(
396
            $viewableTemplate,
397
            $templateData,
398
            $m
399
        );
400
    }
401
402
    /**
403
     * Get the value of other
404
     * @return array
405
     */
406
    public function &getOther(): array
407
    {
408
        return $this->other;
409
    }
410
}
411