Completed
Branch master (8e0976)
by Adam
04:13
created

Field::mergeOptions()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 5
nop 1
dl 0
loc 25
rs 8.8977
c 0
b 0
f 0
1
<?php
2
3
namespace Coyote\Services\FormBuilder\Fields;
4
5
use Coyote\Services\FormBuilder\Form;
6
use Coyote\Services\FormBuilder\RenderTrait;
7
use Illuminate\Database\Eloquent\Model;
8
9
abstract class Field
10
{
11
    use RenderTrait;
12
13
    const DEFAULT_TEMPLATE = 'row';
14
15
    /**
16
     * @var string
17
     */
18
    protected $name;
19
20
    /**
21
     * @var mixed
22
     */
23
    protected $value;
24
25
    /**
26
     * @var string
27
     */
28
    protected $type;
29
30
    /**
31
     * @var Form
32
     */
33
    protected $parent;
34
35
    /**
36
     * @var string
37
     */
38
    protected $label;
39
40
    /**
41
     * @var string
42
     */
43
    protected $help;
44
45
    /**
46
     * @var string
47
     */
48
    protected $theme;
49
50
    /**
51
     * @var string
52
     */
53
    protected $template = self::DEFAULT_TEMPLATE;
54
55
    /**
56
     * @var string
57
     */
58
    protected $rules;
59
60
    /**
61
     * @var array
62
     */
63
    protected $attr = [];
64
65
    /**
66
     * @var array
67
     */
68
    protected $labelAttr = [];
69
70
    /**
71
     * @var array
72
     */
73
    protected $rowAttr = [];
74
75
    /**
76
     * @var array
77
     */
78
    protected $helpAttr = [];
79
80
    /**
81
     * Name of the property for value setting
82
     *
83
     * @var string
84
     */
85
    protected $valueProperty = 'value';
86
87
    /**
88
     * @var bool
89
     */
90
    protected $required = false;
91
92
    /**
93
     * Field constructor.
94
     * @param $name
95
     * @param $type
96
     * @param Form $parent
97
     * @param array $options
98
     */
99
    public function __construct($name, $type, Form $parent, array $options = [])
100
    {
101
        $this->setName($name);
102
        $this->setType($type);
103
        $this->setParent($parent);
104
105
        // 1) Set up options (attributes, field value)
106
        $this->setDefaultOptions($options);
107
        // 2) Set up the value (from model, request, session etc) if it wasn't set before
108
        $this->setupValue();
109
    }
110
111
    /**
112
     * @return string
113
     */
114
    public function getName()
115
    {
116
        return $this->name;
117
    }
118
119
    /**
120
     * @param mixed $name
121
     */
122
    public function setName($name)
123
    {
124
        $this->name = $name;
125
    }
126
127
    /**
128
     * @return mixed
129
     */
130
    public function getValue()
131
    {
132
        return $this->value;
133
    }
134
135
    /**
136
     * Set value (can be string, can be array etc)
137
     *
138
     * @param mixed $value
139
     */
140
    public function setValue($value)
141
    {
142
        $this->value = $value;
143
    }
144
145
    /**
146
     * @param string $type
147
     * @return $this
148
     */
149
    protected function setType($type)
150
    {
151
        $this->type = $type;
152
        return $this;
153
    }
154
155
    /**
156
     * @return string
157
     */
158
    public function getType()
159
    {
160
        return $this->type;
161
    }
162
163
    /**
164
     * @param Form $parent
165
     * @return $this
166
     */
167
    protected function setParent(Form $parent)
168
    {
169
        $this->parent = $parent;
170
        return $this;
171
    }
172
173
    /**
174
     * @return Form
175
     */
176
    public function getParent()
177
    {
178
        return $this->parent;
179
    }
180
181
    /**
182
     * @return string
183
     */
184
    public function getLabel()
185
    {
186
        return $this->label;
187
    }
188
189
    /**
190
     * @param string $label
191
     */
192
    public function setLabel($label)
193
    {
194
        $this->label = $label;
195
    }
196
197
    /**
198
     * @param string $rules
199
     * @return $this
200
     */
201
    public function setRules($rules)
202
    {
203
        $this->rules = $rules;
204
205
        if (is_string($rules)) {
206
            $rules = explode('|', $rules);
207
208
            if (in_array('required', $rules)) {
209
                $this->setRequired(true);
210
            }
211
        }
212
        return $this;
213
    }
214
215
    /**
216
     * @return string
217
     */
218
    public function getRules()
219
    {
220
        return $this->rules;
221
    }
222
223
    /**
224
     * @return string
225
     */
226
    public function getHelp()
227
    {
228
        return $this->help;
229
    }
230
231
    /**
232
     * @param mixed $help
233
     */
234
    public function setHelp($help)
235
    {
236
        $this->help = $help;
237
    }
238
239
    /**
240
     * @return array
241
     */
242
    public function getAttr()
243
    {
244
        return $this->attr;
245
    }
246
247
    /**
248
     * @param array $attr
249
     */
250
    public function setAttr($attr)
251
    {
252
        $this->attr = $attr;
253
    }
254
255
    /**
256
     * @return array
257
     */
258
    public function getLabelAttr()
259
    {
260
        return $this->labelAttr;
261
    }
262
263
    /**
264
     * @param array $labelAttr
265
     */
266
    public function setLabelAttr($labelAttr)
267
    {
268
        $this->labelAttr = $labelAttr;
269
    }
270
271
    /**
272
     * @return array
273
     */
274
    public function getRowAttr()
275
    {
276
        return $this->rowAttr;
277
    }
278
279
    /**
280
     * @param array $rowAttr
281
     */
282
    public function setRowAttr($rowAttr)
283
    {
284
        $this->rowAttr = $rowAttr;
285
    }
286
287
    /**
288
     * @return array
289
     */
290
    public function getHelpAttr()
291
    {
292
        return $this->helpAttr;
293
    }
294
295
    /**
296
     * @param array $helpAttr
297
     * @return $this
298
     */
299
    public function setHelpAttr($helpAttr)
300
    {
301
        $this->helpAttr = $helpAttr;
302
303
        return $this;
304
    }
305
306
    /**
307
     * @return boolean
308
     */
309
    public function isRequired()
310
    {
311
        return $this->required;
312
    }
313
314
    /**
315
     * Method alias
316
     *
317
     * @return bool
318
     */
319
    public function getRequired()
320
    {
321
        return $this->isRequired();
322
    }
323
324
    /**
325
     * @param boolean $required
326
     * @return $this
327
     */
328
    public function setRequired($required)
329
    {
330
        $this->required = $required;
331
332
        return $this;
333
    }
334
335
    /**
336
     * @param array $options
337
     * @return $this
338
     */
339
    public function mergeOptions(array $options)
340
    {
341
        $reflection = new \ReflectionClass($this);
342
343
        foreach ($options as $key => $values) {
344
            $baseName = ucfirst(camel_case($key));
345
            $setter = 'set' . $baseName;
346
347
            if (method_exists($this, $setter)) {
348
                $getter = 'get' . $baseName;
349
350
                if ($reflection->hasMethod($getter)
351
                    && $reflection->getMethod($getter)->getNumberOfParameters() === 0) {
352
                    $currentValue = $this->$getter();
353
354
                    if (is_array($currentValue)) {
355
                        $values = $this->arrayMerge($currentValue, $values);
356
                    }
357
                }
358
                $this->$setter($values);
359
            }
360
        }
361
362
        return $this;
363
    }
364
365
    /**
366
     * @param array $old
367
     * @param array $new
368
     * @return array
369
     */
370
    protected function arrayMerge($old, $new)
371
    {
372
        return array_merge($old, $new);
373
    }
374
375
    /**
376
     * @return array|null
377
     */
378
    public function getErrors()
379
    {
380
        return $this->parent->errors() ? $this->parent->errors()->get($this->transformToDotSyntax($this->name)) : null;
381
    }
382
383
    /**
384
     * @return string|null
385
     */
386
    public function getError()
387
    {
388
        return $this->parent->errors() ? $this->parent->errors()->first($this->transformToDotSyntax($this->name)) : null;
389
    }
390
391
    /**
392
     * @return string
393
     * @throws \Exception
394
     * @throws \Throwable
395
     */
396
    public function renderLabel()
397
    {
398
        return $this->view($this->getViewPath('label'), $this->viewData())->render();
399
    }
400
401
    /**
402
     * @return string
403
     * @throws \Exception
404
     * @throws \Throwable
405
     */
406
    public function renderWidget()
407
    {
408
        return $this->view($this->getWidgetPath(), $this->viewData())->render();
409
    }
410
411
    /**
412
     * @return string
413
     * @throws \Exception
414
     * @throws \Throwable
415
     */
416
    public function renderError()
417
    {
418
        return $this->view($this->getViewPath('error'), $this->viewData())->render();
419
    }
420
421
    /**
422
     * Render entire element
423
     *
424
     * @return mixed
425
     */
426
    public function render()
427
    {
428
        return $this->view($this->getViewPath($this->getTemplate()), $this->viewData())->render();
429
    }
430
431
    /**
432
     * Setup field value when initialized
433
     */
434
    protected function setupValue()
435
    {
436
        if ($this->parent->isSubmitted()) {
437
            $this->setValue($this->parent->getRequest()->get($this->name));
438
        } elseif ($this->hasOldInput($this->name)) {
439
            $this->setValue($this->getOldInput($this->name));
440
        // we set value from model/object/array only if current value is empty.
441
            // that's because we can set custom value in form class and we DO NOT want
442
            // overwrite this here:
443
        } elseif ($this->value === null && !($this instanceof ChildForm)) {
444
            $this->setValue($this->getDataValue($this->parent->getData(), $this->name));
445
        }
446
    }
447
448
    /**
449
     * @param mixed $data
450
     * @param string $name
451
     * @return mixed|null
452
     */
453
    protected function getDataValue($data, $name)
454
    {
455
        $name = $this->transformToDotSyntax($name);
456
        $data = $this->loadModelRelation($data, $name);
457
458
        if (is_string($data)) {
459
            return $data;
460
        } elseif (is_array($data) || $data instanceof \ArrayAccess) {
461
            return array_get($data, $name);
462
        } elseif (is_object($data)) {
463
            return object_get($data, $name);
464
        }
465
466
        return $this->getValue();
467
    }
468
469
    /**
470
     * @param string $key
471
     * @return string
472
     */
473
    protected function getOldInput($key)
474
    {
475
        return $this->parent->getRequest()->session()->getOldInput($key);
0 ignored issues
show
Bug introduced by
The method getOldInput() does not seem to exist on object<Symfony\Component...ssion\SessionInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
476
    }
477
478
    /**
479
     * @param string $key
480
     * @return bool
481
     */
482
    protected function hasOldInput($key)
483
    {
484
        return $this->parent->getRequest()->session()->hasOldInput($key);
0 ignored issues
show
Bug introduced by
The method hasOldInput() does not seem to exist on object<Symfony\Component...ssion\SessionInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
485
    }
486
487
    /**
488
     * If object is a instance of Eloquent model, we have to make sure that relations were loaded
489
     *
490
     * @param $model
491
     * @param string $key
492
     * @return mixed
493
     */
494
    protected function loadModelRelation($model, $key)
495
    {
496
        if (!($model instanceof Model)) {
497
            return $model;
498
        }
499
500
        if (!isset($model->$key) && method_exists($model, $key)) {
501
            $model->getRelationValue($key);
502
        }
503
504
        return $model;
505
    }
506
507
    /**
508
     * @return array
509
     */
510
    protected function viewData()
511
    {
512
        $result = [];
513
514
        $reflection = new \ReflectionClass($this);
515
516
        foreach ($reflection->getMethods() as $method) {
517
            $name = $method->getName();
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
518
            $snakeCase = snake_case($name);
519
            $prefix = substr($snakeCase, 0, strpos($snakeCase, '_'));
520
521
            if (in_array($prefix, ['get', 'is']) && $method->getNumberOfParameters() === 0 && !$method->isPrivate() && $name !== 'getParent') {
522
                $withoutPrefix = $snakeCase;
523
524
                if ($prefix === 'get') {
525
                    $withoutPrefix = substr($withoutPrefix, 4);
526
                }
527
                $result[$withoutPrefix] = $this->$name();
528
            }
529
        }
530
531
        return $result;
532
    }
533
534
    /**
535
     * @return string
536
     */
537
    protected function getWidgetName()
538
    {
539
        return $this->getType() . '_widget';
540
    }
541
542
    /**
543
     * @param array $options
544
     */
545
    protected function setDefaultOptions(array $options)
546
    {
547
        // if default value was provided, we would like to set it at the end after all other options
548
        // because setting value can modify children elements in Collection.php. Before creating children
549
        // forms, we want to make sure that other options have been set.
550
        $defaultValue = array_pull($options, 'value');
551
552 View Code Duplication
        foreach ($options as $key => $values) {
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...
553
            $methodName = 'set' . ucfirst(camel_case($key));
554
555
            if (method_exists($this, $methodName)) {
556
                $this->$methodName($values);
557
            }
558
        }
559
560
        if ($defaultValue !== null) {
561
            $this->setValue($defaultValue);
562
        }
563
    }
564
565
    /**
566
     * @param string $string
567
     * @return string
568
     */
569
    public function transformToDotSyntax($string)
570
    {
571
        return str_replace(['.', '[]', '[', ']'], ['_', '', '.', ''], $string);
572
    }
573
574
    /**
575
     * @return string
576
     */
577
    public function __toString()
578
    {
579
        return $this->getValue();
580
    }
581
}
582