Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Passed
Pull Request — master (#3897)
by
unknown
12:30
created

FieldsProtectedMethods   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 319
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 94
c 4
b 0
f 0
dl 0
loc 319
rs 6
wmc 55

15 Methods

Rating   Name   Duplication   Size   Complexity  
A makeSureFieldHasName() 0 11 4
A makeSureFieldHasModel() 0 5 1
A makeSureFieldHasMultiple() 0 7 2
A makeSureFieldHasRelationType() 0 5 1
A makeSureFieldHasPivot() 0 5 1
A overwriteFieldNameFromDotNotationToArray() 0 14 5
B makeSureFieldHasEntity() 0 38 7
A getFieldKey() 0 7 2
A overwriteFieldNameFromEntity() 0 22 5
A makeSureFieldHasLabel() 0 9 3
A addFieldToOperationSettings() 0 8 1
A makeSureFieldHasType() 0 7 3
A enableTabsIfFieldUsesThem() 0 6 3
A makeSureFieldHasAttribute() 0 9 4
C handleRepeatableFieldsToJsonColumn() 0 47 13

How to fix   Complexity   

Complex Class

Complex classes like FieldsProtectedMethods often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FieldsProtectedMethods, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Backpack\CRUD\app\Library\CrudPanel\Traits;
4
5
use Illuminate\Support\Arr;
6
use Illuminate\Support\Str;
7
8
trait FieldsProtectedMethods
9
{
10
    /**
11
     * If field has entity we want to get the relation type from it.
12
     *
13
     * @param  array  $field
14
     * @return array
15
     */
16
    public function makeSureFieldHasRelationType($field)
17
    {
18
        $field['relation_type'] = $field['relation_type'] ?? $this->inferRelationTypeFromRelationship($field);
0 ignored issues
show
Bug introduced by
It seems like inferRelationTypeFromRelationship() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

18
        $field['relation_type'] = $field['relation_type'] ?? $this->/** @scrutinizer ignore-call */ inferRelationTypeFromRelationship($field);
Loading history...
19
20
        return $field;
21
    }
22
23
    /**
24
     * If field has entity we want to make sure it also has a model for that relation.
25
     *
26
     * @param  array  $field
27
     * @return array
28
     */
29
    public function makeSureFieldHasModel($field)
30
    {
31
        $field['model'] = $field['model'] ?? $this->inferFieldModelFromRelationship($field);
0 ignored issues
show
Bug introduced by
It seems like inferFieldModelFromRelationship() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

31
        $field['model'] = $field['model'] ?? $this->/** @scrutinizer ignore-call */ inferFieldModelFromRelationship($field);
Loading history...
32
33
        return $field;
34
    }
35
36
    /**
37
     * Based on relation type we can guess if pivot is set.
38
     *
39
     * @param  array  $field
40
     * @return array
41
     */
42
    public function makeSureFieldHasPivot($field)
43
    {
44
        $field['pivot'] = $field['pivot'] ?? $this->guessIfFieldHasPivotFromRelationType($field['relation_type']);
0 ignored issues
show
Bug introduced by
It seems like guessIfFieldHasPivotFromRelationType() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

44
        $field['pivot'] = $field['pivot'] ?? $this->/** @scrutinizer ignore-call */ guessIfFieldHasPivotFromRelationType($field['relation_type']);
Loading history...
45
46
        return $field;
47
    }
48
49
    /**
50
     * Based on relation type we can try to guess if it is a multiple field.
51
     *
52
     * @param  array  $field
53
     * @return array
54
     */
55
    public function makeSureFieldHasMultiple($field)
56
    {
57
        if (isset($field['relation_type'])) {
58
            $field['multiple'] = $field['multiple'] ?? $this->guessIfFieldHasMultipleFromRelationType($field['relation_type']);
0 ignored issues
show
Bug introduced by
It seems like guessIfFieldHasMultipleFromRelationType() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

58
            $field['multiple'] = $field['multiple'] ?? $this->/** @scrutinizer ignore-call */ guessIfFieldHasMultipleFromRelationType($field['relation_type']);
Loading history...
59
        }
60
61
        return $field;
62
    }
63
64
    /**
65
     * In case field name is dot notation we want to convert it to a valid HTML array field name for validation purposes.
66
     *
67
     * @param  array  $field
68
     * @return array
69
     */
70
    public function overwriteFieldNameFromDotNotationToArray($field)
71
    {
72
        if (! is_array($field['name']) && strpos($field['name'], '.') !== false) {
73
            $entity_array = explode('.', $field['name']);
74
            $name_string = '';
75
76
            foreach ($entity_array as $key => $array_entity) {
77
                $name_string .= ($key == 0) ? $array_entity : '['.$array_entity.']';
78
            }
79
80
            $field['name'] = $name_string;
81
        }
82
83
        return $field;
84
    }
85
86
    /**
87
     * If the field_definition_array array is a string, it means the programmer was lazy
88
     * and has only passed the name of the field. Turn that into a proper array.
89
     *
90
     * @param  string|array  $field  The field definition array (or string).
91
     * @return array
92
     */
93
    protected function makeSureFieldHasName($field)
94
    {
95
        if (is_string($field)) {
96
            return ['name' => $field];
97
        }
98
99
        if (is_array($field) && ! isset($field['name'])) {
100
            abort(500, 'All fields must have their name defined');
101
        }
102
103
        return $field;
104
    }
105
106
    /**
107
     * If entity is not present, but it looks like the field SHOULD be a relationship field,
108
     * try to determine the method on the model that defines the relationship, and pass it to
109
     * the field as 'entity'.
110
     *
111
     * @param  [type] $field [description]
0 ignored issues
show
Documentation Bug introduced by
The doc comment [type] at position 0 could not be parsed: Unknown type name '[' at position 0 in [type].
Loading history...
112
     * @return [type]        [description]
0 ignored issues
show
Documentation Bug introduced by
The doc comment [type] at position 0 could not be parsed: Unknown type name '[' at position 0 in [type].
Loading history...
113
     */
114
    protected function makeSureFieldHasEntity($field)
115
    {
116
        if (isset($field['entity'])) {
117
            return $field;
118
        }
119
120
        // if the name is an array it's definitely not a relationship
121
        if (is_array($field['name'])) {
122
            return $field;
123
        }
124
125
        //if the name is dot notation we are sure it's a relationship
126
        if (strpos($field['name'], '.') !== false) {
127
            $field['entity'] = $field['name'];
128
129
            return $field;
130
        }
131
132
        // if there's a method on the model with this name
133
        if (method_exists($this->model, $field['name'])) {
134
            $field['entity'] = $field['name'];
135
136
            return $field;
137
        }
138
139
        // if the name ends with _id and that method exists,
140
        // we can probably use it as an entity
141
        if (Str::endsWith($field['name'], '_id')) {
142
            $possibleMethodName = Str::replaceLast('_id', '', $field['name']);
143
144
            if (method_exists($this->model, $possibleMethodName)) {
145
                $field['entity'] = $possibleMethodName;
146
147
                return $field;
148
            }
149
        }
150
151
        return $field;
152
    }
153
154
    protected function overwriteFieldNameFromEntity($field)
155
    {
156
        // if the entity doesn't have a dot, it means we don't need to overwrite the name
157
        if (! Str::contains($field['entity'], '.')) {
158
            return $field;
159
        }
160
161
        // only 1-1 relationships are supported, if it's anything else, abort
162
        if ($field['relation_type'] != 'HasOne') {
163
            return $field;
164
        }
165
166
        if (count(explode('.', $field['entity'])) == count(explode('.', $this->getOnlyRelationEntity($field)))) {
0 ignored issues
show
Bug introduced by
It seems like getOnlyRelationEntity() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

166
        if (count(explode('.', $field['entity'])) == count(explode('.', $this->/** @scrutinizer ignore-call */ getOnlyRelationEntity($field)))) {
Loading history...
167
            $field['name'] = implode('.', array_slice(explode('.', $field['entity']), 0, -1));
168
            $relation = $this->getRelationInstance($field);
0 ignored issues
show
Bug introduced by
It seems like getRelationInstance() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

168
            /** @scrutinizer ignore-call */ 
169
            $relation = $this->getRelationInstance($field);
Loading history...
169
            if (! empty($field['name'])) {
170
                $field['name'] .= '.';
171
            }
172
            $field['name'] .= $relation->getForeignKeyName();
173
        }
174
175
        return $field;
176
    }
177
178
    protected function makeSureFieldHasAttribute($field)
179
    {
180
        // if there's a model defined, but no attribute
181
        // guess an attribute using the identifiableAttribute functionality in CrudTrait
182
        if (isset($field['model']) && ! isset($field['attribute']) && method_exists($field['model'], 'identifiableAttribute')) {
183
            $field['attribute'] = call_user_func([(new $field['model']), 'identifiableAttribute']);
184
        }
185
186
        return $field;
187
    }
188
189
    /**
190
     * Set the label of a field, if it's missing, by capitalizing the name and replacing
191
     * underscores with spaces.
192
     *
193
     * @param  array  $field  Field definition array.
194
     * @return array Field definition array that contains label too.
195
     */
196
    protected function makeSureFieldHasLabel($field)
197
    {
198
        if (! isset($field['label'])) {
199
            $name = is_array($field['name']) ? $field['name'][0] : $field['name'];
200
            $name = str_replace('_id', '', $name);
201
            $field['label'] = mb_ucfirst(str_replace('_', ' ', $name));
202
        }
203
204
        return $field;
205
    }
206
207
    /**
208
     * Set the type of a field, if it's missing, by inferring it from the
209
     * db column type.
210
     *
211
     * @param  array  $field  Field definition array.
212
     * @return array Field definition array that contains type too.
213
     */
214
    protected function makeSureFieldHasType($field)
215
    {
216
        if (! isset($field['type'])) {
217
            $field['type'] = isset($field['relation_type']) ? $this->inferFieldTypeFromFieldRelation($field) : $this->inferFieldTypeFromDbColumnType($field['name']);
0 ignored issues
show
Bug introduced by
It seems like inferFieldTypeFromDbColumnType() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

217
            $field['type'] = isset($field['relation_type']) ? $this->inferFieldTypeFromFieldRelation($field) : $this->/** @scrutinizer ignore-call */ inferFieldTypeFromDbColumnType($field['name']);
Loading history...
Bug introduced by
It seems like inferFieldTypeFromFieldRelation() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

217
            $field['type'] = isset($field['relation_type']) ? $this->/** @scrutinizer ignore-call */ inferFieldTypeFromFieldRelation($field) : $this->inferFieldTypeFromDbColumnType($field['name']);
Loading history...
218
        }
219
220
        return $field;
221
    }
222
223
    /**
224
     * Enable the tabs functionality, if a field has a tab defined.
225
     *
226
     * @param  array  $field  Field definition array.
227
     * @return void
228
     */
229
    protected function enableTabsIfFieldUsesThem($field)
230
    {
231
        // if a tab was mentioned, we should enable it
232
        if (isset($field['tab'])) {
233
            if (! $this->tabsEnabled()) {
0 ignored issues
show
Bug introduced by
It seems like tabsEnabled() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

233
            if (! $this->/** @scrutinizer ignore-call */ tabsEnabled()) {
Loading history...
234
                $this->enableTabs();
0 ignored issues
show
Bug introduced by
The method enableTabs() does not exist on Backpack\CRUD\app\Librar...\FieldsProtectedMethods. Did you maybe mean enableTabsIfFieldUsesThem()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

234
                $this->/** @scrutinizer ignore-call */ 
235
                       enableTabs();

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...
235
            }
236
        }
237
    }
238
239
    /**
240
     * Add a field to the current operation, using the Settings API.
241
     *
242
     * @param  array  $field  Field definition array.
243
     */
244
    protected function addFieldToOperationSettings($field)
245
    {
246
        $fieldKey = $this->getFieldKey($field);
247
248
        $allFields = $this->getOperationSetting('fields');
0 ignored issues
show
Unused Code introduced by
The assignment to $allFields is dead and can be removed.
Loading history...
Bug introduced by
It seems like getOperationSetting() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

248
        /** @scrutinizer ignore-call */ 
249
        $allFields = $this->getOperationSetting('fields');
Loading history...
249
        $allFields = Arr::add($this->fields(), $fieldKey, $field);
0 ignored issues
show
Bug introduced by
It seems like fields() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

249
        $allFields = Arr::add($this->/** @scrutinizer ignore-call */ fields(), $fieldKey, $field);
Loading history...
250
251
        $this->setOperationSetting('fields', $allFields);
0 ignored issues
show
Bug introduced by
It seems like setOperationSetting() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

251
        $this->/** @scrutinizer ignore-call */ 
252
               setOperationSetting('fields', $allFields);
Loading history...
252
    }
253
254
    /**
255
     * Get the string that should be used as an array key, for the attributive array
256
     * where the fields are stored for the current operation.
257
     *
258
     * The array key for the field should be:
259
     * - name (if the name is a string)
260
     * - name1_name2_name3 (if the name is an array)
261
     *
262
     * @param  array  $field  Field definition array.
263
     * @return string The string that should be used as array key.
264
     */
265
    protected function getFieldKey($field)
266
    {
267
        if (is_array($field['name'])) {
268
            return implode('_', $field['name']);
269
        }
270
271
        return $field['name'];
272
    }
273
274
    /**
275
     * Handle repeatable fields conversion to json and mutates the needed attributes.
276
     *
277
     * @param  array  $data  the form data
278
     * @return array $data the form data with parsed repeatable inputs to be stored
279
     */
280
    protected function handleRepeatableFieldsToJsonColumn($data)
281
    {
282
        $repeatable_fields = array_filter($this->fields(), function ($field) {
283
            return $field['type'] === 'repeatable';
284
        });
285
286
        if (empty($repeatable_fields)) {
287
            return $data;
288
        }
289
290
        $repeatable_data_fields = collect($data)->filter(function ($value, $key) use ($repeatable_fields, &$data) {
291
            if (in_array($key, array_column($repeatable_fields, 'name'))) {
292
                if (! is_string($value)) {
293
                    return true;
294
                } else {
295
                    unset($data[$key]);
296
297
                    return false;
298
                }
299
            }
300
        })->toArray();
301
302
        // cicle all the repeatable fields
303
        foreach ($repeatable_fields as $repeatable_name => $repeatable_field) {
304
            $deleted_elements = json_decode(request()->input($repeatable_name.'_deleted_elements') ?? null, true);
0 ignored issues
show
Bug introduced by
It seems like request()->input($repeat...eted_elements') ?? null can also be of type null; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

304
            $deleted_elements = json_decode(/** @scrutinizer ignore-type */ request()->input($repeatable_name.'_deleted_elements') ?? null, true);
Loading history...
305
            $changed_elements = json_decode(request()->input($repeatable_name.'_changed_elements') ?? null, true);
306
307
            if (isset($repeatable_field['onDelete']) && is_callable($repeatable_field['onDelete']) && ! empty($deleted_elements)) {
308
                $repeatable_field['onDelete']($deleted_elements, $changed_elements);
309
            }
310
311
            // check if any of the repeatable fields have a onCreate mutator and run it!
312
            foreach ($repeatable_field['fields'] as $key => $repeatable_subfield) {
313
                if (isset($repeatable_data_fields[$repeatable_name])) {
314
                    if (isset($repeatable_subfield['onCreate']) && is_callable($repeatable_subfield['onCreate'])) {
315
                        foreach ($repeatable_data_fields[$repeatable_name] as $field_key => $field_value) {
316
                            $repeatable_data_fields[$repeatable_name][$field_key][$repeatable_subfield['name']] = $repeatable_subfield['onCreate']($field_value[$repeatable_subfield['name']], $changed_elements, $deleted_elements);
317
                        }
318
                    }
319
                }
320
            }
321
322
            // set the properly json encoded string to be stored in database
323
            $data[$repeatable_name] = json_encode($repeatable_data_fields[$repeatable_name] ?? []);
324
        }
325
326
        return $data;
327
    }
328
}
329