Completed
Push — master ( 6de3cf...c386e0 )
by Song
03:18
created

Form::fieldset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12

Duplication

Lines 12
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 12
loc 12
rs 9.8666
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin\Widgets;
4
5
use Closure;
6
use Encore\Admin\Form as BaseForm;
7
use Encore\Admin\Form\Field;
8
use Illuminate\Contracts\Support\Arrayable;
9
use Illuminate\Contracts\Support\Renderable;
10
use Illuminate\Http\Request;
11
use Illuminate\Support\Arr;
12
use Illuminate\Support\MessageBag;
13
use Illuminate\Validation\Validator;
14
15
/**
16
 * Class Form.
17
 *
18
 * @method Field\Text           text($name, $label = '')
19
 * @method Field\Password       password($name, $label = '')
20
 * @method Field\Checkbox       checkbox($name, $label = '')
21
 * @method Field\Radio          radio($name, $label = '')
22
 * @method Field\Select         select($name, $label = '')
23
 * @method Field\MultipleSelect multipleSelect($name, $label = '')
24
 * @method Field\Textarea       textarea($name, $label = '')
25
 * @method Field\Hidden         hidden($name, $label = '')
26
 * @method Field\Id             id($name, $label = '')
27
 * @method Field\Ip             ip($name, $label = '')
28
 * @method Field\Url            url($name, $label = '')
29
 * @method Field\Color          color($name, $label = '')
30
 * @method Field\Email          email($name, $label = '')
31
 * @method Field\Mobile         mobile($name, $label = '')
32
 * @method Field\Slider         slider($name, $label = '')
33
 * @method Field\File           file($name, $label = '')
34
 * @method Field\Image          image($name, $label = '')
35
 * @method Field\Date           date($name, $label = '')
36
 * @method Field\Datetime       datetime($name, $label = '')
37
 * @method Field\Time           time($name, $label = '')
38
 * @method Field\Year           year($column, $label = '')
39
 * @method Field\Month          month($column, $label = '')
40
 * @method Field\DateRange      dateRange($start, $end, $label = '')
41
 * @method Field\DateTimeRange  dateTimeRange($start, $end, $label = '')
42
 * @method Field\TimeRange      timeRange($start, $end, $label = '')
43
 * @method Field\Number         number($name, $label = '')
44
 * @method Field\Currency       currency($name, $label = '')
45
 * @method Field\SwitchField    switch($name, $label = '')
46
 * @method Field\Display        display($name, $label = '')
47
 * @method Field\Rate           rate($name, $label = '')
48
 * @method Field\Divider        divider($title = '')
49
 * @method Field\Decimal        decimal($column, $label = '')
50
 * @method Field\Html           html($html)
51
 * @method Field\Tags           tags($column, $label = '')
52
 * @method Field\Icon           icon($column, $label = '')
53
 * @method Field\Captcha        captcha($column, $label = '')
54
 * @method Field\Listbox        listbox($column, $label = '')
55
 * @method Field\Table          table($column, $label, $builder)
56
 * @method Field\Timezone       timezone($column, $label = '')
57
 * @method Field\KeyValue       keyValue($column, $label = '')
58
 * @method Field\ListField      list($column, $label = '')
59
 * @method mixed                handle(Request $request)
60
 */
