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 — main (#5535)
by
unknown
19:17 queued 04:18
created

Update::getModelAndMethodFromEntity()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 17
rs 10
1
<?php
2
3
namespace Backpack\CRUD\app\Library\CrudPanel\Traits;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Facades\DB;
8
use Illuminate\Support\Str;
9
10
trait Update
11
{
12
    /*
13
    |--------------------------------------------------------------------------
14
    |                                   UPDATE
15
    |--------------------------------------------------------------------------
16
    */
17
18
    /**
19
     * Update a row in the database.
20
     *
21
     * @param  int  $id  The entity's id
22
     * @param  array  $input  All inputs to be updated.
23
     * @return object
24
     */
25
    public function update($id, $input)
26
    {
27
        $item = $this->model->findOrFail($id);
28
29
        [$directInputs, $relationInputs] = $this->splitInputIntoDirectAndRelations($input);
0 ignored issues
show
Bug introduced by
It seems like splitInputIntoDirectAndRelations() 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
        [$directInputs, $relationInputs] = $this->splitInputIntoDirectAndRelations($input);
Loading history...
30
        if ($this->get('update.useDatabaseTransactions') ?? config('backpack.base.useDatabaseTransactions', false)) {
0 ignored issues
show
Bug introduced by
It seems like get() 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

30
        if ($this->/** @scrutinizer ignore-call */ get('update.useDatabaseTransactions') ?? config('backpack.base.useDatabaseTransactions', false)) {
Loading history...
31
            return DB::transaction(fn () => $this->updateModelAndRelations($item, $directInputs, $relationInputs));
32
        }
33
34
        return $this->updateModelAndRelations($item, $directInputs, $relationInputs);
35
    }
36
37
    private function updateModelAndRelations(Model $item, array $directInputs, array $relationInputs): Model
38
    {
39
        $item->update($directInputs);
40
        $this->createRelationsForItem($item, $relationInputs);
0 ignored issues
show
Bug introduced by
It seems like createRelationsForItem() 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

40
        $this->/** @scrutinizer ignore-call */ 
41
               createRelationsForItem($item, $relationInputs);
Loading history...
41
42
        return $item;
43
    }
44
45
    /**
46
     * Get all fields needed for the EDIT ENTRY form.
47
     *
48
     * @param  int  $id  The id of the entry that is being edited.
49
     * @return array The fields with attributes, fake attributes and values.
50
     */
51
    public function getUpdateFields($id = false)
52
    {
53
        $fields = $this->fields();
0 ignored issues
show
Bug introduced by
The method fields() does not exist on Backpack\CRUD\app\Library\CrudPanel\Traits\Update. Did you maybe mean getSubfieldsValues()? ( Ignorable by Annotation )

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

53
        /** @scrutinizer ignore-call */ 
54
        $fields = $this->fields();

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...
54
        $entry = ($id != false) ? $this->getEntry($id) : $this->getCurrentEntry();
0 ignored issues
show
Bug introduced by
It seems like getEntry() 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

54
        $entry = ($id != false) ? $this->/** @scrutinizer ignore-call */ getEntry($id) : $this->getCurrentEntry();
Loading history...
Bug introduced by
It seems like getCurrentEntry() 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

54
        $entry = ($id != false) ? $this->getEntry($id) : $this->/** @scrutinizer ignore-call */ getCurrentEntry();
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing $id of type false|integer against false; this is ambiguous if the integer can be zero. Consider using a strict comparison !== instead.
Loading history...
55
56
        foreach ($fields as &$field) {
57
            $field['value'] = $field['value'] ?? $this->getModelAttributeValue($entry, $field);
58
        }
59
60
        // always have a hidden input for the entry id
61
        if (! array_key_exists('id', $fields)) {
62
            $fields['id'] = [
63
                'name' => $entry->getKeyName(),
64
                'value' => $entry->getKey(),
65
                'type' => 'hidden',
66
            ];
67
        }
68
69
        return $fields;
70
    }
71
72
    /**
73
     * Get the value of the 'name' attribute from the declared relation model in the given field.
74
     *
75
     * @param  \Illuminate\Database\Eloquent\Model  $model  The current CRUD model.
76
     * @param  array  $field  The CRUD field array.
77
     * @return mixed The value of the 'name' attribute from the relation model.
78
     */
79
    private function getModelAttributeValue($model, $field)
80
    {
81
        $model = $model->withFakes();
82
83
        $fieldEntity = $field['entity'] ?? false;
84
        $fakeField = $field['fake'] ?? false;
85
86
        if ($fieldEntity && ! $fakeField) {
87
            return $this->getModelAttributeValueFromRelationship($model, $field);
0 ignored issues
show
Bug introduced by
It seems like $model can also be of type Illuminate\Database\Eloquent\Builder and Illuminate\Database\Eloq...gHasThroughRelationship; however, parameter $model of Backpack\CRUD\app\Librar...ValueFromRelationship() does only seem to accept Illuminate\Database\Eloquent\Model, 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

87
            return $this->getModelAttributeValueFromRelationship(/** @scrutinizer ignore-type */ $model, $field);
Loading history...
88
        }
89
90
        if ($this->holdsMultipleInputs($field['name'])) {
0 ignored issues
show
Bug introduced by
It seems like holdsMultipleInputs() 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

90
        if ($this->/** @scrutinizer ignore-call */ holdsMultipleInputs($field['name'])) {
Loading history...
91
            $result = array_map(function ($item) use ($model) {
92
                return $model->{$item};
93
            }, explode(',', $field['name']));
94
95
            return $result;
96
        }
97
98
        return $model->{$field['name']};
99
    }
100
101
    /**
102
     * Returns the value of the given attribute in the relationship.
103
     * It takes into account nested relationships.
104
     *
105
     * @param  \Illuminate\Database\Eloquent\Model  $model
106
     * @param  array  $field
107
     * @return mixed
108
     */
109
    private function getModelAttributeValueFromRelationship($model, $field)
110
    {
111
        [$relatedModel, $relationMethod] = $this->getModelAndMethodFromEntity($model, $field);
112
113
        if (! method_exists($relatedModel, $relationMethod)) {
114
            return $relatedModel->{$relationMethod};
115
        }
116
117
        $relation = $relatedModel->{$relationMethod}();
118
        $relationType = Str::afterLast(get_class($relation), '\\');
119
120
        switch ($relationType) {
121
            case 'MorphMany':
122
            case 'HasMany':
123
            case 'BelongsToMany':
124
            case 'MorphToMany':
125
                if (
126
                    ($relationType == 'BelongsToMany' || $relationType == 'MorphToMany') &&
127
                    $relatedModel->{$relationMethod}->count() > 0 &&
128
                    $relatedModel->{$relationMethod}[0]->{$relatedModel->{$relationMethod}()->getRelatedPivotKeyName()}) {
129
                    $key = $relatedModel->{$relationMethod}[0]->{$relatedModel->{$relationMethod}()->getRelatedPivotKeyName()}->getKeyName();
130
                    $relationModels = $relatedModel->{$relationMethod}()->withPivot($key)->get();
131
                } else {
132
                    $relationModels = $relatedModel->{$relationMethod};
133
                }
134
                $result = collect();
135
136
                foreach ($relationModels as $model) {
0 ignored issues
show
introduced by
$model is overwriting one of the parameters of this function.
Loading history...
137
                    $model = $this->setupRelatedModelLocale($model);
138
                    // when subfields are NOT set we don't need to get any more values
139
                    // we just return the plain models as we only need the ids
140
                    if (! isset($field['subfields'])) {
141
                        $result->push($model);
142
143
                        continue;
144
                    }
145
                    // when subfields are set we need to parse their values so they can be displayed
146
                    switch ($relationType) {
147
                        case 'HasMany':
148
                        case 'MorphMany':
149
                            // we will get model direct attributes and merge with subfields values.
150
                            $directAttributes = $this->getModelWithFakes($model)->getAttributes();
151
                            $result->push(array_merge($directAttributes, $this->getSubfieldsValues($field['subfields'], $model)));
0 ignored issues
show
Bug introduced by
array_merge($directAttri...['subfields'], $model)) of type array is incompatible with the type Illuminate\Support\TValue expected by parameter $values of Illuminate\Support\Collection::push(). ( Ignorable by Annotation )

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

151
                            $result->push(/** @scrutinizer ignore-type */ array_merge($directAttributes, $this->getSubfieldsValues($field['subfields'], $model)));
Loading history...
152
                            break;
153
154
                        case 'BelongsToMany':
155
                        case 'MorphToMany':
156
                            // for any given model, we grab the attributes that belong to our pivot table.
157
                            $item = $model->{$relation->getPivotAccessor()}->getAttributes();
158
                            $item[$relationMethod] = $model->getKey();
159
                            $result->push($item);
160
                            break;
161
                    }
162
                }
163
164
                return $result;
165
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
166
            case 'HasOne':
167
            case 'MorphOne':
168
                if (! method_exists($relatedModel, $relationMethod)) {
169
                    return;
170
                }
171
172
                $model = $relatedModel->{$relationMethod};
173
174
                if (! $model) {
175
                    return;
176
                }
177
178
                $model = $this->setupRelatedModelLocale($model);
179
                $model = $this->getModelWithFakes($model);
180
181
                // if `entity` contains a dot here it means developer added a main HasOne/MorphOne relation with dot notation
182
                if (Str::contains($field['entity'], '.')) {
183
                    return $model->{Str::afterLast($field['entity'], '.')};
184
                }
185
186
                // when subfields exists developer used the repeatable interface to manage this relation
187
                if (isset($field['subfields'])) {
188
                    return [$this->getSubfieldsValues($field['subfields'], $model)];
189
                }
190
191
                return $this->getModelWithFakes($model);
192
193
                break;
194
            case 'BelongsTo':
195
                if ($relatedModel->{$relationMethod}) {
196
                    return $relatedModel->{$relationMethod}->getKey();
197
                }
198
199
                return $relatedModel->{$relationMethod};
200
                break;
201
            default:
202
                return $relatedModel->{$relationMethod};
203
        }
204
    }
205
206
    /**
207
     * Set the locale on the related models.
208
     *
209
     * @param  \Illuminate\Database\Eloquent\Model  $model
210
     * @return \Illuminate\Database\Eloquent\Model
211
     */
212
    private function setupRelatedModelLocale($model)
213
    {
214
        if (method_exists($model, 'translationEnabled') && $model->translationEnabled()) {
215
            $locale = request('_locale', \App::getLocale());
216
            if (in_array($locale, array_keys($model->getAvailableLocales()))) {
0 ignored issues
show
Bug introduced by
It seems like $model->getAvailableLocales() can also be of type Illuminate\Database\Eloquent\Builder and Illuminate\Database\Eloq...gHasThroughRelationship; however, parameter $array of array_keys() does only seem to accept array, 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

216
            if (in_array($locale, array_keys(/** @scrutinizer ignore-type */ $model->getAvailableLocales()))) {
Loading history...
217
                $model->setLocale($locale);
218
            }
219
        }
220
221
        return $model;
222
    }
223
224
    /**
225
     * This function checks if the provided model uses the CrudTrait.
226
     * If IT DOES it adds the fakes to the model attributes.
227
     * Otherwise just return the model back.
228
     *
229
     * @param  \Illuminate\Database\Eloquent\Model  $model
230
     * @return \Illuminate\Database\Eloquent\Model
231
     */
232
    private function getModelWithFakes($model)
233
    {
234
        if (in_array(\Backpack\CRUD\app\Models\Traits\CrudTrait::class, class_uses_recursive($model))) {
235
            return $model->withFakes();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $model->withFakes() also could return the type Illuminate\Database\Eloq...gHasThroughRelationship which is incompatible with the documented return type Illuminate\Database\Eloquent\Model.
Loading history...
236
        }
237
238
        return $model;
239
    }
240
241
    /**
242
     * Returns the model and the method from the relation string starting from the provided model.
243
     *
244
     * @param  Illuminate\Database\Eloquent\Model  $model
0 ignored issues
show
Bug introduced by
The type Backpack\CRUD\app\Librar...Database\Eloquent\Model was not found. Did you mean Illuminate\Database\Eloquent\Model? If so, make sure to prefix the type with \.
Loading history...
245
     * @param  array  $field
246
     * @return array
247
     */
248
    private function getModelAndMethodFromEntity($model, $field)
249
    {
250
        // HasOne and MorphOne relations contains the field in the relation string. We want only the relation part.
251
        $relationEntity = $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

251
        /** @scrutinizer ignore-call */ 
252
        $relationEntity = $this->getOnlyRelationEntity($field);
Loading history...
252
253
        $relationArray = explode('.', $relationEntity);
254
255
        $relatedModel = array_reduce(array_splice($relationArray, 0, -1), function ($obj, $method) {
256
            // if the string ends with `_id` we strip it out
257
            $method = Str::endsWith($method, '_id') ? Str::replaceLast('_id', '', $method) : $method;
258
259
            return $obj->{$method} ? $obj->{$method} : $obj;
260
        }, $model);
261
262
        $relationMethod = Str::afterLast($relationEntity, '.');
263
264
        return [$relatedModel, $relationMethod];
265
    }
266
267
    /**
268
     * Return the subfields values from the related model.
269
     *
270
     * @param  array  $subfields
271
     * @param  \Illuminate\Database\Eloquent\Model  $relatedModel
272
     * @return array
273
     */
274
    private function getSubfieldsValues($subfields, $relatedModel)
275
    {
276
        $result = [];
277
        foreach ($subfields as $subfield) {
278
            $name = is_string($subfield) ? $subfield : $subfield['name'];
0 ignored issues
show
Unused Code introduced by
The assignment to $name is dead and can be removed.
Loading history...
279
            // if the subfield name does not contain a dot we just need to check
280
            // if it has subfields and return the result accordingly.
281
            foreach ((array) $subfield['name'] as $name) {
282
                if (! Str::contains($name, '.')) {
283
                    // when subfields are present, $relatedModel->{$name} returns a model instance
284
                    // otherwise returns the model attribute.
285
                    if ($relatedModel->{$name}) {
286
                        if (isset($subfield['subfields'])) {
287
                            $result[$name] = [$relatedModel->{$name}->only(array_column($subfield['subfields'], 'name'))];
288
                        } else {
289
                            $result[$name] = $relatedModel->{$name};
290
                        }
291
                    }
292
                } else {
293
                    // if the subfield name contains a dot, we are going to iterate through
294
                    // those parts to get the last connected part and parse it for returning.
295
                    // $iterator would be either a string (the attribute in model, eg: street)
296
                    // or a model instance (eg: AddressModel)
297
                    $iterator = $relatedModel;
298
299
                    foreach (explode('.', $name) as $part) {
300
                        $iterator = $iterator->$part;
301
                    }
302
303
                    Arr::set($result, $name, is_a($iterator, 'Illuminate\Database\Eloquent\Model', true) ? $this->getModelWithFakes($iterator)->getAttributes() : $iterator);
304
                }
305
            }
306
        }
307
308
        return $result;
309
    }
310
}
311