Completed
Pull Request — master (#1281)
by
unknown
02:32
created

NestedForm::getForm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
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\Support\Collection;
8
9
/**
10
 * Class NestedForm.
11
 *
12
 * @method Field\Text           text($column, $label = '')
13
 * @method Field\Checkbox       checkbox($column, $label = '')
14
 * @method Field\Radio          radio($column, $label = '')
15
 * @method Field\Select         select($column, $label = '')
16
 * @method Field\MultipleSelect multipleSelect($column, $label = '')
17
 * @method Field\Textarea       textarea($column, $label = '')
18
 * @method Field\Hidden         hidden($column, $label = '')
19
 * @method Field\Id             id($column, $label = '')
20
 * @method Field\Ip             ip($column, $label = '')
21
 * @method Field\Url            url($column, $label = '')
22
 * @method Field\Color          color($column, $label = '')
23
 * @method Field\Email          email($column, $label = '')
24
 * @method Field\Mobile         mobile($column, $label = '')
25
 * @method Field\Slider         slider($column, $label = '')
26
 * @method Field\Map            map($latitude, $longitude, $label = '')
27
 * @method Field\Editor         editor($column, $label = '')
28
 * @method Field\File           file($column, $label = '')
29
 * @method Field\Image          image($column, $label = '')
30
 * @method Field\Date           date($column, $label = '')
31
 * @method Field\Datetime       datetime($column, $label = '')
32
 * @method Field\Time           time($column, $label = '')
33
 * @method Field\Year           year($column, $label = '')
34
 * @method Field\Month          month($column, $label = '')
35
 * @method Field\DateRange      dateRange($start, $end, $label = '')
36
 * @method Field\DateTimeRange  datetimeRange($start, $end, $label = '')
37
 * @method Field\TimeRange      timeRange($start, $end, $label = '')
38
 * @method Field\Number         number($column, $label = '')
39
 * @method Field\Currency       currency($column, $label = '')
40
 * @method Field\HasMany        hasMany($relationName, $callback)
41
 * @method Field\SwitchField    switch($column, $label = '')
42
 * @method Field\Display        display($column, $label = '')
43
 * @method Field\Rate           rate($column, $label = '')
44
 * @method Field\Divide         divider()
45
 * @method Field\Password       password($column, $label = '')
46
 * @method Field\Decimal        decimal($column, $label = '')
47
 * @method Field\Html           html($html, $label = '')
48
 * @method Field\Tags           tags($column, $label = '')
49
 * @method Field\Icon           icon($column, $label = '')
50
 * @method Field\Embeds         embeds($column, $label = '')
51
 */
52
class NestedForm
53
{
54
    const DEFAULT_KEY_NAME = '__LA_KEY__';
55
56
    const REMOVE_FLAG_NAME = '_remove_';
57
58
    const REMOVE_FLAG_CLASS = 'fom-removed';
59
60
    /**
61
     * @var string
62
     */
63
    protected $relationName;
64
65
    /**
66
     * NestedForm key.
67
     *
68
     * @var
69
     */
70
    protected $key;
71
72
    /**
73
     * Fields in form.
74
     *
75
     * @var Collection
76
     */
77
    protected $fields;
78
79
    /**
80
     * Original data for this field.
81
     *
82
     * @var array
83
     */
84
    protected $original = [];
85
86
    /**
87
     * @var \Encore\Admin\Form
88
     */
89
    protected $form;
90
91
    /**
92
     * Create a new NestedForm instance.
93
     *
94
     * NestedForm constructor.
95
     *
96
     * @param string $relation
97
     * @param null   $key
98
     */
99
    public function __construct($relation, $key = null)
100
    {
101
        $this->relationName = $relation;
102
103
        $this->key = $key;
104
105
        $this->fields = new Collection();
106
    }
107
108
    /**
109
     * Set Form.
110
     *
111
     * @param Form $form
112
     *
113
     * @return $this
114
     */
115
    public function setForm(Form $form = null)
116
    {
117
        $this->form = $form;
118
119
        return $this;
120
    }
121
122
    /**
123
     * Get form.
124
     *
125
     * @return Form
126
     */
127
    public function getForm()
128
    {
129
        return $this->form;
130
    }
131
132
    /**
133
     * Set original values for fields.
134
     *
135
     * @param array  $data
136
     * @param string $relatedKeyName
137
     *
138
     * @return $this
139
     */
140
    public function setOriginal($data, $relatedKeyName)
141
    {
142
        if (empty($data)) {
143
            return $this;
144
        }
145
146
        foreach ($data as $value) {
147
            /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
148
             * like $this->original[30] = [ id = 30, .....]
149
             */
150
            $this->original[$value[$relatedKeyName]] = $value;
151
        }
152
153
        return $this;
154
    }
155
156
    /**
157
     * Prepare for insert or update.
158
     *
159
     * @param array $input
160
     *
161
     * @return mixed
162
     */
163
    public function prepare($input)
164
    {
165
        foreach ($input as $key => $record) {
166
            $this->setFieldOriginalValue($key);
167
            $input[$key] = $this->prepareRecord($record);
168
        }
169
170
        return $input;
171
    }
172
173
    /**
174
     * Set original data for each field.
175
     *
176
     * @param string $key
177
     *
178
     * @return void
179
     */
180 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...
181
    {
182
        $values = [];
183
        if (array_key_exists($key, $this->original)) {
184
            $values = $this->original[$key];
185
        }
186
        $this->fields->each(function (Field $field) use ($values) {
187
            $field->setOriginal($values);
188
        });
189
    }
190
191
    /**
192
     * Do prepare work before store and update.
193
     *
194
     * @param array $record
195
     *
196
     * @return array
197
     */
198
    protected function prepareRecord($record)
199
    {
200
        if ($record[static::REMOVE_FLAG_NAME] == 1) {
201
            return $record;
202
        }
203
204
        $prepared = [];
205
206
        /* @var Field $field */
207
        foreach ($this->fields as $field) {
208
            $columns = $field->column();
209
210
            $value = $this->fetchColumnValue($record, $columns);
211
212
            if (is_null($value)) {
213
                continue;
214
            }
215
216
            if (method_exists($field, 'prepare')) {
217
                $value = $field->prepare($value);
218
            }
219
220
            if (($field instanceof \Encore\Admin\Form\Field\Hidden) || $value != $field->original()) {
221
                if (is_array($columns)) {
222
                    foreach ($columns as $name => $column) {
223
                        array_set($prepared, $column, $value[$name]);
224
                    }
225
                } elseif (is_string($columns)) {
226
                    array_set($prepared, $columns, $value);
227
                }
228
            }
229
        }
230
231
        $prepared[static::REMOVE_FLAG_NAME] = $record[static::REMOVE_FLAG_NAME];
232
233
        return $prepared;
234
    }
235
236
    /**
237
     * Fetch value in input data by column name.
238
     *
239
     * @param array        $data
240
     * @param string|array $columns
241
     *
242
     * @return array|mixed
243
     */
244 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...
245
    {
246
        if (is_string($columns)) {
247
            return array_get($data, $columns);
248
        }
249
250
        if (is_array($columns)) {
251
            $value = [];
252
            foreach ($columns as $name => $column) {
253
                if (!array_has($data, $column)) {
254
                    continue;
255
                }
256
                $value[$name] = array_get($data, $column);
257
            }
258
259
            return $value;
260
        }
261
    }
262
263
    /**
264
     * @param Field $field
265
     *
266
     * @return $this
267
     */
268
    public function pushField(Field $field)
269
    {
270
        $this->fields->push($field);
271
272
        return $this;
273
    }
274
275
    /**
276
     * Get fields of this form.
277
     *
278
     * @return Collection
279
     */
280
    public function fields()
281
    {
282
        return $this->fields;
283
    }
284
285
    /**
286
     * Fill data to all fields in form.
287
     *
288
     * @param array $data
289
     *
290
     * @return $this
291
     */
292
    public function fill(array $data)
293
    {
294
        /* @var Field $field */
295
        foreach ($this->fields() as $field) {
296
            $field->fill($data);
297
        }
298
299
        return $this;
300
    }
301
302
    /**
303
     * Get the html and script of template.
304
     *
305
     * @return array
306
     */
307
    public function getTemplateHtmlAndScript()
308
    {
309
        $html = '';
310
        $scripts = [];
311
312
        /* @var Field $field */
313
        foreach ($this->fields() as $field) {
314
315
            //when field render, will push $script to Admin
316
            $html .= $field->render();
317
318
            /*
319
             * Get and remove the last script of Admin::$script stack.
320
             */
321
            if ($field->getScript()) {
322
                $scripts[] = array_pop(Admin::$script);
323
            }
324
        }
325
326
        return [$html, implode("\r\n", $scripts)];
327
    }
328
329
    /**
330
     * Set `errorKey` `elementName` `elementClass` for fields inside hasmany fields.
331
     *
332
     * @param Field $field
333
     *
334
     * @return Field
335
     */
336
    protected function formatField(Field $field)
337
    {
338
        $column = $field->column();
339
340
        $elementName = $elementClass = $errorKey = '';
341
342
        $key = $this->key ?: 'new_'.static::DEFAULT_KEY_NAME;
343
344
        if (is_array($column)) {
345
            foreach ($column as $k => $name) {
346
                $errorKey[$k] = sprintf('%s.%s.%s', $this->relationName, $key, $name);
347
                $elementName[$k] = sprintf('%s[%s][%s]', $this->relationName, $key, $name);
348
                $elementClass[$k] = [$this->relationName, $name];
349
            }
350
        } else {
351
            $errorKey = sprintf('%s.%s.%s', $this->relationName, $key, $column);
352
            $elementName = sprintf('%s[%s][%s]', $this->relationName, $key, $column);
353
            $elementClass = [$this->relationName, $column];
354
        }
355
356
        return $field->setErrorKey($errorKey)
357
            ->setElementName($elementName)
358
            ->setElementClass($elementClass);
0 ignored issues
show
Bug introduced by
It seems like $elementClass defined by array($this->relationName, $column) on line 353 can also be of type array<integer,string,{"0":"string","1":"string"}>; however, Encore\Admin\Form\Field::setElementClass() 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...
359
    }
360
361
    /**
362
     * Add nested-form fields dynamically.
363
     *
364
     * @param string $method
365
     * @param array  $arguments
366
     *
367
     * @return mixed
368
     */
369
    public function __call($method, $arguments)
370
    {
371
        if ($className = Form::findFieldClass($method)) {
372
            $column = array_get($arguments, 0, '');
373
374
            /* @var Field $field */
375
            $field = new $className($column, array_slice($arguments, 1));
376
377
            $field->setForm($this->form);
378
379
            $field = $this->formatField($field);
380
381
            $this->pushField($field);
382
383
            return $field;
384
        }
385
386
        return $this;
387
    }
388
}
389