61
class Form implements Renderable
62
{
63
    /**
64
     * The title of form.
65
     *
66
     * @var string
67
     */
68
    public $title;
69
70
    /**
71
     * @var Field[]
72
     */
73
    protected $fields = [];
74
75
    /**
76
     * @var array
77
     */
78
    protected $data = [];
79
80
    /**
81
     * @var array
82
     */
83
    protected $attributes = [];
84
85
    /**
86
     * Available buttons.
87
     *
88
     * @var array
89
     */
90
    protected $buttons = ['reset', 'submit'];
91
92
    /**
93
     * Width for label and submit field.
94
     *
95
     * @var array
96
     */
97
    protected $width = [
98
        'label' => 2,
99
        'field' => 8,
100
    ];
101
102
    /**
103
     * Form constructor.
104
     *
105
     * @param array $data
106
     */
107
    public function __construct($data = [])
108
    {
109
        $this->fill($data);
110
111
        $this->initFormAttributes();
112
    }
113
114
    /**
115
     * Get form title.
116
     *
117
     * @return mixed
118
     */
119
    protected function title()
120
    {
121
        return $this->title;
122
    }
123
124
    /**
125
     * @return array
126
     */
127
    public function data()
128
    {
129
        return $this->data;
130
    }
131
132
    /**
133
     * Fill data to form fields.
134
     *
135
     * @param array $data
136
     *
137
     * @return $this
138
     */
139
    public function fill($data = [])
140
    {
141
        if ($data instanceof Arrayable) {
142
            $data = $data->toArray();
143
        }
144
145
        if (!empty($data)) {
146
            $this->data = $data;
147
        }
148
149
        return $this;
150
    }
151
152
    /**
153
     * @return $this
154
     */
155
    public function sanitize()
156
    {
157
        foreach (['_form_', '_token'] as $key) {
158
            request()->request->remove($key);
159
        }
160
161
        return $this;
162
    }
163
164
    /**
165
     * Initialize the form attributes.
166
     */
167
    protected function initFormAttributes()
168
    {
169
        $this->attributes = [
170
            'method'         => 'POST',
171
            'action'         => '',
172
            'class'          => 'form-horizontal',
173
            'accept-charset' => 'UTF-8',
174
            'pjax-container' => true,
175
        ];
176
    }
177
178
    /**
179
     * Add form attributes.
180
     *
181
     * @param string|array $attr
182
     * @param string       $value
183
     *
184
     * @return $this
185
     */
186
    public function attribute($attr, $value = '')
187
    {
188
        if (is_array($attr)) {
189
            foreach ($attr as $key => $value) {
190
                $this->attribute($key, $value);
191
            }
192
        } else {
193
            $this->attributes[$attr] = $value;
194
        }
195
196
        return $this;
197
    }
198
199
    /**
200
     * Format form attributes form array to html.
201
     *
202
     * @param array $attributes
203
     *
204
     * @return string
205
     */
206
    public function formatAttribute($attributes = [])
207
    {
208
        $attributes = $attributes ?: $this->attributes;
209
210
        if ($this->hasFile()) {
211
            $attributes['enctype'] = 'multipart/form-data';
212
        }
213
214
        $html = [];
215
        foreach ($attributes as $key => $val) {
216
            $html[] = "$key=\"$val\"";
217
        }
218
219
        return implode(' ', $html) ?: '';
220
    }
221
222
    /**
223
     * Action uri of the form.
224
     *
225
     * @param string $action
226
     *
227
     * @return $this
228
     */
229
    public function action($action)
230
    {
231
        return $this->attribute('action', $action);
232
    }
233
234
    /**
235
     * Method of the form.
236
     *
237
     * @param string $method
238
     *
239
     * @return $this
240
     */
241
    public function method($method = 'POST')
242
    {
243
        if (strtolower($method) == 'put') {
244
            $this->hidden('_method')->default($method);
245
246
            return $this;
247
        }
248
249
        return $this->attribute('method', strtoupper($method));
250
    }
251
252
    /**
253
     * Disable Pjax.
254
     *
255
     * @return $this
256
     */
257
    public function disablePjax()
258
    {
259
        Arr::forget($this->attributes, 'pjax-container');
260
261
        return $this;
262
    }
263
264
    /**
265
     * Disable reset button.
266
     *
267
     * @return $this
268
     */
269
    public function disableReset()
270
    {
271
        array_delete($this->buttons, 'reset');
272
273
        return $this;
274
    }
275
276
    /**
277
     * Disable submit button.
278
     *
279
     * @return $this
280
     */
281
    public function disableSubmit()
282
    {
283
        array_delete($this->buttons, 'submit');
284
285
        return $this;
286
    }
287
288
    /**
289
     * Set field and label width in current form.
290
     *
291
     * @param int $fieldWidth
292
     * @param int $labelWidth
293
     *
294
     * @return $this
295
     */
296
    public function setWidth($fieldWidth = 8, $labelWidth = 2)
297
    {
298
        collect($this->fields)->each(function ($field) use ($fieldWidth, $labelWidth) {
299
            /* @var Field $field  */
300
            $field->setWidth($fieldWidth, $labelWidth);
301
        });
302
303
        // set this width
304
        $this->width = [
305
            'label' => $labelWidth,
306
            'field' => $fieldWidth,
307
        ];
308
309
        return $this;
310
    }
311
312
    /**
313
     * Determine if the form has field type.
314
     *
315
     * @param string $name
316
     *
317
     * @return bool
318
     */
319
    public function hasField($name)
320
    {
321
        return isset(BaseForm::$availableFields[$name]);
322
    }
323
324
    /**
325
     * Add a form field to form.
326
     *
327
     * @param Field $field
328
     *
329
     * @return $this
330
     */
331
    public function pushField(Field &$field)
332
    {
333
        array_push($this->fields, $field);
334
335
        return $this;
336
    }
337
338
    /**
339
     * Get all fields of form.
340
     *
341
     * @return Field[]
342
     */
343
    public function fields()
344
    {
345
        return $this->fields;
346
    }
347
348
    /**
349
     * Get variables for render form.
350
     *
351
     * @return array
352
     */
353
    protected function getVariables()
354
    {
355
        foreach ($this->fields as $field) {
356
            $field->fill($this->data());
357
        }
358
359
        return [
360
            'fields'     => $this->fields,
361
            'attributes' => $this->formatAttribute(),
362
            'method'     => $this->attributes['method'],
363
            'buttons'    => $this->buttons,
364
            'width'      => $this->width,
365
        ];
366
    }
367
368
    /**
369
     * Determine if form fields has files.
370
     *
371
     * @return bool
372
     */
373
    public function hasFile()
374
    {
375
        foreach ($this->fields as $field) {
376
            if ($field instanceof Field\File) {
377
                return true;
378
            }
379
        }
380
381
        return false;
382
    }
383
384
    /**
385
     * Validate this form fields.
386
     *
387
     * @param Request $request
388
     *
389
     * @return bool|MessageBag
390
     */
391
    public function validate(Request $request)
392
    {
393
        if (method_exists($this, 'form')) {
394
            $this->form();
0 ignored issues
show
Documentation Bug introduced by
The method form does not exist on object<Encore\Admin\Widgets\Form>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
395
        }
396
397
        $failedValidators = [];
398
399
        /** @var Field $field */
400 View Code Duplication
        foreach ($this->fields() as $field) {
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...
401
            if (!$validator = $field->getValidator($request->all())) {
402
                continue;
403
            }
404
405
            if (($validator instanceof Validator) && !$validator->passes()) {
406
                $failedValidators[] = $validator;
407
            }
408
        }
409
410
        $message = $this->mergeValidationMessages($failedValidators);
411
412
        return $message->any() ? $message : false;
413
    }
414
415
    /**
416
     * Merge validation messages from input validators.
417
     *
418
     * @param \Illuminate\Validation\Validator[] $validators
419
     *
420
     * @return MessageBag
421
     */
422
    protected function mergeValidationMessages($validators)
423
    {
424
        $messageBag = new MessageBag();
425
426
        foreach ($validators as $validator) {
427
            $messageBag = $messageBag->merge($validator->messages());
428
        }
429
430
        return $messageBag;
431
    }
432
433
    /**
434
     * Add a fieldset to form.
435
     *
436
     * @param string $title
437
     * @param Closure $setCallback
438
     *
439
     * @return Field\Fieldset
440
     */
441 View Code Duplication
    public function fieldset(string $title, Closure $setCallback)
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...
442
    {
443
        $fieldset = new Field\Fieldset();
444
445
        $this->html($fieldset->start($title))->plain();
446
447
        $setCallback($this);
448
449
        $this->html($fieldset->end())->plain();
450
451
        return $fieldset;
452
    }
453
454
    /**
455
     * Render the form.
456
     *
457
     * @return string
458
     */
459
    public function render()
460
    {
461
        if (method_exists($this, 'form')) {
462
            $this->form();
0 ignored issues
show
Documentation Bug introduced by
The method form does not exist on object<Encore\Admin\Widgets\Form>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
463
        }
464
465
        if (method_exists($this, 'handle')) {
466
            $this->method('POST');
467
            $this->action(route('admin.handle-form'));
468
            $this->hidden('_form_')->default(get_called_class());
469
        }
470
471
        $form = view('admin::widgets.form', $this->getVariables())->render();
0 ignored issues
show
Bug introduced by
The method render does only exist in Illuminate\View\View, but not in Illuminate\Contracts\View\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
472
473
        $title = $this->title();
474
475
        if (!$title) {
476
            return $form;
477
        }
478
479
        return new Box($title, $form);
480
    }
481
482
    /**
483
     * Generate a Field object and add to form builder if Field exists.
484
     *
485
     * @param string $method
486
     * @param array  $arguments
487
     *
488
     * @return Field|$this
489
     */
490
    public function __call($method, $arguments)
491
    {
492
        if (!$this->hasField($method)) {
493
            return $this;
494
        }
495
496
        $class = BaseForm::$availableFields[$method];
497
498
        $field = new $class($arguments[0], array_slice($arguments, 1));
499
500
        return tap($field, function ($field) {
501
            $this->pushField($field);
502
        });
503
    }
504
505
    /**
506
     * Output as string.
507
     *
508
     * @return string
509
     */
510
    public function __toString()
511
    {
512
        return $this->render();
513
    }
514
}
515