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

Test Setup Failed
Pull Request — master (#3383)
by Cristian
17:18
created

Create::getRelationFieldsWithPivot()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 1
nop 0
dl 0
loc 6
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\Model;
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 n-n relationships when updating the eloquent item
30
        $nn_relationships = Arr::pluck($this->getRelationFieldsWithPivot(), 'name');
31
32
        // init and fill model
33
        $item = $this->model->make(Arr::except($data, $nn_relationships));
34
35
        // handle BelongsTo 1:1 relations
36
        $item = $this->associateOrDissociateBelongsToRelations($item, $data);
0 ignored issues
show
Bug introduced by
It seems like associateOrDissociateBelongsToRelations() 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

36
        /** @scrutinizer ignore-call */ 
37
        $item = $this->associateOrDissociateBelongsToRelations($item, $data);
Loading history...
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
     * Get all fields with relation set (model key set on field).
57
     *
58
     * @return array The fields with model key set.
59
     */
60
    public function getRelationFields()
61
    {
62
        $fields = $this->fields();
63
        $relationFields = [];
64
65
        foreach ($fields as $field) {
66
            if (isset($field['model']) && $field['model'] !== false) {
67
                array_push($relationFields, $field);
68
            }
69
70
            if (isset($field['subfields']) &&
71
                is_array($field['subfields']) &&
72
                count($field['subfields'])) {
73
                foreach ($field['subfields'] as $subfield) {
74
                    array_push($relationFields, $subfield);
75
                }
76
            }
77
        }
78
79
        return $relationFields;
80
    }
81
82
    /**
83
     * Get all fields with n-n relation set (pivot table is true).
84
     *
85
     * @return array The fields with n-n relationships.
86
     */
87
    public function getRelationFieldsWithPivot()
88
    {
89
        $all_relation_fields = $this->getRelationFields();
90
91
        return Arr::where($all_relation_fields, function ($value, $key) {
92
            return isset($value['pivot']) && $value['pivot'];
93
        });
94
    }
95
96
    /**
97
     * Create the relations for the current model.
98
     *
99
     * @param \Illuminate\Database\Eloquent\Model $item The current CRUD model.
100
     * @param array                               $data The form data.
101
     */
102
    public function createRelations($item, $data)
103
    {
104
        $this->syncPivot($item, $data);
105
        $this->createOneToOneRelations($item, $data);
106
    }
107
108
    /**
109
     * Sync the declared many-to-many associations through the pivot field.
110
     *
111
     * @param \Illuminate\Database\Eloquent\Model $model The current CRUD model.
112
     * @param array                               $data  The form data.
113
     */
114
    public function syncPivot($model, $data)
115
    {
116
        $fields_with_relationships = $this->getRelationFields();
117
        foreach ($fields_with_relationships as $key => $field) {
118
            if (isset($field['pivot']) && $field['pivot']) {
119
                $values = isset($data[$field['name']]) ? $data[$field['name']] : [];
120
121
                // if a JSON was passed instead of an array, turn it into an array
122
                if (is_string($values)) {
123
                    $values = json_decode($values);
124
                }
125
126
                $relation_data = [];
127
                foreach ($values as $pivot_id) {
128
                    $pivot_data = [];
129
130
                    if (isset($field['pivotFields'])) {
131
                        foreach ($field['pivotFields'] as $pivot_field_name) {
132
                            $pivot_data[$pivot_field_name] = $data[$pivot_field_name][$pivot_id];
133
                        }
134
                    }
135
                    $relation_data[$pivot_id] = $pivot_data;
136
                }
137
138
                $model->{$field['name']}()->sync($relation_data);
139
            }
140
141
            if (isset($field['morph']) && $field['morph'] && isset($data[$field['name']])) {
142
                $values = $data[$field['name']];
143
                $model->{$field['name']}()->sync($values);
144
            }
145
        }
146
    }
147
148
    /**
149
     * Create any existing one to one relations and subsquent relations for the item.
150
     *
151
     * @param \Illuminate\Database\Eloquent\Model $item The current CRUD model.
152
     * @param array                               $data The form data.
153
     */
154
    private function createOneToOneRelations($item, $data)
155
    {
156
        $relationData = $this->getRelationDataFromFormData($data);
157
        $this->createRelationsForItem($item, $relationData);
158
    }
159
160
    /**
161
     * Create any existing one to one relations and subsquent relations from form data.
162
     *
163
     * @param \Illuminate\Database\Eloquent\Model $item          The current CRUD model.
164
     * @param array                               $formattedData The form data.
165
     *
166
     * @return bool|null
167
     */
168
    private function createRelationsForItem($item, $formattedData)
169
    {
170
        if (! isset($formattedData['relations'])) {
171
            return false;
172
        }
173
        foreach ($formattedData['relations'] as $relationMethod => $relationData) {
174
            if (! isset($relationData['model'])) {
175
                continue;
176
            }
177
178
            $relation = $item->{$relationMethod}();
179
180
            if ($relation instanceof HasOne) {
181
                if (isset($relationData['relations'])) {
182
                    $belongsToRelations = Arr::where($relationData['relations'], function ($relation_data) {
183
                        return $relation_data['relation_type'] == 'BelongsTo';
184
                    });
185
                    // adds the values of the BelongsTo relations of this entity to the array of values that will
186
                    // be saved at the same time like we do in parent entity belongs to relations
187
                    $valuesWithRelations = $this->associateHasOneBelongsTo($belongsToRelations, $relationData['values'], $relation->getModel());
188
189
                    $relationData['relations'] = Arr::where($relationData['relations'], function ($item) {
190
                        return $item['relation_type'] != 'BelongsTo';
191
                    });
192
193
                    $modelInstance = $relation->updateOrCreate([], $valuesWithRelations);
194
                } else {
195
                    $modelInstance = $relation->updateOrCreate([], $relationData['values']);
196
                }
197
            }
198
199
            if (isset($relationData['relations'])) {
200
                $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...
201
            }
202
        }
203
    }
204
205
    private function associateHasOneBelongsTo($belongsToRelations, $modelValues, $modelInstance)
206
    {
207
        foreach ($belongsToRelations as $methodName => $values) {
208
            $relation = $modelInstance->{$methodName}();
209
            $modelValues[$relation->getForeignKeyName()] = $values['values'][$methodName];
210
        }
211
212
        return $modelValues;
213
    }
214
215
    /**
216
     * Get a relation data array from the form data.
217
     * For each relation defined in the fields through the entity attribute, set the model, the parent model and the
218
     * attribute values.
219
     *
220
     * We traverse this relation array later to create the relations, for example:
221
     *
222
     * Current model HasOne Address, this Address (line_1, country_id) BelongsTo Country through country_id in Address Model.
223
     *
224
     * So when editing current model crud user have two fields address.line_1 and address.country (we infer country_id from relation)
225
     *
226
     * Those will be nested accordingly in this relation array, so address relation will have a nested relation with country.
227
     *
228
     *
229
     * @param array $data The form data.
230
     *
231
     * @return array The formatted relation data.
232
     */
233
    private function getRelationDataFromFormData($data)
234
    {
235
        // exclude the already attached belongs to relations but include nested belongs to.
236
        $relation_fields = Arr::where($this->getRelationFields(), function ($field, $key) {
237
            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

237
            return $field['relation_type'] !== 'BelongsTo' || $this->/** @scrutinizer ignore-call */ isNestedRelation($field);
Loading history...
238
        });
239
240
        $relationData = [];
241
242
        foreach ($relation_fields as $relation_field) {
243
            $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

243
            $attributeKey = $this->/** @scrutinizer ignore-call */ parseRelationFieldNamesFromHtml([$relation_field])[0]['name'];
Loading history...
244
            if (isset($relation_field['pivot']) && $relation_field['pivot'] !== true) {
245
                $key = implode('.relations.', explode('.', $this->getOnlyRelationEntity($relation_field)));
246
                $fieldData = Arr::get($relationData, 'relations.'.$key, []);
247
                if (! array_key_exists('model', $fieldData)) {
248
                    $fieldData['model'] = $relation_field['model'];
249
                }
250
                if (! array_key_exists('parent', $fieldData)) {
251
                    $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

251
                    /** @scrutinizer ignore-call */ 
252
                    $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...
252
                }
253
254
                if (! array_key_exists('relation_type', $fieldData)) {
255
                    $fieldData['relation_type'] = $relation_field['relation_type'];
256
                }
257
                $relatedAttribute = Arr::last(explode('.', $attributeKey));
258
                $fieldData['values'][$relatedAttribute] = Arr::get($data, $attributeKey);
259
260
                Arr::set($relationData, 'relations.'.$key, $fieldData);
261
            }
262
        }
263
264
        return $relationData;
265
    }
266
267
    public function getOnlyRelationEntity($relation_field)
268
    {
269
        $entity_array = explode('.', $relation_field['entity']);
270
271
        $relation_model = $this->getRelationModel($relation_field['entity'], -1);
272
273
        $related_method = Arr::last($entity_array);
274
275
        if (! method_exists($relation_model, $related_method)) {
276
            if (count($entity_array) <= 1) {
277
                return $relation_field['entity'];
278
            } else {
279
                array_pop($entity_array);
280
            }
281
282
            return implode('.', $entity_array);
283
        }
284
285
        return $relation_field['entity'];
286
    }
287
}
288