Completed
Push — master ( e4fa3f...8b68bb )
by Song
02:50
created

NestedForm::setKey()   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 Illuminate\Database\Eloquent\Model;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Collection;
10
11
/**
12
 * Class NestedForm.
13
 *
14
 * @method Field\Text           text($column, $label = '')
15
 * @method Field\Checkbox       checkbox($column, $label = '')
16
 * @method Field\Radio          radio($column, $label = '')
17
 * @method Field\Select         select($column, $label = '')
18
 * @method Field\MultipleSelect multipleSelect($column, $label = '')
19
 * @method Field\Textarea       textarea($column, $label = '')
20
 * @method Field\Hidden         hidden($column, $label = '')
21
 * @method Field\Id             id($column, $label = '')
22
 * @method Field\Ip             ip($column, $label = '')
23
 * @method Field\Url            url($column, $label = '')
24
 * @method Field\Color          color($column, $label = '')
25
 * @method Field\Email          email($column, $label = '')
26
 * @method Field\Mobile         mobile($column, $label = '')
27
 * @method Field\Slider         slider($column, $label = '')
28
 * @method Field\Map            map($latitude, $longitude, $label = '')
29
 * @method Field\Editor         editor($column, $label = '')
30
 * @method Field\File           file($column, $label = '')
31
 * @method Field\Image          image($column, $label = '')
32
 * @method Field\Date           date($column, $label = '')
33
 * @method Field\Datetime       datetime($column, $label = '')
34
 * @method Field\Time           time($column, $label = '')
35
 * @method Field\Year           year($column, $label = '')
36
 * @method Field\Month          month($column, $label = '')
37
 * @method Field\DateRange      dateRange($start, $end, $label = '')
38
 * @method Field\DateTimeRange  datetimeRange($start, $end, $label = '')
39
 * @method Field\TimeRange      timeRange($start, $end, $label = '')
40
 * @method Field\Number         number($column, $label = '')
41
 * @method Field\Currency       currency($column, $label = '')
42
 * @method Field\HasMany        hasMany($relationName, $callback)
43
 * @method Field\SwitchField    switch($column, $label = '')
44
 * @method Field\Display        display($column, $label = '')
45
 * @method Field\Rate           rate($column, $label = '')
46
 * @method Field\Divide         divider()
47
 * @method Field\Password       password($column, $label = '')
48
 * @method Field\Decimal        decimal($column, $label = '')
49
 * @method Field\Html           html($html, $label = '')
50
 * @method Field\Tags           tags($column, $label = '')
51
 * @method Field\Icon           icon($column, $label = '')
52
 * @method Field\Embeds         embeds($column, $label = '')
53
 */
