Completed
Push — master ( c833c1...b511be )
by Song
03:16
created

CanCascadeFields::addDependents()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 3
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin\Form\Field;
4
5
use Encore\Admin\Admin;
6
use Encore\Admin\Form;
7
use Illuminate\Support\Arr;
8
9
/**
10
 * @property Form $form
11
 */
12
trait CanCascadeFields
13
{
14
    /**
15
     * @var array
16
     */
17
    protected $conditions = [];
18
19
    /**
20
     * @param $operator
21
     * @param $value
22
     * @param $closure
23
     * @return $this
24
     */
25
    public function when($operator, $value, $closure = null)
26
    {
27
        if (func_num_args() == 2) {
28
            $closure  = $value;
29
            $value    = $operator;
30
            $operator = '=';
31
        }
32
33
        $this->formatValues($operator, $value);
34
35
        $this->addDependents($operator, $value, $closure);
36
37
        return $this;
38
    }
39
40
    /**
41
     * @param string $operator
42
     * @param mixed  $value
43
     */
44
    protected function formatValues(string $operator, &$value)
45
    {
46
        if (in_array($operator, ['in', 'notIn'])) {
47
            $value = Arr::wrap($value);
48
        }
49
50
        if (is_array($value)) {
51
            $value = array_map('strval', $value);
52
        } else {
53
            $value = strval($value);
54
        }
55
    }
56
57
    /**
58
     * @param string $operator
59
     * @param mixed $value
60
     * @param \Closure $closure
61
     */
62
    protected function addDependents(string $operator, $value, \Closure $closure)
63
    {
64
        $this->conditions[] = compact('operator', 'value', 'closure');
65
66
        $dependency = [
67
            'field' => $this->column(),
0 ignored issues
show
Bug introduced by
It seems like column() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
68
            'group' => count($this->conditions) - 1,
69
        ];
70
71
        $this->form->callWithDependency($dependency, $closure);
72
    }
73
74
    /**
75
     * {@inheritDoc}
76
     */
77
    public function fill($data)
78
    {
79
        parent::fill($data);
80
81
        $this->applyCascadeConditions();
82
    }
83
84
    /**
85
     * Apply conditions to dependents fields.
86
     *
87
     * @return void
88
     */
89
    protected function applyCascadeConditions()
90
    {
91
        $this->form->fields()->filter(function (Form\Field $field) {
92
            return $field->isDependsOn($this);
93
        })->each(function (Form\Field $field) {
94
            $group = Arr::get($field->getDependency(), 'group');
0 ignored issues
show
Documentation introduced by
$field->getDependency() is of type object<Encore\Admin\Form\Field>|null, but the function expects a object<ArrayAccess>|array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
95
            $field->setGroupClass(
96
                $this->getDependentsElementClass($group)
97
            );
98
        });
99
    }
100
101
    /**
102
     * @param int $group
103
     * @return array
104
     * @throws \Exception
105
     */
106
    protected function getDependentsElementClass(int $group)
107
    {
108
        $condition = $this->conditions[$group];
109
110
        return [
111
            'cascade',
112
            $this->hitsCondition($condition) ? '' : 'hide',
113
            $this->getCascadeClass($condition['value'])
114
        ];
115
    }
116
117
    /**
118
     * @param mixed $value
119
     * @return string
120
     */
121
    protected function getCascadeClass($value)
122
    {
123
        if (is_array($value)) {
124
            $value = implode('-', $value);
125
        }
126
127
        return sprintf('cascade-%s-%s', $this->getElementClassString(), $value);
0 ignored issues
show
Bug introduced by
It seems like getElementClassString() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
128
    }
129
130
    /**
131
     * @param $operator
132
     * @param $value
133
     * @return bool
134
     * @throws \Exception
135
     */
136
    protected function hitsCondition($condition)
137
    {
138
        extract($condition);
139
140
        $old = old($this->column(), $this->value());
0 ignored issues
show
Bug introduced by
It seems like column() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
Bug introduced by
It seems like value() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
141
142
        switch ($operator) {
143
            case '=' :
144
                return $old == $value;
145
            case '>' :
146
                return $old > $value;
147
            case '<' :
148
                return $old < $value;
149
            case '>=' :
150
                return $old >= $value;
151
            case '<=' :
152
                return $old <= $value;
153
            case '!=' :
154
                return $old != $value;
155
            case 'in' :
156
                return in_array($old, $value);
157
            case 'notIn' :
158
                return !in_array($old, $value);
159
            case 'has' :
160
                return in_array($value, $old);
161
            default:
162
                throw new \Exception("Operator [$operator] not support.");
163
        }
164
    }
165
166
    /**
167
     * Add cascade scripts to contents.
168
     *
169
     * @return void
170
     */
171
    protected function addCascadeScript()
172
    {
173
        if (empty($this->conditions)) {
174
            return;
175
        }
176
177
        $group = [];
178
179
        foreach ($this->conditions as $item) {
180
            $group[] = [
181
                'class'    => $this->getCascadeClass($item['value']),
182
                'operator' => $item['operator'],
183
                'value'    => $item['value']
184
            ];
185
        }
186
187
        $cascadeGroups = json_encode($group);
188
189
        $script = <<<SCRIPT
190
(function () {
191
    var operator_table = {
192
        '=': function(a, b) {
193
            if ($.isArray(a) && $.isArray(b)) {
194
                return $(a).not(b).length === 0 && $(b).not(a).length === 0;
195
            }
196
197
            return a == b;
198
        },
199
        '>': function(a, b) { return a > b; },
200
        '<': function(a, b) { return a < b; },
201
        '>=': function(a, b) { return a >= b; },
202
        '<=': function(a, b) { return a <= b; },
203
        '!=': function(a, b) {
204
             if ($.isArray(a) && $.isArray(b)) {
205
                return !($(a).not(b).length === 0 && $(b).not(a).length === 0);
206
             }
207
208
             return a != b;
209
        },
210
        'in': function(a, b) { return $.inArray(a, b) != -1; },
211
        'notIn': function(a, b) { return $.inArray(a, b) == -1; },
212
        'has': function(a, b) { return $.inArray(b, a) != -1; },
213
    };
214
    var cascade_groups = {$cascadeGroups};
215
    $('{$this->getElementClassSelector()}').on('{$this->cascadeEvent}', function (e) {
0 ignored issues
show
Bug introduced by
The property cascadeEvent does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
Bug introduced by
It seems like getElementClassSelector() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
216
217
        {$this->getFormFrontValue()}
218
219
        cascade_groups.forEach(function (event) {
220
            var group = $('div.form-group.'+event.class);
221
            if( operator_table[event.operator](checked, event.value) ) {
222
                group.removeClass('hide');
223
            } else {
224
                group.addClass('hide');
225
            }
226
        });
227
    })
228
})();
229
SCRIPT;
230
231
        Admin::script($script);
232
    }
233
234
    /**
235
     * @return string
236
     */
237
    protected function getFormFrontValue()
238
    {
239
        switch (get_class($this)) {
240
            case Radio::class:
241
            case RadioButton::class:
242
            case RadioCard::class:
243
            case Select::class:
244
            case BelongsTo::class:
245
            case BelongsToMany::class:
246
            case MultipleSelect::class:
247
                return "var checked = $(this).val();";
248
            case Checkbox::class:
249
            case CheckboxButton::class:
250
            case CheckboxCard::class:
251
                return <<<SCRIPT
252
var checked = $('{$this->getElementClassSelector()}:checked').map(function(){
0 ignored issues
show
Bug introduced by
It seems like getElementClassSelector() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
253
  return $(this).val();
254
}).get();
255
SCRIPT;
256
            default:
257
                throw new \InvalidArgumentException('Invalid form field type');
258
        }
259
    }
260
}
261