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 (#3796)
by Cristian
12:06
created

Create::getCreateFields()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Backpack\CRUD\app\Library\CrudPanel\Traits;
4
5
use Illuminate\Database\Eloquent\Relations\BelongsTo;
6
use Illuminate\Database\Eloquent\Relations\HasOne;
7
use Illuminate\Support\Arr;
8
9
trait Create
10
{
11
    /*
12
    |--------------------------------------------------------------------------
13
    |                                   CREATE
14
    |--------------------------------------------------------------------------
15
    */
16
17
    /**
18
     * Insert a row in the database.
19
     *
20
     * @param array $data All input values to be inserted.
21
     *
22
     * @return \Illuminate\Database\Eloquent\Model
23
     */
24
    public function create($data)
25
    {
26
        $data = $this->decodeJsonCastedAttributes($data);
0 ignored issues
show
Bug introduced by
It seems like decodeJsonCastedAttributes() 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

26
        /** @scrutinizer ignore-call */ 
27
        $data = $this->decodeJsonCastedAttributes($data);
Loading history...
27
        $data = $this->compactFakeFields($data);
0 ignored issues
show
Bug introduced by
It seems like compactFakeFields() 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

27
        /** @scrutinizer ignore-call */ 
28
        $data = $this->compactFakeFields($data);
Loading history...
28
29
        // omit the relationships when initing the model
30
        $relationships = Arr::pluck($this->getRelationFields(), 'name');
31
32
        // init and fill model
33
        $item = $this->model->make(Arr::except($data, $relationships));
34
35
        // handle BelongsTo 1:1 relations
36
        $item = $this->associateOrDissociateBelongsToRelations($item, $data);
37
        $item->save();
38
39
        // if there are any other relations create them.
40
        $this->createRelations($item, $data);
41
42
        return $item;
43
    }
44
45
    /**
46
     * Get all fields needed for the ADD NEW ENTRY form.
47
     *
48
     * @return array The fields with attributes and fake attributes.
49
     */
50
    public function getCreateFields()
51
    {
52
        return $this->fields();
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

52
        return $this->/** @scrutinizer ignore-call */ fields();
Loading history...
53
    }
54
55
    /**
56
     * Associate and dissociate BelongsTo relations in the model.
57
     *
58
     * @param  Model $item
0 ignored issues
show
Bug introduced by
The type Backpack\CRUD\app\Library\CrudPanel\Traits\Model was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
59
     * @param  array $data The form data.
60
     * @return Model Model with relationships set up.
61
     */
62
    protected function associateOrDissociateBelongsToRelations($item, array $data)
63
    {
64
        $belongsToFields = $this->getFieldsWithRelationType('BelongsTo');
0 ignored issues
show
Bug introduced by
It seems like getFieldsWithRelationType() 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

64
        /** @scrutinizer ignore-call */ 
65
        $belongsToFields = $this->getFieldsWithRelationType('BelongsTo');
Loading history...
65
66
        foreach ($belongsToFields as $relationField) {
67
            if (method_exists($item, $this->getOnlyRelationEntity($relationField))) {
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

67
            if (method_exists($item, $this->/** @scrutinizer ignore-call */ getOnlyRelationEntity($relationField))) {
Loading history...
68
                $relatedId = Arr::get($data, $relationField['name']);
69
                if (isset($relatedId) && $relatedId !== null) {
70
                    $related = $relationField['model']::find($relatedId);
71
72
                    $item->{$this->getOnlyRelationEntity($relationField)}()->associate($related);
73
                } else {
74
                    $item->{$this->getOnlyRelationEntity($relationField)}()->dissociate();
75
                }
76
            }
77
        }
78
79
        return $item;
80
    }
81
82
    /**
83
     * Get all fields with relation set (model key set on field).
84
     *
85
     * @return array The fields with model key set.
86
     */
87
    public function getRelationFields()
88
    {
89
        $fields = $this->fields();
90
        $relationFields = [];
91
92
        foreach ($fields as $field) {
93
            if (isset($field['model']) && $field['model'] !== false) {
94
                array_push($relationFields, $field);
95
            }
96
97
            if (isset($field['subfields']) &&
98
                is_array($field['subfields']) &&
99
                count($field['subfields'])) {
100
                foreach ($field['subfields'] as $subfield) {
101
                    array_push($relationFields, $subfield);
102
                }
103
            }
104
        }
105
106
        return $relationFields;
107
    }
108
109
    /**
110
     * Get all fields with n-n relation set (pivot table is true).
111
     *
112
     * @return array The fields with n-n relationships.
113
     */
114
    public function getRelationFieldsWithPivot()
115
    {
116
        $all_relation_fields = $this->getRelationFields();
117
118
        return Arr::where($all_relation_fields, function ($value, $key) {
119
            return isset($value['pivot']) && $value['pivot'];
120
        });
121
    }
122
123
    /**
124
     * Create the relations for the current model.
125
     *
126
     * @param \Illuminate\Database\Eloquent\Model $item The current CRUD model.
127
     * @param array                               $data The form data.
128
     */
129
    public function createRelations($item, $data)
130
    {
131
        $this->syncPivot($item, $data);
132
        $this->createOneToOneRelations($item, $data);
133
    }
134
135
    /**
136
     * Sync the declared many-to-many associations through the pivot field.
137
     *
138
     * @param \Illuminate\Database\Eloquent\Model $model The current CRUD model.
139
     * @param array                               $data  The form data.
140
     */
141
    public function syncPivot($model, $data)
142
    {
143
        $fields_with_relationships = $this->getRelationFields();
144
        foreach ($fields_with_relationships as $key => $field) {
145
            if (isset($field['pivot']) && $field['pivot']) {
146
                $values = isset($data[$field['name']]) ? $data[$field['name']] : [];
147
148
                // if a JSON was passed instead of an array, turn it into an array
149
                if (is_string($values)) {
150
                    $values = json_decode($values);
151
                }
152
153
                $relation_data = [];
154
                foreach ($values as $pivot_id) {
155
                    $pivot_data = [];
156
157
                    if (isset($field['pivotFields'])) {
158
                        foreach ($field['pivotFields'] as $pivot_field_name) {
159
                            $pivot_data[$pivot_field_name] = $data[$pivot_field_name][$pivot_id];
160
                        }
161
                    }
162
                    $relation_data[$pivot_id] = $pivot_data;
163
                }
164
165
                $model->{$field['name']}()->sync($relation_data);
166
            }
167
168
            if (isset($field['morph']) && $field['morph'] && isset($data[$field['name']])) {
169
                $values = $data[$field['name']];
170
                $model->{$field['name']}()->sync($values);
171
            }
172
        }
173
    }
174
175
    /**
176
     * Create any existing one to one relations for the current model from the form data.
177
     *
178
     * @param \Illuminate\Database\Eloquent\Model $item The current CRUD model.
179
     * @param array                               $data The form data.
180
     */
181
    private function createOneToOneRelations($item, $data)
182
    {
183
        $relationData = $this->getRelationDataFromFormData($data);
184
        $this->createRelationsForItem($item, $relationData);
185
    }
186
187
    /**
188
     * Create any existing one to one relations for the current model from the relation data.
189
     *
190
     * @param \Illuminate\Database\Eloquent\Model $item          The current CRUD model.
191
     * @param array                               $formattedData The form data.
192
     *
193
     * @return bool|null
194
     */
195
    private function createRelationsForItem($item, $formattedData)
196
    {
197
        if (! isset($formattedData['relations'])) {
198
            return false;
199
        }
200
        foreach ($formattedData['relations'] as $relationMethod => $relationData) {
201
            $relation = $item->{$relationMethod}();
202
203
            if ($relation instanceof HasOne) {
204
                if (isset($relationData['relations'])) {
205
                    // if there are nested relations, we first add the BelongsTo like in main entry
206
                    $belongsToRelations = Arr::where($relationData['relations'], function ($relation_data) {
207
                        return isset($relation_data['relation_type']) && $relation_data['relation_type'] == 'BelongsTo';
208
                    });
209
210
                    // adds the values of the BelongsTo relations of this entity to the array of values that will
211
                    // be saved at the same time like we do in parent entity belongs to relations
212
                    $valuesWithRelations = $this->associateHasOneBelongsTo($belongsToRelations, $relationData['values'] ?? [], $relation->getModel());
213
214
                    // remove previously added BelongsTo relations from relation data.
215
                    $relationData['relations'] = Arr::where($relationData['relations'], function ($item) {
216
                        return isset($item['relation_type']) && $item['relation_type'] != 'BelongsTo';
217
                    });
218
219
                    $modelInstance = $relation->updateOrCreate([], $valuesWithRelations);
220
                } else {
221
                    $modelInstance = $relation->updateOrCreate([], $relationData['values']);
222
                }
223
            }
224
225
            if (isset($relationData['relations'])) {
226
                $this->createRelationsForItem($modelInstance, ['relations' => $relationData['relations']]);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $modelInstance does not seem to be defined for all execution paths leading up to this point.
Loading history...
227
            }
228
        }
229
    }
230
231
    /**
232
     * Associate the nested HasOne -> BelongsTo relations by adding the "connecting key"
233
     * to the array of values that is going to be saved with HasOne relation.
234
     *
235
     * @param array $belongsToRelations
236
     * @param array $modelValues
237
     * @param Model $relationInstance
238
     * @return array
239
     */
240
    private function associateHasOneBelongsTo($belongsToRelations, $modelValues, $modelInstance)
241
    {
242
        foreach ($belongsToRelations as $methodName => $values) {
243
            $relation = $modelInstance->{$methodName}();
244
245
            $modelValues[$relation->getForeignKeyName()] = $values['values'][$methodName];
246
        }
247
248
        return $modelValues;
249
    }
250
251
    /**
252
     * Get a relation data array from the form data.
253
     * For each relation defined in the fields through the entity attribute, set the model, the parent model and the
254
     * attribute values.
255
     *
256
     * We traverse this relation array later to create the relations, for example:
257
     *
258
     * Current model HasOne Address, this Address (line_1, country_id) BelongsTo Country through country_id in Address Model.
259
     *
260
     * So when editing current model crud user have two fields address.line_1 and address.country (we infer country_id from relation)
261
     *
262
     * Those will be nested accordingly in this relation array, so address relation will have a nested relation with country.
263
     *
264
     *
265
     * @param array $data The form data.
266
     *
267
     * @return array The formatted relation data.
268
     */
269
    private function getRelationDataFromFormData($data)
270
    {
271
        $relation_fields = Arr::where($this->getRelationFields(), function ($field, $key) {
272
            return $field['relation_type'] !== 'BelongsTo' || $this->isNestedRelation($field);
0 ignored issues
show
Bug introduced by
It seems like isNestedRelation() 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

272
            return $field['relation_type'] !== 'BelongsTo' || $this->/** @scrutinizer ignore-call */ isNestedRelation($field);
Loading history...
273
        });
274
275
        $relationData = [];
276
        foreach ($relation_fields as $relation_field) {
277
            $attributeKey = $this->parseRelationFieldNamesFromHtml([$relation_field])[0]['name'];
0 ignored issues
show
Bug introduced by
It seems like parseRelationFieldNamesFromHtml() 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

277
            $attributeKey = $this->/** @scrutinizer ignore-call */ parseRelationFieldNamesFromHtml([$relation_field])[0]['name'];
Loading history...
278
            if (isset($relation_field['pivot']) && $relation_field['pivot'] !== true) {
279
                $key = implode('.relations.', explode('.', $this->getOnlyRelationEntity($relation_field)));
280
                $fieldData = Arr::get($relationData, 'relations.'.$key, []);
281
                if (! array_key_exists('model', $fieldData)) {
282
                    $fieldData['model'] = $relation_field['model'];
283
                }
284
                if (! array_key_exists('parent', $fieldData)) {
285
                    $fieldData['parent'] = $this->getRelationModel($attributeKey, -1);
0 ignored issues
show
Bug introduced by
The method getRelationModel() does not exist on Backpack\CRUD\app\Library\CrudPanel\Traits\Create. Did you maybe mean getRelationFields()? ( Ignorable by Annotation )

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

285
                    /** @scrutinizer ignore-call */ 
286
                    $fieldData['parent'] = $this->getRelationModel($attributeKey, -1);

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...
286
                }
287
288
                if (! array_key_exists('relation_type', $fieldData)) {
289
                    $fieldData['relation_type'] = $relation_field['relation_type'];
290
                }
291
292
                $relatedAttribute = Arr::last(explode('.', $attributeKey));
293
                $fieldData['values'][$relatedAttribute] = Arr::get($data, $attributeKey);
294
295
                Arr::set($relationData, 'relations.'.$key, $fieldData);
296
            }
297
        }
298
299
        return $relationData;
300
    }
301
}
302