54
class NestedForm
55
{
56
    const DEFAULT_KEY_NAME = '__LA_KEY__';
57
58
    const REMOVE_FLAG_NAME = '_remove_';
59
60
    const REMOVE_FLAG_CLASS = 'fom-removed';
61
62
    /**
63
     * @var mixed
64
     */
65
    protected $key;
66
67
    /**
68
     * @var string
69
     */
70
    protected $relationName;
71
72
    /**
73
     * NestedForm key.
74
     *
75
     * @var Model
76
     */
77
    protected $model;
78
79
    /**
80
     * Fields in form.
81
     *
82
     * @var Collection
83
     */
84
    protected $fields;
85
86
    /**
87
     * Original data for this field.
88
     *
89
     * @var array
90
     */
91
    protected $original = [];
92
93
    /**
94
     * @var \Encore\Admin\Form
95
     */
96
    protected $form;
97
98
    /**
99
     * Create a new NestedForm instance.
100
     *
101
     * NestedForm constructor.
102
     *
103
     * @param string $relation
104
     * @param Model  $model
105
     */
106
    public function __construct($relation, $model = null)
107
    {
108
        $this->relationName = $relation;
109
110
        $this->model = $model;
111
112
        $this->fields = new Collection();
113
    }
114
115
    /**
116
     * Get current model.
117
     *
118
     * @return Model|null
119
     */
120
    public function model()
121
    {
122
        return $this->model;
123
    }
124
125
    /**
126
     * Get the value of the model's primary key.
127
     *
128
     * @return mixed|null
129
     */
130
    public function getKey()
131
    {
132
        if ($this->model) {
133
            $key = $this->model->getKey();
134
        }
135
136
        if (!is_null($this->key)) {
137
            $key = $this->key;
138
        }
139
140
        if (isset($key)) {
141
            return $key;
142
        }
143
144
        return 'new_'.static::DEFAULT_KEY_NAME;
145
    }
146
147
    /**
148
     * Set key for current form.
149
     *
150
     * @param mixed $key
151
     * @return $this
152
     */
153
    public function setKey($key)
154
    {
155
        $this->key = $key;
156
157
        return $this;
158
    }
159
160
    /**
161
     * Set Form.
162
     *
163
     * @param Form $form
164
     *
165
     * @return $this
166
     */
167
    public function setForm(Form $form = null)
168
    {
169
        $this->form = $form;
170
171
        return $this;
172
    }
173
174
    /**
175
     * Get form.
176
     *
177
     * @return Form
178
     */
179
    public function getForm()
180
    {
181
        return $this->form;
182
    }
183
184
    /**
185
     * Set original values for fields.
186
     *
187
     * @param array  $data
188
     * @param string $relatedKeyName
189
     *
190
     * @return $this
191
     */
192
    public function setOriginal($data, $relatedKeyName = null)
193
    {
194
        if (empty($data)) {
195
            return $this;
196
        }
197
198
        foreach ($data as $key => $value) {
199
            /*
200
             * like $this->original[30] = [ id = 30, .....]
201
             */
202
            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...
203
                $key = $value[$relatedKeyName];
204
            }
205
206
            $this->original[$key] = $value;
207
        }
208
209
        return $this;
210
    }
211
212
    /**
213
     * Prepare for insert or update.
214
     *
215
     * @param array $input
216
     *
217
     * @return mixed
218
     */
219
    public function prepare($input)
220
    {
221
        foreach ($input as $key => $record) {
222
            $this->setFieldOriginalValue($key);
223
            $input[$key] = $this->prepareRecord($record);
224
        }
225
226
        return $input;
227
    }
228
229
    /**
230
     * Set original data for each field.
231
     *
232
     * @param string $key
233
     *
234
     * @return void
235
     */
236 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...
237
    {
238
        $values = [];
239
        if (array_key_exists($key, $this->original)) {
240
            $values = $this->original[$key];
241
        }
242
243
        $this->fields->each(function (Field $field) use ($values) {
244
            $field->setOriginal($values);
245
        });
246
    }
247
248
    /**
249
     * Do prepare work before store and update.
250
     *
251
     * @param array $record
252
     *
253
     * @return array
254
     */
255
    protected function prepareRecord($record)
256
    {
257
        if ($record[static::REMOVE_FLAG_NAME] == 1) {
258
            return $record;
259
        }
260
261
        $prepared = [];
262
263
        /* @var Field $field */
264
        foreach ($this->fields as $field) {
265
            $columns = $field->column();
266
267
            $value = $this->fetchColumnValue($record, $columns);
268
269
            if (is_null($value)) {
270
                continue;
271
            }
272
273
            if (method_exists($field, 'prepare')) {
274
                $value = $field->prepare($value);
275
            }
276
277
            if (($field instanceof \Encore\Admin\Form\Field\Hidden) || $value != $field->original()) {
278 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...
279
                    foreach ($columns as $name => $column) {
280
                        Arr::set($prepared, $column, $value[$name]);
281
                    }
282
                } elseif (is_string($columns)) {
283
                    Arr::set($prepared, $columns, $value);
284
                }
285
            }
286
        }
287
288
        $prepared[static::REMOVE_FLAG_NAME] = $record[static::REMOVE_FLAG_NAME];
289
290
        return $prepared;
291
    }
