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 (#3200)
by Cristian
16:03
created

Create::getRelationFieldsWithRepeatable()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 3
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 6
rs 10
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\HasMany;
7
use Illuminate\Database\Eloquent\Relations\HasOne;
8
use Illuminate\Database\Eloquent\Relations\MorphMany;
9
use Illuminate\Support\Arr;
10
11
trait Create
12
{
13
    /*
14
    |--------------------------------------------------------------------------
15
    |                                   CREATE
16
    |--------------------------------------------------------------------------
17
    */
18
19
    /**
20
     * Insert a row in the database.
21
     *
22
     * @param array $data All input values to be inserted.
23
     *
24
     * @return \Illuminate\Database\Eloquent\Model
25
     */
26
    public function create($data)
27
    {
28
        $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

28
        /** @scrutinizer ignore-call */ 
29
        $data = $this->decodeJsonCastedAttributes($data);
Loading history...
29
        $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

29
        /** @scrutinizer ignore-call */ 
30
        $data = $this->compactFakeFields($data);
Loading history...
30
31
        // omit the n-n relationships when updating the eloquent item
32
        $nn_relationships = Arr::pluck($this->getRelationFieldsWithPivot(), 'name');
33
        $repeatable_relationships = Arr::pluck($this->getRelationFieldsWithRepeatable(), 'name');
34
        $excluded_relationships = array_merge($nn_relationships, $repeatable_relationships);
35
        $item = $this->model->create(Arr::except($data, $excluded_relationships));
36
37
        // if there are any relationships available, also sync those
38
        $this->createRelations($item, $data);
39
40
        return $item;
41
    }
42
43
    /**
44
     * Get all fields needed for the ADD NEW ENTRY form.
45
     *
46
     * @return array The fields with attributes and fake attributes.
47
     */
48
    public function getCreateFields()
49
    {
50
        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

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

255
            $attributeKey = $this->/** @scrutinizer ignore-call */ parseRelationFieldNamesFromHtml([$relation_field])[0]['name'];
Loading history...
256
257
            if (! is_null(Arr::get($data, $attributeKey)) && isset($relation_field['pivot']) && $relation_field['pivot'] !== true) {
258
                $key = implode('.relations.', explode('.', $this->getOnlyRelationEntity($relation_field)));
259
                $fieldData = Arr::get($relationData, 'relations.'.$key, []);
260
                if (! array_key_exists('model', $fieldData)) {
261
                    $fieldData['model'] = $relation_field['model'];
262
                }
263
                if (! array_key_exists('parent', $fieldData)) {
264
                    $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

264
                    /** @scrutinizer ignore-call */ 
265
                    $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...
265
                }
266
                $relatedAttribute = Arr::last(explode('.', $attributeKey));
267
                $fieldData['values'][$relatedAttribute] = Arr::get($data, $attributeKey);
268
269
                Arr::set($relationData, 'relations.'.$key, $fieldData);
270
            }
271
        }
272
273
        return $relationData;
274
    }
275
276
    public function getOnlyRelationEntity($relation_field)
277
    {
278
        $entity_array = explode('.', $relation_field['entity']);
279
280
        $relation_model = $this->getRelationModel($relation_field['entity'], -1);
281
282
        $related_method = Arr::last($entity_array);
283
284
        if (! method_exists($relation_model, $related_method)) {
285
            if (count($entity_array) <= 1) {
286
                return $relation_field['entity'];
287
            } else {
288
                array_pop($entity_array);
289
            }
290
291
            return implode('.', $entity_array);
292
        }
293
294
        return $relation_field['entity'];
295
    }
296
}
297