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

src/Form/NestedForm.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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) {
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)
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)) {
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)
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)
430
            ->setElementName($elementName)
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)
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