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 (#4161)
by
unknown
15:22
created

Update::getModelAttributeValue()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 2 Features 0
Metric Value
cc 6
eloc 12
nc 5
nop 2
dl 0
loc 22
rs 9.2222
c 2
b 2
f 0
1
<?php
2
3
namespace Backpack\CRUD\app\Library\CrudPanel\Traits;
4
5
use Illuminate\Support\Arr;
6
use Illuminate\Support\Str;
7
8
trait Update
9
{
10
    /*
11
    |--------------------------------------------------------------------------
12
    |                                   UPDATE
13
    |--------------------------------------------------------------------------
14
    */
15
16
    /**
17
     * Update a row in the database.
18
     *
19
     * @param  int  $id  The entity's id
20
     * @param  array  $input  All inputs to be updated.
21
     * @return object
22
     */
23
    public function update($id, $input)
24
    {
25
        $item = $this->model->findOrFail($id);
26
27
        [$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

27
        /** @scrutinizer ignore-call */ 
28
        [$directInputs, $relationInputs] = $this->splitInputIntoDirectAndRelations($input);
Loading history...
28
        $updated = $item->update($directInputs);
0 ignored issues
show
Unused Code introduced by
The assignment to $updated is dead and can be removed.
Loading history...
29
        $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

29
        $this->/** @scrutinizer ignore-call */ 
30
               createRelationsForItem($item, $relationInputs);
Loading history...
30
31
        return $item;
32
    }
33
34
    /**
35
     * Get all fields needed for the EDIT ENTRY form.
36
     *
37
     * @param  int  $id  The id of the entry that is being edited.
38
     * @return array The fields with attributes, fake attributes and values.
39
     */
40
    public function getUpdateFields($id = false)
41
    {
42
        $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

42
        /** @scrutinizer ignore-call */ 
43
        $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...
43
        $entry = ($id != false) ? $this->getEntry($id) : $this->getCurrentEntry();
0 ignored issues
show
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

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

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

76
            return $this->getModelAttributeValueFromRelationship(/** @scrutinizer ignore-type */ $model, $field);
Loading history...
77
        }
78
79
        if (is_string($field['name'])) {
80
            return $model->{$field['name']};
81
        }
82
83
        if (is_array($field['name'])) {
84
            $result = [];
85
            foreach ($field['name'] as $name) {
86
                $result[] = $model->{$name};
87
            }
88
89
            return $result;
90
        }
91
    }
92
93
    /**
94
     * Returns the value of the given attribute in the relationship.
95
     * It takes into account nested relationships.
96
     *
97
     * @param  \Illuminate\Database\Eloquent\Model  $model
98
     * @param  array  $field
99
     * @return mixed
100
     */
101
    private function getModelAttributeValueFromRelationship($model, $field)
102
    {
103
        [$relatedModel, $relationMethod] = $this->getModelAndMethodFromEntity($model, $field);
104
105
        if (! method_exists($relatedModel, $relationMethod)) {
106
            return $relatedModel->{$relationMethod};
107
        }
108
109
        $relation = $relatedModel->{$relationMethod}();
110
        $relationType = Str::afterLast(get_class($relation), '\\');
111
112
        switch ($relationType) {
113
            case 'MorphMany':
114
            case 'HasMany':
115
            case 'BelongsToMany':
116
            case 'MorphToMany':
117
                $relationModels = $relatedModel->{$relationMethod};
118
                $result = collect();
119
120
                foreach ($relationModels as $model) {
0 ignored issues
show
introduced by
$model is overwriting one of the parameters of this function.
Loading history...
121
                    // when subfields are NOT set we don't need to get any more values
122
                    // we just return the plain models as we only need the ids
123
                    if (! isset($field['subfields'])) {
124
                        $result->push($model);
125
                        continue;
126
                    }
127
                    // when subfields are set we need to parse their values so they can be displayed
128
                    switch ($relationType) {
129
                        case 'HasMany':
130
                        case 'MorphMany':
131
                            // we will get model direct attributes and merge with subfields values.
132
                            $directAttributes = $this->getModelWithFakes($model)->getAttributes();
133
                            $result->push(array_merge($directAttributes, $this->getSubfieldsValues($field['subfields'], $model)));
134
                            break;
135
136
                        case 'BelongsToMany':
137
                        case 'MorphToMany':
138
                            // for any given model, we grab the attributes that belong to our pivot table.
139
                            $item = $model->pivot->getAttributes();
140
                            $item[$relationMethod] = $model->getKey();
141
                            $result->push($item);
142
                            break;
143
                    }
144
                }
145
146
                return $result;
147
                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...
148
            case 'HasOne':
149
            case 'MorphOne':
150
                if (! method_exists($relatedModel, $relationMethod)) {
151
                    return;
152
                }
153
154
                $model = $relatedModel->{$relationMethod};
155
156
                if (! $model) {
157
                    return;
158
                }
159
160
                $model = $this->getModelWithFakes($model);
161
162
                // if `entity` contains a dot here it means developer added a main HasOne/MorphOne relation with dot notation
163
                if (Str::contains($field['entity'], '.')) {
164
                    return $model->{Str::afterLast($field['entity'], '.')};
165
                }
166
167
                // when subfields exists developer used the repeatable interface to manage this relation
168
                if ($field['subfields']) {
169
                    return [$this->getSubfieldsValues($field['subfields'], $model)];
170
                }
171
172
                return $this->getModelWithFakes($model);
173
174
                break;
175
            case 'BelongsTo':
176
                if ($relatedModel->{$relationMethod}) {
177
                    return $relatedModel->{$relationMethod}->getKey();
178
                }
179
180
                return $relatedModel->{$relationMethod};
181
                break;
182
            default:
183
                return $relatedModel->{$relationMethod};
184
        }
185
    }
186
187
    /**
188
     * This function checks if the provided model uses the CrudTrait.
189
     * If IT DOES it adds the fakes to the model attributes.
190
     * Otherwise just return the model back.
191
     *
192
     * @param  \Illuminate\Database\Eloquent\Model  $model
193
     * @return \Illuminate\Database\Eloquent\Model
194
     */
195
    private function getModelWithFakes($model)
196
    {
197
        if (in_array(\Backpack\CRUD\app\Models\Traits\CrudTrait::class, class_uses_recursive($model))) {
198
            return $model->withFakes();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $model->withFakes() also could return the type Illuminate\Database\Eloquent\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Model.
Loading history...
199
        }
200
201
        return $model;
202
    }
203
204
    /**
205
     * Returns the model and the method from the relation string starting from the provided model.
206
     *
207
     * @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...
208
     * @param  array  $field
209
     * @return array
210
     */
211
    private function getModelAndMethodFromEntity($model, $field)
212
    {
213
        // HasOne and MorphOne relations contains the field in the relation string. We want only the relation part.
214
        $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

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