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 Failed
Pull Request — main (#4529)
by Cristian
27:11 queued 12:09
created

Validation   D

Complexity

Total Complexity 59

Size/Duplication

Total Lines 373
Duplicated Lines 0 %

Importance

Changes 5
Bugs 3 Features 0
Metric Value
eloc 120
c 5
b 3
f 0
dl 0
loc 373
rs 4.08
wmc 59

18 Methods

Rating   Name   Duplication   Size   Complexity  
B getValidationMessagesFromFieldsAndSubfields() 0 28 7
A setValidationFromArray() 0 5 1
A getValidationRulesAndMessagesFromField() 0 22 6
A isRequired() 0 11 3
A getFormRequest() 0 3 1
A mergeRules() 0 14 4
A setFormRequest() 0 3 1
A getValidationRulesFromFieldsAndSubfields() 0 27 5
A getRequestRulesAsArray() 0 8 3
A validateRequest() 0 22 4
A setValidation() 0 10 6
A unsetValidation() 0 6 1
A setValidationFromFields() 0 13 1
B setRequiredFields() 0 35 11
A disableValidation() 0 3 1
A setupFieldValidation() 0 6 2
A setValidationFromRequest() 0 4 1
A checkRequestValidity() 0 6 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace Backpack\CRUD\app\Library\CrudPanel\Traits;
4
5
use Illuminate\Foundation\Http\FormRequest;
6
use Illuminate\Support\Str;
7
8
trait Validation
9
{
10
    /**
11
     * Adds the required rules from an array and allows validation of that array.
12
     *
13
     * @param  array  $requiredFields
14
     */
15
    public function setValidationFromArray(array $rules, array $messages = [])
16
    {
17
        $this->setRequiredFields($rules);
18
        $this->setOperationSetting('validationRules', array_merge($this->getOperationSetting('validationRules') ?? [], $rules));
0 ignored issues
show
Bug introduced by
It seems like getOperationSetting() 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

18
        $this->setOperationSetting('validationRules', array_merge($this->/** @scrutinizer ignore-call */ getOperationSetting('validationRules') ?? [], $rules));
Loading history...
Bug introduced by
It seems like setOperationSetting() 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

18
        $this->/** @scrutinizer ignore-call */ 
19
               setOperationSetting('validationRules', array_merge($this->getOperationSetting('validationRules') ?? [], $rules));
Loading history...
19
        $this->setOperationSetting('validationMessages', array_merge($this->getOperationSetting('validationMessages') ?? [], $messages));
20
    }
21
22
    /**
23
     * Take the rules defined on fields and create a validation
24
     * array from them.
25
     */
26
    public function setValidationFromFields()
27
    {
28
        $fields = $this->getOperationSetting('fields');
29
30
        // construct the validation rules array
31
        // (eg. ['name' => 'required|min:2'])
32
        $rules = $this->getValidationRulesFromFieldsAndSubfields($fields);
33
34
        // construct the validation messages array
35
        // (eg. ['title.required' => 'You gotta write smth man.'])
36
        $messages = $this->getValidationMessagesFromFieldsAndSubfields($fields);
37
38
        $this->setValidationFromArray($rules, $messages);
39
    }
40
41
    /**
42
     * Return the rules for the fields and subfields in the current crud panel.
43
     *
44
     * @param  array  $fields
45
     * @return array
46
     */
47
    private function getValidationRulesFromFieldsAndSubfields($fields)
48
    {
49
        $rules = collect($fields)
0 ignored issues
show
Bug introduced by
$fields of type array is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $value of collect(). ( Ignorable by Annotation )

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

49
        $rules = collect(/** @scrutinizer ignore-type */ $fields)
Loading history...
50
            ->filter(function ($value, $key) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed. ( Ignorable by Annotation )

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

50
            ->filter(function ($value, /** @scrutinizer ignore-unused */ $key) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
51
                // only keep fields where 'validationRules' OR there are subfields
52
                return array_key_exists('validationRules', $value) || array_key_exists('subfields', $value);
53
            })->map(function ($item, $key) {
54
                $validationRules = [];
55
                // only keep the rules, not the entire field definition
56
                if (isset($item['validationRules'])) {
57
                    $validationRules[$key] = $item['validationRules'];
58
                }
59
                // add validation rules for subfields
60
                if (array_key_exists('subfields', $item)) {
61
                    $subfieldsWithValidation = array_filter($item['subfields'], function ($subfield) {
62
                        return array_key_exists('validationRules', $subfield);
63
                    });
64
65
                    foreach ($subfieldsWithValidation as $subfield) {
66
                        $validationRules[$item['name'].'.*.'.$subfield['name']] = $subfield['validationRules'];
67
                    }
68
                }
69
70
                return $validationRules;
71
            })->toArray();
72
73
        return array_merge(...array_values($rules));
74
    }
75
76
    /**
77
     * Return the messages for the fields and subfields in the current crud panel.
78
     *
79
     * @param  array  $fields
80
     * @return array
81
     */
82
    private function getValidationMessagesFromFieldsAndSubfields($fields)
83
    {
84
        $messages = [];
85
        collect($fields)
0 ignored issues
show
Bug introduced by
$fields of type array is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $value of collect(). ( Ignorable by Annotation )

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

85
        collect(/** @scrutinizer ignore-type */ $fields)
Loading history...
86
            ->filter(function ($value, $key) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed. ( Ignorable by Annotation )

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

86
            ->filter(function ($value, /** @scrutinizer ignore-unused */ $key) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
87
                // only keep fields where 'validationMessages' OR there are subfields
88
                return array_key_exists('validationMessages', $value) || array_key_exists('subfields', $value);
89
            })->each(function ($item, $key) use (&$messages) {
90
                if (isset($item['validationMessages'])) {
91
                    foreach ($item['validationMessages'] as $rule => $message) {
92
                        $messages[$key.'.'.$rule] = $message;
93
                    }
94
                }
95
                // add messages from subfields
96
                if (array_key_exists('subfields', $item)) {
97
                    $subfieldsWithValidationMessages = array_filter($item['subfields'], function ($subfield) {
98
                        return array_key_exists('validationRules', $subfield);
99
                    });
100
101
                    foreach ($subfieldsWithValidationMessages as $subfield) {
102
                        foreach ($subfield['validationMessages'] ?? [] as $rule => $message) {
103
                            $messages[$item['name'].'.*.'.$subfield['name'].'.'.$rule] = $message;
104
                        }
105
                    }
106
                }
107
            })->toArray();
108
109
        return $messages;
110
    }
111
112
    /**
113
     * Mark a FormRequest file as required for the current operation, in Settings.
114
     * Adds the required rules to an array for easy access.
115
     *
116
     * @param  string  $class  Class that extends FormRequest
117
     */
118
    public function setValidationFromRequest($class)
119
    {
120
        $this->setFormRequest($class);
121
        $this->setRequiredFields($class);
122
    }
123
124
    /**
125
     * Mark a FormRequest file as required for the current operation, in Settings.
126
     * Adds the required rules to an array for easy access.
127
     *
128
     * @param  string|array  $classOrRulesArray  Class that extends FormRequest or array of validation rules
129
     * @param  array  $messages  Array of validation messages.
130
     */
131
    public function setValidation($classOrRulesArray = false, $messages = [])
132
    {
133
        if (! $classOrRulesArray) {
134
            $this->setValidationFromFields();
135
        } elseif (is_array($classOrRulesArray)) {
136
            $this->setValidationFromArray($classOrRulesArray, $messages);
137
        } elseif (is_string($classOrRulesArray) && class_exists($classOrRulesArray) && is_a($classOrRulesArray, FormRequest::class, true)) {
138
            $this->setValidationFromRequest($classOrRulesArray);
139
        } else {
140
            abort(500, 'Please pass setValidation() nothing, a rules array or a FormRequest class.');
141
        }
142
    }
143
144
    /**
145
     * Remove the current FormRequest from configuration, so it will no longer be validated.
146
     */
147
    public function unsetValidation()
148
    {
149
        $this->setOperationSetting('formRequest', false);
150
        $this->setOperationSetting('validationRules', []);
151
        $this->setOperationSetting('validationMessages', []);
152
        $this->setOperationSetting('requiredFields', []);
153
    }
154
155
    /**
156
     * Remove the current FormRequest from configuration, so it will no longer be validated.
157
     */
158
    public function disableValidation()
159
    {
160
        $this->unsetValidation();
161
    }
162
163
    /**
164
     * Mark a FormRequest file as required for the current operation, in Settings.
165
     *
166
     * @param  string  $class  Class that extends FormRequest
167
     */
168
    public function setFormRequest($class)
169
    {
170
        $this->setOperationSetting('formRequest', $class);
171
    }
172
173
    /**
174
     * Get the current form request file, in any.
175
     * Returns null if no FormRequest is required for the current operation.
176
     *
177
     * @return string Class that extends FormRequest
178
     */
179
    public function getFormRequest()
180
    {
181
        return $this->getOperationSetting('formRequest');
182
    }
183
184
    /**
185
     * Run the authorization and validation for the current crud panel.
186
     * That authorization is gathered from 3 places:
187
     * - the FormRequest when provided.
188
     * - the rules added in the controller.
189
     * - the rules defined in the fields itself.
190
     *
191
     * @return \Illuminate\Http\Request
192
     */
193
    public function validateRequest()
194
    {
195
        $formRequest = $this->getFormRequest();
196
197
        $rules = $this->getOperationSetting('validationRules') ?? [];
198
        $messages = $this->getOperationSetting('validationMessages') ?? [];
199
200
        if ($formRequest) {
201
            // when there is no validation in the fields, just validate the form request.
202
            if (empty($rules)) {
203
                return app($formRequest);
0 ignored issues
show
Bug Best Practice introduced by
The expression return app($formRequest) also could return the type Illuminate\Contracts\Foundation\Application which is incompatible with the documented return type Illuminate\Http\Request.
Loading history...
204
            }
205
206
            $formRequest = (new $formRequest)->createFrom($this->getRequest());
0 ignored issues
show
Bug introduced by
The method getRequest() does not exist on Backpack\CRUD\app\Librar...Panel\Traits\Validation. Did you maybe mean getRequestRulesAsArray()? ( Ignorable by Annotation )

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

206
            $formRequest = (new $formRequest)->createFrom($this->/** @scrutinizer ignore-call */ getRequest());

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...
207
            $extendedRules = $this->mergeRules($formRequest, $rules);
208
            $extendedMessages = array_merge($messages, $formRequest->messages());
209
210
            // validate the complete request with FormRequest + controller validation + field validation (our anonymous class)
211
            return $this->checkRequestValidity($extendedRules, $extendedMessages, $formRequest);
212
        }
213
214
        return ! empty($rules) ? $this->checkRequestValidity($rules, $messages) : $this->getRequest();
215
    }
216
217
    /**
218
     * Return an array containing the request rules and the field/controller rules merged.
219
     * The rules in request will take precedence over the ones in controller/fields.
220
     *
221
     * @param  \Illuminate\Http\Request  $request
222
     * @param  array  $rules
223
     * @return array
224
     */
225
    private function mergeRules($request, $rules)
226
    {
227
        $extendedRules = [];
228
        $requestRules = $this->getRequestRulesAsArray($request);
229
        $rules = array_map(function ($ruleDefinition) {
230
            return is_array($ruleDefinition) ? $ruleDefinition : explode('|', $ruleDefinition);
231
        }, $rules);
232
233
        foreach ($requestRules as $ruleKey => $rule) {
234
            $extendedRules[$ruleKey] = array_key_exists($ruleKey, $rules) ? array_merge($rule, $rules[$ruleKey]) : $rule;
235
            unset($rules[$ruleKey]);
236
        }
237
238
        return array_merge($rules, $extendedRules);
239
    }
240
241
    /**
242
     * Return the request rules as an array of rules if developer provided a rule string configuration.
243
     *
244
     * @param  \Illuminate\Http\Request  $request
245
     * @return array
246
     */
247
    private function getRequestRulesAsArray($request)
248
    {
249
        $requestRules = [];
250
        foreach ($request->rules() as $ruleKey => $rule) {
251
            $requestRules[$ruleKey] = is_array($rule) ? $rule : explode('|', $rule);
252
        }
253
254
        return $requestRules;
255
    }
256
257
    /**
258
     * Checks if the request is valid against the rules.
259
     *
260
     * @param  array  $rules
261
     * @param  array  $messages
262
     * @param  \Illuminate\Http\Request|null  $request
263
     * @return \Illuminate\Http\Request
264
     */
265
    private function checkRequestValidity($rules, $messages, $request = null)
266
    {
267
        $request = $request ?? $this->getRequest();
268
        $request->validate($rules, $messages);
269
270
        return $request;
271
    }
272
273
    /**
274
     * Parse a FormRequest class, figure out what inputs are required
275
     * and store this knowledge in the current object.
276
     *
277
     * @param  string|array  $classOrRulesArray  Class that extends FormRequest or rules array
278
     */
279
    public function setRequiredFields($classOrRulesArray)
280
    {
281
        $requiredFields = $this->getOperationSetting('requiredFields') ?? [];
282
283
        if (is_array($classOrRulesArray)) {
284
            $rules = $classOrRulesArray;
285
        } else {
286
            $formRequest = new $classOrRulesArray();
287
            $rules = $formRequest->rules();
288
        }
289
290
        if (count($rules)) {
291
            foreach ($rules as $key => $rule) {
292
                if (
293
                    (is_string($rule) && strpos($rule, 'required') !== false && strpos($rule, 'required_') === false) ||
294
                    (is_array($rule) && array_search('required', $rule) !== false && array_search('required_', $rule) === false)
295
                ) {
296
                    if (Str::contains($key, '.')) {
297
                        $key = Str::dotsToSquareBrackets($key, ['*']);
298
                    }
299
300
                    $requiredFields[] = $key;
301
                }
302
            }
303
        }
304
305
        // merge any previous required fields with current ones
306
        $requiredFields = array_merge($this->getOperationSetting('requiredFields') ?? [], $requiredFields);
307
308
        // since this COULD BE called twice (to support the previous syntax where developers needed to call `setValidation` after the field definition)
309
        // and to make this change non-breaking, we are going to return an unique array. There is NO WARM returning repeated names, but there is also
310
        // no sense in doing it, so array_unique() it is.
311
        $requiredFields = array_unique($requiredFields);
312
313
        $this->setOperationSetting('requiredFields', $requiredFields);
314
    }
315
316
    /**
317
     * Check the current object to see if an input is required
318
     * for the given operation.
319
     *
320
     * @param  string  $inputKey  Field or input name.
321
     * @param  string  $operation  create / update
322
     * @return bool
323
     */
324
    public function isRequired($inputKey)
325
    {
326
        if (! $this->hasOperationSetting('requiredFields')) {
0 ignored issues
show
Bug introduced by
It seems like hasOperationSetting() 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

326
        if (! $this->/** @scrutinizer ignore-call */ hasOperationSetting('requiredFields')) {
Loading history...
327
            return false;
328
        }
329
330
        if (Str::contains($inputKey, '.')) {
331
            $inputKey = Str::dotsToSquareBrackets($inputKey, ['*']);
332
        }
333
334
        return in_array($inputKey, $this->getOperationSetting('requiredFields'));
335
    }
336
337
    /**
338
     * Add the validation setup by developer in field `validationRules` to the crud validation.
339
     *
340
     * @param  array  $field  - the field we want to get the validation from.
341
     * @param  bool|string  $parent  - the parent name when setting up validation for subfields.
342
     */
343
    private function setupFieldValidation($field, $parent = false)
344
    {
345
        [$rules, $messages] = $this->getValidationRulesAndMessagesFromField($field, $parent);
346
347
        if (! empty($rules)) {
348
            $this->setValidation($rules, $messages);
349
        }
350
    }
351
352
    /**
353
     * Return the array of rules and messages with the validation key accordingly set
354
     * to match the field or the subfield accordingly.
355
     *
356
     * @param  array  $field  - the field we want to get the rules and messages from.
357
     * @param  bool|string  $parent  - the parent name when setting up validation for subfields.
358
     */
359
    private function getValidationRulesAndMessagesFromField($field, $parent = false)
360
    {
361
        $rules = [];
362
        $messages = [];
363
364
        foreach ((array) $field['name'] as $fieldName) {
365
            if ($parent) {
366
                $fieldName = $parent.'.*.'.$fieldName;
0 ignored issues
show
Bug introduced by
Are you sure $parent of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

366
                $fieldName = /** @scrutinizer ignore-type */ $parent.'.*.'.$fieldName;
Loading history...
367
            }
368
369
            if (isset($field['validationRules'])) {
370
                $rules[$fieldName] = $field['validationRules'];
371
            }
372
            if (isset($field['validationMessages'])) {
373
                foreach ($field['validationMessages'] as $validator => $message) {
374
                    $fieldValidationName = $fieldName.'.'.$validator;
375
                    $messages[$fieldValidationName] = $message;
376
                }
377
            }
378
        }
379
380
        return [$rules, $messages];
381
    }
382
}
383