292
293
    /**
294
     * Fetch value in input data by column name.
295
     *
296
     * @param array        $data
297
     * @param string|array $columns
298
     *
299
     * @return array|mixed
300
     */
301 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...
302
    {
303
        if (is_string($columns)) {
304
            return Arr::get($data, $columns);
305
        }
306
307
        if (is_array($columns)) {
308
            $value = [];
309
            foreach ($columns as $name => $column) {
310
                if (!Arr::has($data, $column)) {
311
                    continue;
312
                }
313
                $value[$name] = Arr::get($data, $column);
314
            }
315
316
            return $value;
317
        }
318
    }
319
320
    /**
321
     * @param Field $field
322
     *
323
     * @return $this
324
     */
325
    public function pushField(Field $field)
326
    {
327
        $this->fields->push($field);
328
329
        return $this;
330
    }
331
332
    /**
333
     * Get fields of this form.
334
     *
335
     * @return Collection
336
     */
337
    public function fields()
338
    {
339
        return $this->fields;
340
    }
341
342
    /**
343
     * Fill data to all fields in form.
344
     *
345
     * @param array $data
346
     *
347
     * @return $this
348
     */
349
    public function fill(array $data)
350
    {
351
        /* @var Field $field */
352
        foreach ($this->fields() as $field) {
353
            $field->fill($data);
354
        }
355
356
        return $this;
357
    }
358
359
    /**
360
     * Get the html and script of template.
361
     *
362
     * @return array
363
     */
364
    public function getTemplateHtmlAndScript()
365
    {
366
        $html = '';
367
        $scripts = [];
368
369
        /* @var Field $field */
370
        foreach ($this->fields() as $field) {
371
372
            //when field render, will push $script to Admin
373
            $html .= $field->render();
374
375
            /*
376
             * Get and remove the last script of Admin::$script stack.
377
             */
378
            if ($field->getScript()) {
379
                $scripts[] = array_pop(Admin::$script);
380
            }
381
        }
382
383
        return [$html, implode("\r\n", $scripts)];
384
    }
385
386
    /**
387
     * Set `errorKey` `elementName` `elementClass` for fields inside hasmany fields.
388
     *
389
     * @param Field $field
390
     *
391
     * @return Field
392
     */
393
    protected function formatField(Field $field)
394
    {
395
        $column = $field->column();
396
397
        $elementName = $elementClass = $errorKey = [];
398
399
        $key = $this->getKey();
400
401
        if (is_array($column)) {
402
            foreach ($column as $k => $name) {
403
                $errorKey[$k] = sprintf('%s.%s.%s', $this->relationName, $key, $name);
404
                $elementName[$k] = sprintf('%s[%s][%s]', $this->relationName, $key, $name);
405
                $elementClass[$k] = [$this->relationName, $name];
406
            }
407
        } else {
408
            $errorKey = sprintf('%s.%s.%s', $this->relationName, $key, $column);
409
            $elementName = sprintf('%s[%s][%s]', $this->relationName, $key, $column);
410
            $elementClass = [$this->relationName, $column];
411
        }
412
413
        return $field->setErrorKey($errorKey)
0 ignored issues
show
Bug introduced by
It seems like $errorKey defined by array() on line 397 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...
414
            ->setElementName($elementName)
0 ignored issues
show
Bug introduced by
It seems like $elementName defined by $elementClass = $errorKey = array() on line 397 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...
415
            ->setElementClass($elementClass);
416
    }
417
418
    /**
419
     * Add nested-form fields dynamically.
420
     *
421
     * @param string $method
422
     * @param array  $arguments
423
     *
424
     * @return mixed
425
     */
426 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...
427
    {
428
        if ($className = Form::findFieldClass($method)) {
429
            $column = Arr::get($arguments, 0, '');
430
431
            /* @var Field $field */
432
            $field = new $className($column, array_slice($arguments, 1));
433
434
            $field->setForm($this->form);
435
436
            $field = $this->formatField($field);
437
438
            $this->pushField($field);
439
440
            return $field;
441
        }
442
443
        return $this;
444
    }
445
}
446