Completed
Push — master ( a13290...ae4efa )
by Song
30s
created

NestedForm::setWidgetForm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin\Form;
4
5
use Encore\Admin\Admin;
6
use Encore\Admin\Form;
7
use Encore\Admin\Widgets\Form as WidgetForm;
8
use Illuminate\Database\Eloquent\Model;
9
use Illuminate\Support\Arr;
10
use Illuminate\Support\Collection;
11
12
/**
13
 * Class NestedForm.
14
 *
15
 * @method Field\Text           text($column, $label = '')
16
 * @method Field\Checkbox       checkbox($column, $label = '')
17
 * @method Field\Radio          radio($column, $label = '')
18
 * @method Field\Select         select($column, $label = '')
19
 * @method Field\MultipleSelect multipleSelect($column, $label = '')
20
 * @method Field\Textarea       textarea($column, $label = '')
21
 * @method Field\Hidden         hidden($column, $label = '')
22
 * @method Field\Id             id($column, $label = '')
23
 * @method Field\Ip             ip($column, $label = '')
24
 * @method Field\Url            url($column, $label = '')
25
 * @method Field\Color          color($column, $label = '')
26
 * @method Field\Email          email($column, $label = '')
27
 * @method Field\Mobile         mobile($column, $label = '')
28
 * @method Field\Slider         slider($column, $label = '')
29
 * @method Field\Map            map($latitude, $longitude, $label = '')
30
 * @method Field\Editor         editor($column, $label = '')
31
 * @method Field\File           file($column, $label = '')
32
 * @method Field\Image          image($column, $label = '')
33
 * @method Field\Date           date($column, $label = '')
34
 * @method Field\Datetime       datetime($column, $label = '')
35
 * @method Field\Time           time($column, $label = '')
36
 * @method Field\Year           year($column, $label = '')
37
 * @method Field\Month          month($column, $label = '')
38
 * @method Field\DateRange      dateRange($start, $end, $label = '')
39
 * @method Field\DateTimeRange  datetimeRange($start, $end, $label = '')
40
 * @method Field\TimeRange      timeRange($start, $end, $label = '')
41
 * @method Field\Number         number($column, $label = '')
42
 * @method Field\Currency       currency($column, $label = '')
43
 * @method Field\HasMany        hasMany($relationName, $callback)
44
 * @method Field\SwitchField    switch($column, $label = '')
45
 * @method Field\Display        display($column, $label = '')
46
 * @method Field\Rate           rate($column, $label = '')
47
 * @method Field\Divide         divider()
48
 * @method Field\Password       password($column, $label = '')
49
 * @method Field\Decimal        decimal($column, $label = '')
50
 * @method Field\Html           html($html, $label = '')
51
 * @method Field\Tags           tags($column, $label = '')
52
 * @method Field\Icon           icon($column, $label = '')
53
 * @method Field\Embeds         embeds($column, $label = '')
54
 */
