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

Relationships::inferFieldTypeFromFieldRelation()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 10
nc 7
nop 1
dl 0
loc 13
rs 8.8333
c 0
b 0
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 Relationships
9
{
10
    /**
11
     * From the field entity we get the relation instance.
12
     *
13
     * @param  array  $entity
14
     * @return object
15
     */
16
    public function getRelationInstance($field)
17
    {
18
        $entity = $this->getOnlyRelationEntity($field);
19
        $possible_method = Str::before($entity, '.');
20
        $model = isset($field['baseModel']) ? app($field['baseModel']) : $this->model;
21
22
        if (method_exists($model, $possible_method)) {
23
            $parts = explode('.', $entity);
24
            // here we are going to iterate through all relation parts to check
25
            foreach ($parts as $i => $part) {
26
                $relation = $model->$part();
27
                $model = $relation->getRelated();
28
            }
29
30
            return $relation;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $relation seems to be defined by a foreach iteration on line 25. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
31
        }
32
33
        abort(500, 'Looks like field <code>'.$field['name'].'</code> is not properly defined. The <code>'.$field['entity'].'()</code> relationship doesn\'t seem to exist on the <code>'.get_class($model).'</code> model.');
34
    }
35
36
    /**
37
     * Grabs an relation instance and returns the class name of the related model.
38
     *
39
     * @param  array  $field
40
     * @return string
41
     */
42
    public function inferFieldModelFromRelationship($field)
43
    {
44
        $relation = $this->getRelationInstance($field);
45
46
        return get_class($relation->getRelated());
47
    }
48
49
    /**
50
     * Return the relation type from a given field: BelongsTo, HasOne ... etc.
51
     *
52
     * @param  array  $field
53
     * @return string
54
     */
55
    public function inferRelationTypeFromRelationship($field)
56
    {
57
        $relation = $this->getRelationInstance($field);
58
59
        return Arr::last(explode('\\', get_class($relation)));
60
    }
61
62
    public function getOnlyRelationEntity($field)
63
    {
64
        $entity = isset($field['baseEntity']) ? $field['baseEntity'].'.'.$field['entity'] : $field['entity'];
65
        $model = $this->getRelationModel($entity, -1);
0 ignored issues
show
Bug introduced by
The method getRelationModel() does not exist on Backpack\CRUD\app\Librar...el\Traits\Relationships. Did you maybe mean getRelationInstance()? ( Ignorable by Annotation )

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

65
        /** @scrutinizer ignore-call */ 
66
        $model = $this->getRelationModel($entity, -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...
66
        $lastSegmentAfterDot = Str::of($field['entity'])->afterLast('.');
67
68
        if (! method_exists($model, $lastSegmentAfterDot)) {
69
            return (string) Str::of($field['entity'])->beforeLast('.');
70
        }
71
72
        return $field['entity'];
73
    }
74
75
    /**
76
     * Get the fields for relationships, according to the relation type. It looks only for direct
77
     * relations - it will NOT look through relationships of relationships.
78
     *
79
     * @param  string|array  $relation_types  Eloquent relation class or array of Eloquent relation classes. Eg: BelongsTo
80
     * @param  bool  $nested  Should nested fields be included
81
     * @return array The fields with corresponding relation types.
82
     */
83
    public function getFieldsWithRelationType($relation_types, $nested = false): array
84
    {
85
        $relation_types = (array) $relation_types;
86
87
        return collect($this->getCleanStateFields())
0 ignored issues
show
Bug introduced by
It seems like getCleanStateFields() 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

87
        return collect($this->/** @scrutinizer ignore-call */ getCleanStateFields())
Loading history...
88
            ->whereIn('relation_type', $relation_types)
89
            ->filter(function ($item) use ($nested) {
90
                if ($nested) {
91
                    return true;
92
                }
93
94
                return Str::contains($item['entity'], '.') ? false : true;
95
            })
96
            ->toArray();
97
    }
98
99
    /**
100
     * Parse the field name back to the related entity after the form is submited.
101
     * Its called in getAllFieldNames().
102
     *
103
     * @param  array  $fields
104
     * @return array
105
     */
106
    public function parseRelationFieldNamesFromHtml($fields)
107
    {
108
        foreach ($fields as &$field) {
109
            //we only want to parse fields that has a relation type and their name contains [ ] used in html.
110
            if (isset($field['relation_type']) && preg_match('/[\[\]]/', $field['name']) !== 0) {
111
                $chunks = explode('[', $field['name']);
112
113
                foreach ($chunks as &$chunk) {
114
                    if (strpos($chunk, ']')) {
115
                        $chunk = str_replace(']', '', $chunk);
116
                    }
117
                }
118
                $field['name'] = implode('.', $chunks);
119
            }
120
        }
121
122
        return $fields;
123
    }
124
125
    /**
126
     * Gets the relation fields that DON'T contain the provided relations.
127
     *
128
     * @param  string|array  $relations  - the relations to exclude
129
     * @param  array  $fields
130
     */
131
    private function getRelationFieldsWithoutRelationType($relations, $fields = [])
132
    {
133
        if (! is_array($relations)) {
134
            $relations = [$relations];
135
        }
136
137
        if (empty($fields)) {
138
            $fields = $this->getRelationFields();
0 ignored issues
show
Bug introduced by
The method getRelationFields() does not exist on Backpack\CRUD\app\Librar...el\Traits\Relationships. Did you maybe mean getRelationFieldsWithoutPivot()? ( Ignorable by Annotation )

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

138
            /** @scrutinizer ignore-call */ 
139
            $fields = $this->getRelationFields();

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...
139
        }
140
141
        foreach ($relations as $relation) {
142
            $fields = array_filter($fields, function ($field) use ($relation) {
143
                if (! isset($field['relation_type'])) {
144
                    return false;
145
                }
146
147
                return $field['relation_type'] !== $relation;
148
            });
149
        }
150
151
        return $fields;
152
    }
153
154
    /**
155
     * Changes the input names to use the foreign_key, instead of the relation name,
156
     * for BelongsTo relations (eg. "user_id" instead of "user").
157
     *
158
     * When $fields are provided, we will use those fields to determine the correct
159
     * foreign key. Otherwise, we will use the main CRUD fields.
160
     *
161
     * eg: user -> user_id
162
     *
163
     * @param  array  $input
164
     * @param  array  $belongsToFields
165
     * @return array
166
     */
167
    private function changeBelongsToNamesFromRelationshipToForeignKey($input, $fields = [])
168
    {
169
        if (empty($fields)) {
170
            $fields = $this->getFieldsWithRelationType('BelongsTo');
171
        } else {
172
            foreach ($fields as $field) {
173
                if (isset($field['subfields'])) {
174
                    $fields = array_merge($field['subfields'], $fields);
175
                }
176
            }
177
            $fields = array_filter($fields, function ($field) {
178
                return isset($field['relation_type']) && $field['relation_type'] === 'BelongsTo';
179
            });
180
        }
181
182
        foreach ($fields as $field) {
183
            $foreignKey = $this->getOverwrittenNameForBelongsTo($field);
184
            $lastFieldNameSegment = Str::afterLast($field['name'], '.');
185
186
            if (Arr::has($input, $lastFieldNameSegment) && $lastFieldNameSegment !== $foreignKey) {
187
                Arr::set($input, $foreignKey, Arr::get($input, $lastFieldNameSegment));
188
                Arr::forget($input, $lastFieldNameSegment);
189
            }
190
        }
191
192
        return $input;
193
    }
194
195
    /**
196
     * Based on relation type returns if relation allows multiple entities.
197
     *
198
     * @param  string  $relation_type
199
     * @return bool
200
     */
201
    public function guessIfFieldHasMultipleFromRelationType($relation_type)
202
    {
203
        switch ($relation_type) {
204
            case 'BelongsToMany':
205
            case 'HasMany':
206
            case 'HasManyThrough':
207
            case 'HasOneOrMany':
208
            case 'MorphMany':
209
            case 'MorphOneOrMany':
210
            case 'MorphToMany':
211
                return true;
212
213
            default:
214
                return false;
215
        }
216
    }
217
218
    /**
219
     * Based on relation type returns if relation has a pivot table.
220
     *
221
     * @param  string  $relation_type
222
     * @return bool
223
     */
224
    public function guessIfFieldHasPivotFromRelationType($relation_type)
225
    {
226
        switch ($relation_type) {
227
            case 'BelongsToMany':
228
            case 'HasManyThrough':
229
            case 'MorphToMany':
230
                return true;
231
            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...
232
            default:
233
                return false;
234
        }
235
    }
236
237
    /**
238
     * Get all relation fields that don't have pivot set.
239
     *
240
     * @return array The fields with model key set.
241
     */
242
    public function getRelationFieldsWithoutPivot()
243
    {
244
        $all_relation_fields = $this->getRelationFields();
245
246
        return Arr::where($all_relation_fields, function ($value, $key) {
247
            return isset($value['pivot']) && ! $value['pivot'];
248
        });
249
    }
250
251
    /**
252
     * Get all fields with n-n relation set (pivot table is true).
253
     *
254
     * @return array The fields with n-n relationships.
255
     */
256
    public function getRelationFieldsWithPivot()
257
    {
258
        $all_relation_fields = $this->getRelationFields();
259
260
        return Arr::where($all_relation_fields, function ($value, $key) {
261
            return isset($value['pivot']) && $value['pivot'];
262
        });
263
    }
264
265
    /**
266
     * Return the name for the BelongTo relation making sure it always has the
267
     * foreign_key instead of relationName (eg. "user_id", not "user").
268
     *
269
     * @param  array  $field  The field we want to get the name from
270
     * @return string
271
     */
272
    private function getOverwrittenNameForBelongsTo($field)
273
    {
274
        $relation = $this->getRelationInstance($field);
275
276
        if (Str::afterLast($field['name'], '.') === $relation->getRelationName()) {
277
            return $relation->getForeignKeyName();
278
        }
279
280
        return $field['name'];
281
    }
282
283
    /**
284
     * Returns the pivot definition for BelongsToMany/MorphToMany relation provided in $field.
285
     *
286
     * @param  array  $field
287
     * @return array
288
     */
289
    private static function getPivotFieldStructure($field)
290
    {
291
        $pivotSelectorField['name'] = $field['name'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$pivotSelectorField was never initialized. Although not strictly required by PHP, it is generally a good practice to add $pivotSelectorField = array(); before regardless.
Loading history...
292
        $pivotSelectorField['type'] = 'relationship';
293
        $pivotSelectorField['is_pivot_select'] = true;
294
        $pivotSelectorField['multiple'] = false;
295
        $pivotSelectorField['entity'] = $field['name'];
296
        $pivotSelectorField['relation_type'] = $field['relation_type'];
297
        $pivotSelectorField['model'] = $field['model'];
298
        $pivotSelectorField['minimum_input_length'] = 2;
299
        $pivotSelectorField['delay'] = 500;
300
        $pivotSelectorField['placeholder'] = trans('backpack::crud.select_entry');
301
        $pivotSelectorField['label'] = \Str::of($field['name'])->singular()->ucfirst();
302
303
        if (isset($field['baseModel'])) {
304
            $pivotSelectorField['baseModel'] = $field['baseModel'];
305
        }
306
        if (isset($field['baseEntity'])) {
307
            $pivotSelectorField['baseEntity'] = $field['baseEntity'];
308
        }
309
310
        return $pivotSelectorField;
311
    }
312
}
313