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
Push — use-db-transactions ( f233f9 )
by Pedro
31:23
created

Update::update()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
nc 2
nop 2
dl 0
loc 10
rs 10
c 1
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\Support\Arr;
7
use Illuminate\Support\Str;
8
use Illuminate\Support\Facades\DB;
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')) {
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')) {
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
        $item->update($directInputs);
39
        $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

39
        $this->/** @scrutinizer ignore-call */ 
40
               createRelationsForItem($item, $relationInputs);
Loading history...
40
        return $item;
41
    }
42
43
    /**
44
     * Get all fields needed for the EDIT ENTRY form.
45
     *
46
     * @param  int  $id  The id of the entry that is being edited.
47
     * @return array The fields with attributes, fake attributes and values.
48
     */
49
    public function getUpdateFields($id = false)
50
    {
51
        $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

51
        /** @scrutinizer ignore-call */ 
52
        $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...
52
        $entry = ($id != false) ? $this->getEntry($id) : $this->getCurrentEntry();
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 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

52
        $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

52
        $entry = ($id != false) ? $this->getEntry($id) : $this->/** @scrutinizer ignore-call */ getCurrentEntry();
Loading history...
53
54
        foreach ($fields as &$field) {
55
            $field['value'] = $field['value'] ?? $this->getModelAttributeValue($entry, $field);
56
        }
57
58
        // always have a hidden input for the entry id
59
        if (! array_key_exists('id', $fields)) {
60
            $fields['id'] = [
61
                'name'  => $entry->getKeyName(),
62
                'value' => $entry->getKey(),
63
                'type'  => 'hidden',
64
            ];
65
        }
66
67
        return $fields;
68
    }
69
70
    /**
71
     * Get the value of the 'name' attribute from the declared relation model in the given field.
72
     *
73
     * @param  \Illuminate\Database\Eloquent\Model  $model  The current CRUD model.
74
     * @param  array  $field  The CRUD field array.
75
     * @return mixed The value of the 'name' attribute from the relation model.
76
     */
77
    private function getModelAttributeValue($model, $field)
78
    {
79
        $model = $model->withFakes();
80
81
        $fieldEntity = $field['entity'] ?? false;
82
        $fakeField = $field['fake'] ?? false;
83
84
        if ($fieldEntity && ! $fakeField) {
85
            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

85
            return $this->getModelAttributeValueFromRelationship(/** @scrutinizer ignore-type */ $model, $field);
Loading history...
86
        }
87
88
        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

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

141
                            $result->push(/** @scrutinizer ignore-type */ array_merge($directAttributes, $this->getSubfieldsValues($field['subfields'], $model)));
Loading history...
142
                            break;
143
144
                        case 'BelongsToMany':
145
                        case 'MorphToMany':
146
                            // for any given model, we grab the attributes that belong to our pivot table.
147
                            $item = $model->{$relation->getPivotAccessor()}->getAttributes();
148
                            $item[$relationMethod] = $model->getKey();
149
                            $result->push($item);
150
                            break;
151
                    }
152
                }
153
154
                return $result;
155
                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...
156
            case 'HasOne':
157
            case 'MorphOne':
158
                if (! method_exists($relatedModel, $relationMethod)) {
159
                    return;
160
                }
161
162
                $model = $relatedModel->{$relationMethod};
163
164
                if (! $model) {
165
                    return;
166
                }
167
168
                $model = $this->setupRelatedModelLocale($model);
169
                $model = $this->getModelWithFakes($model);
170
171
                // if `entity` contains a dot here it means developer added a main HasOne/MorphOne relation with dot notation
172
                if (Str::contains($field['entity'], '.')) {
173
                    return $model->{Str::afterLast($field['entity'], '.')};
174
                }
175
176
                // when subfields exists developer used the repeatable interface to manage this relation
177
                if (isset($field['subfields'])) {
178
                    return [$this->getSubfieldsValues($field['subfields'], $model)];
179
                }
180
181
                return $this->getModelWithFakes($model);
182
183
                break;
184
            case 'BelongsTo':
185
                if ($relatedModel->{$relationMethod}) {
186
                    return $relatedModel->{$relationMethod}->getKey();
187
                }
188
189
                return $relatedModel->{$relationMethod};
190
                break;
191
            default:
192
                return $relatedModel->{$relationMethod};
193
        }
194
    }
195
196
    /**
197
     * Set the locale on the related models.
198
     *
199
     * @param  \Illuminate\Database\Eloquent\Model  $model
200
     * @return \Illuminate\Database\Eloquent\Model
201
     */
202
    private function setupRelatedModelLocale($model)
203
    {
204
        if (method_exists($model, 'translationEnabled') && $model->translationEnabled()) {
205
            $locale = request('_locale', \App::getLocale());
206
            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

206
            if (in_array($locale, array_keys(/** @scrutinizer ignore-type */ $model->getAvailableLocales()))) {
Loading history...
207
                $model->setLocale($locale);
208
            }
209
        }
210
211
        return $model;
212
    }
213
214
    /**
215
     * This function checks if the provided model uses the CrudTrait.
216
     * If IT DOES it adds the fakes to the model attributes.
217
     * Otherwise just return the model back.
218
     *
219
     * @param  \Illuminate\Database\Eloquent\Model  $model
220
     * @return \Illuminate\Database\Eloquent\Model
221
     */
222
    private function getModelWithFakes($model)
223
    {
224
        if (in_array(\Backpack\CRUD\app\Models\Traits\CrudTrait::class, class_uses_recursive($model))) {
225
            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...
226
        }
227
228
        return $model;
229
    }
230
231
    /**
232
     * Returns the model and the method from the relation string starting from the provided model.
233
     *
234
     * @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...
235
     * @param  array  $field
236
     * @return array
237
     */
238
    private function getModelAndMethodFromEntity($model, $field)
239
    {
240
        // HasOne and MorphOne relations contains the field in the relation string. We want only the relation part.
241
        $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

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