55
class NestedForm
56
{
57
    const DEFAULT_KEY_NAME = '__LA_KEY__';
58
59
    const REMOVE_FLAG_NAME = '_remove_';
60
61
    const REMOVE_FLAG_CLASS = 'fom-removed';
62
63
    /**
64
     * @var mixed
65
     */
66
    protected $key;
67
68
    /**
69
     * @var string
70
     */
71
    protected $relationName;
72
73
    /**
74
     * NestedForm key.
75
     *
76
     * @var Model
77
     */
78
    protected $model;
79
80
    /**
81
     * Fields in form.
82
     *
83
     * @var Collection
84
     */
85
    protected $fields;
86
87
    /**
88
     * Original data for this field.
89
     *
90
     * @var array
91
     */
92
    protected $original = [];
93
94
    /**
95
     * @var \Encore\Admin\Form|\Encore\Admin\Widgets\Form
96
     */
97
    protected $form;
98
99
    /**
100
     * Create a new NestedForm instance.
101
     *
102
     * NestedForm constructor.
103
     *
104
     * @param string $relation
105
     * @param Model  $model
106
     */
107
    public function __construct($relation, $model = null)
108
    {
109
        $this->relationName = $relation;
110
111
        $this->model = $model;
112
113
        $this->fields = new Collection();
114
    }
115
116
    /**
117
     * Get current model.
118
     *
119
     * @return Model|null
120
     */
121
    public function model()
122
    {
123
        return $this->model;
124
    }
125
126
    /**
127
     * Get the value of the model's primary key.
128
     *
129
     * @return mixed|null
130
     */
131
    public function getKey()
132
    {
133
        if ($this->model) {
134
            $key = $this->model->getKey();
135
        }
136
137
        if (!is_null($this->key)) {
138
            $key = $this->key;
139
        }
140
141
        if (isset($key)) {
142
            return $key;
143
        }
144
145
        return 'new_'.static::DEFAULT_KEY_NAME;
146
    }
147
148
    /**
149
     * Set key for current form.
150
     *
151
     * @param mixed $key
152
     *
153
     * @return $this
154
     */
155
    public function setKey($key)
156
    {
157
        $this->key = $key;
158
159
        return $this;
160
    }
161
162
    /**
163
     * Set Form.
164
     *
165
     * @param Form $form
166
     *
167
     * @return $this
168
     */
169
    public function setForm(Form $form = null)
170
    {
171
        $this->form = $form;
172
173
        return $this;
174
    }
175
176
    /**
177
     * Set Widget/Form.
178
     *
179
     * @param WidgetForm $form
180
     *
181
     * @return $this
182
     */
183
    public function setWidgetForm(WidgetForm $form = null)
184
    {
185
        $this->form = $form;
186
187
        return $this;
188
    }
189
190
    /**
191
     * Get form.
192
     *
193
     * @return Form
194
     */
195
    public function getForm()
196
    {
197
        return $this->form;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->form; of type Encore\Admin\Form|Encore\Admin\Widgets\Form adds the type Encore\Admin\Widgets\Form to the return on line 197 which is incompatible with the return type documented by Encore\Admin\Form\NestedForm::getForm of type Encore\Admin\Form.
Loading history...
198
    }
199
200
    /**
201
     * Set original values for fields.
202
     *
203
     * @param array  $data
204
     * @param string $relatedKeyName
205
     *
206
     * @return $this
207
     */
208
    public function setOriginal($data, $relatedKeyName = null)
209
    {
210
        if (empty($data)) {
211
            return $this;
212
        }
213
214
        foreach ($data as $key => $value) {
215
            /*
216
             * like $this->original[30] = [ id = 30, .....]
217
             */
218
            if ($relatedKeyName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $relatedKeyName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
219
                $key = $value[$relatedKeyName];
220
            }
221
222
            $this->original[$key] = $value;
223
        }
224
225
        return $this;
226
    }
227
228
    /**
229
     * Prepare for insert or update.
230
     *
231
     * @param array $input
232
     *
233
     * @return mixed
234
     */
235
    public function prepare($input)
236
    {
237
        foreach ($input as $key => $record) {
238
            $this->setFieldOriginalValue($key);
239
            $input[$key] = $this->prepareRecord($record);
240
        }
241
242
        return $input;
243
    }
244
245
    /**
246
     * Set original data for each field.
247
     *
248
     * @param string $key
249
     *
250
     * @return void
251
     */
252 View Code Duplication
    protected function setFieldOriginalValue($key)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
253
    {
254
        $values = [];
255
        if (array_key_exists($key, $this->original)) {
256
            $values = $this->original[$key];
257
        }
258
259
        $this->fields->each(function (Field $field) use ($values) {
260
            $field->setOriginal($values);
261
        });
262
    }
263
264
    /**
265
     * Do prepare work before store and update.
266
     *
267
     * @param array $record
268
     *
269
     * @return array
270
     */
271
    protected function prepareRecord($record)
272
    {
273
        if ($record[static::REMOVE_FLAG_NAME] == 1) {
274
            return $record;
275
        }
276
277
        $prepared = [];
278
279
        /* @var Field $field */
280
        foreach ($this->fields as $field) {
281
            $columns = $field->column();
282
283
            $value = $this->fetchColumnValue($record, $columns);
284
285
            if (is_null($value)) {
286
                continue;
287
            }
288
289
            if (method_exists($field, 'prepare')) {
290
                $value = $field->prepare($value);
291
            }
292
293
            if (($field instanceof \Encore\Admin\Form\Field\Hidden) || $value != $field->original()) {
294 View Code Duplication
                if (is_array($columns)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
295
                    foreach ($columns as $name => $column) {
296
                        Arr::set($prepared, $column, $value[$name]);
297
                    }
298
                } elseif (is_string($columns)) {
299
                    Arr::set($prepared, $columns, $value);
300
                }
301
            }
302
        }
303
304
        $prepared[static::REMOVE_FLAG_NAME] = $record[static::REMOVE_FLAG_NAME];
305
306
        return $prepared;
307
    }
308
309
    /**
310
     * Fetch value in input data by column name.
311
     *
312
     * @param array        $data
313
     * @param string|array $columns
314
     *
315
     * @return array|mixed
316
     */
317 View Code Duplication
    protected function fetchColumnValue($data, $columns)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
318
    {
319
        if (is_string($columns)) {
320
            return Arr::get($data, $columns);
321
        }
322
323
        if (is_array($columns)) {
324
            $value = [];
325
            foreach ($columns as $name => $column) {
326
                if (!Arr::has($data, $column)) {
327
                    continue;
328
                }
329
                $value[$name] = Arr::get($data, $column);
330
            }
331
332
            return $value;
333
        }
334
    }
335
336
    /**
337
     * @param Field $field
338
     *
339
     * @return $this
340
     */
341
    public function pushField(Field $field)
342
    {
343
        $this->fields->push($field);
344
345
        return $this;
346
    }
347
348
    /**
349
     * Get fields of this form.
350
     *
351
     * @return Collection
352
     */
353
    public function fields()
354
    {
355
        return $this->fields;
356
    }
357
358
    /**
359
     * Fill data to all fields in form.
360
     *
361
     * @param array $data
362
     *
363
     * @return $this
364
     */
365
    public function fill(array $data)
366
    {
367
        /* @var Field $field */
368
        foreach ($this->fields() as $field) {
369
            $field->fill($data);
370
        }
371
372
        return $this;
373
    }
374
375
    /**
376
     * Get the html and script of template.
377
     *
378
     * @return array
379
     */
380
    public function getTemplateHtmlAndScript()
381
    {
382
        $html = '';
383
        $scripts = [];
384
385
        /* @var Field $field */
386
        foreach ($this->fields() as $field) {
387
388
            //when field render, will push $script to Admin
389
            $html .= $field->render();
390
391
            /*
392
             * Get and remove the last script of Admin::$script stack.
393
             */
394
            if ($field->getScript()) {
395
                $scripts[] = array_pop(Admin::$script);
396
            }
397
        }
398
399
        return [$html, implode("\r\n", $scripts)];
400
    }
401
402
    /**
403
     * Set `errorKey` `elementName` `elementClass` for fields inside hasmany fields.
404
     *
405
     * @param Field $field
406
     *
407
     * @return Field
408
     */
409
    protected function formatField(Field $field)
410
    {
411
        $column = $field->column();
412
413
        $elementName = $elementClass = $errorKey = [];
414
415
        $key = $this->getKey();
416
417
        if (is_array($column)) {
418
            foreach ($column as $k => $name) {
419
                $errorKey[$k] = sprintf('%s.%s.%s', $this->relationName, $key, $name);
420
                $elementName[$k] = sprintf('%s[%s][%s]', $this->relationName, $key, $name);
421
                $elementClass[$k] = [$this->relationName, $name];
422
            }
423
        } else {
424
            $errorKey = sprintf('%s.%s.%s', $this->relationName, $key, $column);
425
            $elementName = sprintf('%s[%s][%s]', $this->relationName, $key, $column);
426
            $elementClass = [$this->relationName, $column];
427
        }
428
429
        return $field->setErrorKey($errorKey)
0 ignored issues
show
Bug introduced by
It seems like $errorKey defined by array() on line 413 can also be of type array; however, Encore\Admin\Form\Field::setErrorKey() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
430
            ->setElementName($elementName)
0 ignored issues
show
Bug introduced by
It seems like $elementName defined by $elementClass = $errorKey = array() on line 413 can also be of type array; however, Encore\Admin\Form\Field::setElementName() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
431
            ->setElementClass($elementClass);
432
    }
433
434
    /**
435
     * Add nested-form fields dynamically.
436
     *
437
     * @param string $method
438
     * @param array  $arguments
439
     *
440
     * @return mixed
441
     */
442 View Code Duplication
    public function __call($method, $arguments)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
443
    {
444
        if ($className = Form::findFieldClass($method)) {
445
            $column = Arr::get($arguments, 0, '');
446
447
            /* @var Field $field */
448
            $field = new $className($column, array_slice($arguments, 1));
449
450
            if ($this->form instanceof WidgetForm) {
451
                $field->setWidgetForm($this->form);
452
            } else {
453
                $field->setForm($this->form);
454
            }
455
456
            $field = $this->formatField($field);
457
458
            $this->pushField($field);
459
460
            return $field;
461
        }
462
463
        return $this;
464
    }
465
}
466