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

Update   B
last analyzed

Complexity

Total Complexity 47

Size/Duplication

Total Lines 276
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 112
dl 0
loc 276
rs 8.64
c 0
b 0
f 0
wmc 47

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getUpdateFields() 0 19 4
A updateModelAndRelations() 0 6 1
A getModelAttributeValue() 0 20 4
A getModelWithFakes() 0 7 2
B getSubfieldsValues() 0 38 9
A getModelAndMethodFromEntity() 0 17 3
D getModelAttributeValueFromRelationship() 0 86 22
A update() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like Update 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 Update, and based on these observations, apply Extract Interface, too.

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->getEntryWithLocale($id) : $this->getCurrentEntryWithLocale();
0 ignored issues
show
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...
Bug introduced by
It seems like getEntryWithLocale() 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 */ getEntryWithLocale($id) : $this->getCurrentEntryWithLocale();
Loading history...
Bug introduced by
It seems like getCurrentEntryWithLocale() 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->getEntryWithLocale($id) : $this->/** @scrutinizer ignore-call */ getCurrentEntryWithLocale();
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) && ! $relatedModel->isRelation($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
                $relationModels = $relatedModel->{$relationMethod};
126
                $result = collect();
127
128
                foreach ($relationModels as $model) {
0 ignored issues
show
introduced by
$model is overwriting one of the parameters of this function.
Loading history...
129
                    $model = $this->setLocaleOnModel($model);
0 ignored issues
show
Bug introduced by
It seems like setLocaleOnModel() 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

129
                    /** @scrutinizer ignore-call */ 
130
                    $model = $this->setLocaleOnModel($model);
Loading history...
130
                    // when subfields are NOT set we don't need to get any more values
131
                    // we just return the plain models as we only need the ids
132
                    if (! isset($field['subfields'])) {
133
                        $result->push($model);
134
135
                        continue;
136
                    }
137
                    // when subfields are set we need to parse their values so they can be displayed
138
                    switch ($relationType) {
139
                        case 'HasMany':
140
                        case 'MorphMany':
141
                            // we will get model direct attributes and merge with subfields values.
142
                            $directAttributes = $this->getModelWithFakes($model)->getAttributes();
143
                            $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

143
                            $result->push(/** @scrutinizer ignore-type */ array_merge($directAttributes, $this->getSubfieldsValues($field['subfields'], $model)));
Loading history...
144
                            break;
145
146
                        case 'BelongsToMany':
147
                        case 'MorphToMany':
148
                            // for any given model, we grab the attributes that belong to our pivot table.
149
                            $item = $model->{$relation->getPivotAccessor()}->getAttributes();
150
                            $item[$relationMethod] = $model->getKey();
151
                            $result->push($item);
152
                            break;
153
                    }
154
                }
155
156
                return $result;
157
                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...
158
            case 'HasOne':
159
            case 'MorphOne':
160
                if (! method_exists($relatedModel, $relationMethod) && ! $relatedModel->isRelation($relationMethod)) {
161
                    return;
162
                }
163
164
                $model = $relatedModel->{$relationMethod};
165
166
                if (! $model) {
167
                    return;
168
                }
169
170
                $model = $this->setLocaleOnModel($model);
171
                $model = $this->getModelWithFakes($model);
172
173
                // if `entity` contains a dot here it means developer added a main HasOne/MorphOne relation with dot notation
174
                if (Str::contains($field['entity'], '.')) {
175
                    return $model->{Str::afterLast($field['entity'], '.')};
176
                }
177
178
                // when subfields exists developer used the repeatable interface to manage this relation
179
                if (isset($field['subfields'])) {
180
                    return [$this->getSubfieldsValues($field['subfields'], $model)];
181
                }
182
183
                return $this->getModelWithFakes($model);
184
185
                break;
186
            case 'BelongsTo':
187
                if ($relatedModel->{$relationMethod}) {
188
                    return $relatedModel->{$relationMethod}->getKey();
189
                }
190
191
                return $relatedModel->{$relationMethod};
192
                break;
193
            default:
194
                return $relatedModel->{$relationMethod};
195
        }
196
    }
197
198
    /**
199
     * This function checks if the provided model uses the CrudTrait.
200
     * If IT DOES it adds the fakes to the model attributes.
201
     * Otherwise just return the model back.
202
     *
203
     * @param  \Illuminate\Database\Eloquent\Model  $model
204
     * @return \Illuminate\Database\Eloquent\Model
205
     */
206
    private function getModelWithFakes($model)
207
    {
208
        if (in_array(\Backpack\CRUD\app\Models\Traits\CrudTrait::class, class_uses_recursive($model))) {
209
            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...
210
        }
211
212
        return $model;
213
    }
214
215
    /**
216
     * Returns the model and the method from the relation string starting from the provided model.
217
     *
218
     * @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...
219
     * @param  array  $field
220
     * @return array
221
     */
222
    private function getModelAndMethodFromEntity($model, $field)
223
    {
224
        // HasOne and MorphOne relations contains the field in the relation string. We want only the relation part.
225
        $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

225
        /** @scrutinizer ignore-call */ 
226
        $relationEntity = $this->getOnlyRelationEntity($field);
Loading history...
226
227
        $relationArray = explode('.', $relationEntity);
228
229
        $relatedModel = array_reduce(array_splice($relationArray, 0, -1), function ($obj, $method) {
230
            // if the string ends with `_id` we strip it out
231
            $method = Str::endsWith($method, '_id') ? Str::replaceLast('_id', '', $method) : $method;
232
233
            return $obj->{$method} ? $obj->{$method} : $obj;
234
        }, $model);
235
236
        $relationMethod = Str::afterLast($relationEntity, '.');
237
238
        return [$relatedModel, $relationMethod];
239
    }
240
241
    /**
242
     * Return the subfields values from the related model.
243
     *
244
     * @param  array  $subfields
245
     * @param  \Illuminate\Database\Eloquent\Model  $relatedModel
246
     * @return array
247
     */
248
    private function getSubfieldsValues($subfields, $relatedModel)
249
    {
250
        $result = [];
251
        foreach ($subfields as $subfield) {
252
            $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...
253
            // if the subfield name does not contain a dot we just need to check
254
            // if it has subfields and return the result accordingly.
255
            foreach ((array) $subfield['name'] as $name) {
256
                if (! Str::contains($name, '.')) {
257
                    // when subfields are present, $relatedModel->{$name} returns a model instance
258
                    // otherwise returns the model attribute.
259
                    if ($relatedModel->{$name} && isset($subfield['subfields'])) {
260
                        $result[$name] = [$relatedModel->{$name}->only(array_column($subfield['subfields'], 'name'))];
261
                    } else {
262
                        $result[$name] = $relatedModel->{$name};
263
                    }
264
                } else {
265
                    // if the subfield name contains a dot, we are going to iterate through
266
                    // those parts to get the last connected part and parse it for returning.
267
                    // $iterator would be either a string (the attribute in model, eg: street)
268
                    // or a model instance (eg: AddressModel)
269
                    $iterator = $relatedModel;
270
271
                    foreach (explode('.', $name) as $part) {
272
                        $iterator = $iterator->$part;
273
                    }
274
275
                    if (is_a($iterator, 'Illuminate\Database\Eloquent\Model', true)) {
276
                        $iterator = $this->setLocaleOnModel($iterator);
277
                        $iterator = $this->getModelWithFakes($iterator)->getAttributes();
278
                    }
279
280
                    Arr::set($result, $name, $iterator);
281
                }
282
            }
283
        }
284
285
        return $result;
286
    }
287
}
288