GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — new ( a1632c...459edf )
by Dave
18:49 queued 07:25
created

NamedFormElement::getValueFromModel()   D

Complexity

Conditions 18
Paths 53

Size

Total Lines 70
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
eloc 40
nc 53
nop 0
dl 0
loc 70
rs 4.8666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SleepingOwl\Admin\Form\Element;
4
5
use LogicException;
6
use Illuminate\Support\Arr;
7
use Illuminate\Database\Eloquent\Model;
8
use SleepingOwl\Admin\Form\FormElement;
9
use Illuminate\Contracts\Support\Htmlable;
10
use KodiComponents\Support\HtmlAttributes;
11
use Illuminate\Database\Eloquent\Relations\HasOne;
12
use Illuminate\Database\Eloquent\Relations\MorphOne;
13
use Illuminate\Database\Eloquent\Relations\Relation;
14
use Illuminate\Database\Eloquent\Relations\BelongsTo;
15
use SleepingOwl\Admin\Exceptions\Form\FormElementException;
16
17
abstract class NamedFormElement extends FormElement
18
{
19
    use HtmlAttributes;
20
    /**
21
     * @var string
22
     */
23
    protected $path;
24
25
    /**
26
     * @var string
27
     */
28
    protected $name;
29
30
    /**
31
     * @var string
32
     */
33
    protected $modelAttributeKey;
34
35
    /**
36
     * @var string
37
     */
38
    protected $label;
39
40
    /**
41
     * @var string
42
     */
43
    protected $helpText;
44
45
    /**
46
     * @var mixed
47
     */
48
    protected $defaultValue;
49
50
    /**
51
     * @var \Closure
52
     */
53
    protected $mutator;
54
55
    /**
56
     * @param string $path
57
     * @param string|null $label
58
     *
59
     * @throws FormElementException
60
     */
61
    public function __construct($path, $label = null)
62
    {
63
        if (empty($path)) {
64
            throw new FormElementException('You must specify field path');
65
        }
66
67
        $this->setPath($path);
68
        $this->setLabel($label);
69
70
        $parts = explode('.', $path);
71
        $this->setName($this->composeName($parts));
72
        $this->setModelAttributeKey(end($parts));
73
74
        parent::__construct();
75
    }
76
77
    /**
78
     * Compose html name from array like this: 'first[second][third]'.
79
     *
80
     * @param array $parts
81
     *
82
     * @return string
83
     */
84
    private function composeName(array $parts)
85
    {
86
        $name = array_shift($parts);
87
88
        while (! empty($parts)) {
89
            $part = array_shift($parts);
90
            $name .= "[$part]";
91
        }
92
93
        return $name;
94
    }
95
96
    /**
97
     * @return string
98
     */
99
    public function getPath()
100
    {
101
        return $this->path;
102
    }
103
104
    /**
105
     * @param string $path
106
     *
107
     * @return $this
108
     */
109
    public function setPath($path)
110
    {
111
        $this->path = $path;
112
113
        return $this;
114
    }
115
116
    /**
117
     * @return string
118
     */
119
    public function getName()
120
    {
121
        return $this->name;
122
    }
123
124
    /**
125
     * @param string $name
126
     *
127
     * @return $this
128
     */
129
    public function setName($name)
130
    {
131
        $this->name = $name;
132
133
        return $this;
134
    }
135
136
    /**
137
     * @return string
138
     */
139
    public function getLabel()
140
    {
141
        return $this->label;
142
    }
143
144
    /**
145
     * @param string $label
146
     *
147
     * @return $this
148
     */
149
    public function setLabel($label)
150
    {
151
        $this->label = $label;
152
153
        return $this;
154
    }
155
156
    /**
157
     * @return string
158
     */
159
    public function getModelAttributeKey()
160
    {
161
        return $this->modelAttributeKey;
162
    }
163
164
    /**
165
     * @param string $key
166
     *
167
     * @return $this
168
     */
169
    public function setModelAttributeKey($key)
170
    {
171
        $this->modelAttributeKey = $key;
172
173
        return $this;
174
    }
175
176
    /**
177
     * @return mixed
178
     */
179
    public function getDefaultValue()
180
    {
181
        return $this->defaultValue;
182
    }
183
184
    /**
185
     * @param mixed $defaultValue
186
     *
187
     * @return $this
188
     */
189
    public function setDefaultValue($defaultValue)
190
    {
191
        $this->defaultValue = $defaultValue;
192
193
        return $this;
194
    }
195
196
    /**
197
     * @return string
198
     */
199
    public function getHelpText()
200
    {
201
        if ($this->helpText instanceof Htmlable) {
0 ignored issues
show
introduced by
$this->helpText is never a sub-type of Illuminate\Contracts\Support\Htmlable. If $this->helpText can have other possible types, add them to src/Form/Element/NamedFormElement.php:41.
Loading history...
202
            return $this->helpText->toHtml();
203
        }
204
205
        return $this->helpText;
206
    }
207
208
    /**
209
     * @param string|Htmlable $helpText
210
     *
211
     * @return $this
212
     */
213
    public function setHelpText($helpText)
214
    {
215
        $this->helpText = $helpText;
0 ignored issues
show
Documentation Bug introduced by
It seems like $helpText can also be of type Illuminate\Contracts\Support\Htmlable. However, the property $helpText is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
216
217
        return $this;
218
    }
219
220
    /**
221
     * @param string|null $message
222
     *
223
     * @return $this
224
     */
225
    public function required($message = null)
226
    {
227
        $this->addValidationRule('required', $message);
228
229
        return $this;
230
    }
231
232
    /**
233
     * @param string|null $message
234
     *
235
     * @return $this
236
     */
237
    public function unique($message = null)
238
    {
239
        $this->addValidationRule('_unique');
240
241
        if (! is_null($message)) {
242
            $this->addValidationMessage('unique', $message);
243
        }
244
245
        return $this;
246
    }
247
248
    /**
249
     * @return array
250
     */
251
    public function getValidationMessages()
252
    {
253
        $messages = parent::getValidationMessages();
254
255
        foreach ($messages as $rule => $message) {
256
            $messages[$this->getName().'.'.$rule] = $message;
257
            unset($messages[$rule]);
258
        }
259
260
        return $messages;
261
    }
262
263
    /**
264
     * @return array
265
     */
266
    public function getValidationLabels()
267
    {
268
        return [$this->getPath() => $this->getLabel()];
269
    }
270
271
    /**
272
     * If FormElement has `_unique` rule, it will get all appropriate
273
     * validation rules based on underlying model.
274
     *
275
     * @return array
276
     */
277
    public function getValidationRules()
278
    {
279
        $rules = parent::getValidationRules();
280
281
        foreach ($rules as &$rule) {
282
            if ($rule !== '_unique') {
283
                continue;
284
            }
285
286
            $model = $this->resolvePath();
287
            $table = $model->getTable();
288
289
            $rule = 'unique:'.$table.','.$this->getModelAttributeKey();
290
            if ($model->exists) {
291
                $rule .= ','.$model->getKey();
292
            }
293
        }
294
        unset($rule);
295
296
        return [$this->getPath() => $rules];
297
    }
298
299
    /**
300
     * Get model related to form element.
301
     *
302
     * @return mixed
303
     */
304
    public function resolvePath()
305
    {
306
        $model = $this->getModel();
307
        $relations = explode('.', $this->getPath());
308
        $count = count($relations);
309
310
        if ($count === 1) {
311
            return $model;
312
        }
313
314
        foreach ($relations as $relation) {
315
            if ($count === 1) {
316
                return $model;
317
            }
318
319
            if ($model->exists && ($value = $model->getAttribute($relation)) instanceof Model) {
320
                $model = $value;
321
322
                $count--;
323
                continue;
324
            }
325
326
            if (method_exists($model, $relation)) {
327
                $relation = $model->{$relation}();
328
329
                if ($relation instanceof Relation) {
330
                    $model = $relation->getModel();
331
                    $count--;
332
                    continue;
333
                }
334
            }
335
336
            break;
337
        }
338
339
        throw new LogicException("Can not resolve path for field '{$this->getPath()}'. Probably relation definition is incorrect");
340
    }
341
342
    /**
343
     * @param \Illuminate\Http\Request $request
344
     *
345
     * @return array|string
346
     */
347
    public function getValueFromRequest(\Illuminate\Http\Request $request)
348
    {
349
        if ($request->hasSession() && ! is_null($value = $request->old($this->getPath()))) {
350
            return $value;
351
        }
352
353
        return $request->input($this->getPath());
354
    }
355
356
    /**
357
     * @return mixed
358
     */
359
    public function getValueFromModel()
360
    {
361
        if (! is_null($value = $this->getValueFromRequest(request()))) {
0 ignored issues
show
introduced by
The condition is_null($value = $this->...FromRequest(request())) is always false.
Loading history...
362
            return $value;
363
        }
364
365
        $model = $this->getModel();
366
        $path = $this->getPath();
367
        $value = $this->getDefaultValue();
368
369
        if (is_null($model) || ! $model->exists) {
370
            return $value;
371
        }
372
373
        $relations = explode('.', $path);
374
        $count = count($relations);
375
376
        if ($count === 1) {
377
            $attribute = $model->getAttribute($this->getModelAttributeKey());
378
379
            if (! empty($attribute) || $attribute === 0 || is_null($value)) {
380
                return $attribute;
381
            }
382
        }
383
384
        foreach ($relations as $relation) {
385
            if ($model->{$relation} instanceof Model) {
386
                $model = $model->{$relation};
387
                continue;
388
            }
389
            if ($count === 2) {
390
                if (str_contains($relation, '->')) {
391
                    $parts = explode('->', $relation);
392
                    $relationField = array_shift($array);
393
                    $jsonPath = implode('.', $parts);
394
                    $attribute = data_get($model->{$relationField}, $jsonPath);
395
                } else {
396
                    $attribute = $model->getAttribute($relation);
397
                }
398
                if (! empty($attribute) || is_null($value)) {
399
                    return $attribute;
400
                }
401
            }
402
403
            if (is_null($this->getDefaultValue())) {
404
                throw new LogicException("Can not fetch value for field '{$path}'. Probably relation definition is incorrect");
405
            }
406
        }
407
408
        /*
409
         * Implement json parsing
410
         */
411
        if (strpos($path, '->') !== false) {
412
            $casts = collect($model->getCasts());
413
            $jsonParts = collect(explode('->', $path));
414
415
            $jsonAttr = $model->{$jsonParts->first()};
416
417
            $cast = $casts->get($jsonParts->first(), false);
418
419
            if ($cast == 'object') {
420
                $jsonAttr = json_decode(json_encode($jsonAttr), true);
421
            } elseif ($cast != 'array') {
422
                $jsonAttr = json_decode($jsonAttr);
423
            }
424
425
            return Arr::get($jsonAttr, $jsonParts->slice(1)->implode('.'));
426
        }
427
428
        return $value;
429
    }
430
431
    /**
432
     * @param \Illuminate\Http\Request $request
433
     *
434
     * @return void
435
     */
436
    public function save(\Illuminate\Http\Request $request)
437
    {
438
        $this->setModelAttribute(
439
            $this->getValueFromRequest(
440
                $request
441
            )
442
        );
443
    }
444
445
    /**
446
     * @param mixed  $value
447
     *
448
     * @return void
449
     */
450
    public function setModelAttribute($value)
451
    {
452
        $model = $this->getModelByPath(
453
            $this->getPath()
454
        );
455
456
        if ($this->isValueSkipped()) {
457
            return;
458
        }
459
460
        $model->setAttribute(
461
            $this->getModelAttributeKey(),
462
            $this->prepareValue($value)
463
        );
464
    }
465
466
    /**
467
     * @param string $path
468
     *
469
     * @return Model|null
470
     */
471
    protected function getModelByPath($path)
472
    {
473
        $model = $this->getModel();
474
475
        $relations = explode('.', $path);
476
        $count = count($relations);
477
        $i = 1;
478
479
        if ($count > 1) {
480
            $i++;
481
            $previousModel = $model;
482
483
            /* @var Model $model */
484
            foreach ($relations as $relation) {
485
                $relatedModel = null;
486
                if ($previousModel->getAttribute($relation) instanceof Model) {
487
                    $relatedModel = $previousModel->getAttribute($relation);
488
                } elseif (method_exists($previousModel, $relation)) {
489
490
                    /* @var Relation $relation */
491
                    $relationObject = $previousModel->{$relation}();
492
493
                    switch (get_class($relationObject)) {
494
                        case BelongsTo::class:
495
                            $relationObject->associate($relatedModel = $relationObject->getRelated());
496
                            break;
497
                        case HasOne::class:
498
                        case MorphOne::class:
499
                            $relatedModel = $relationObject->getRelated()->newInstance();
500
                            $relatedModel->setAttribute($this->getForeignKeyNameFromRelation($relationObject), $relationObject->getParentKey());
501
                            $model->setRelation($relation, $relatedModel);
0 ignored issues
show
Bug introduced by
$relation of type Illuminate\Database\Eloquent\Relations\Relation is incompatible with the type string expected by parameter $relation of Illuminate\Database\Eloquent\Model::setRelation(). ( Ignorable by Annotation )

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

501
                            $model->setRelation(/** @scrutinizer ignore-type */ $relation, $relatedModel);
Loading history...
502
                            break;
503
                    }
504
                }
505
506
                $previousModel = $relatedModel;
507
                if ($i === $count) {
508
                    break;
509
                } elseif (is_null($relatedModel)) {
510
                    throw new LogicException("Field [{$path}] can't be mapped to relations of model ".get_class($model)
511
                        .'. Probably some dot delimeted segment is not a supported relation type');
512
                }
513
            }
514
515
            $model = $previousModel;
516
        }
517
518
        return $model;
519
    }
520
521
    protected function getForeignKeyNameFromRelation($relation)
522
    {
523
        return method_exists($relation, 'getForeignKeyName')
524
            ? $relation->getForeignKeyName()
525
            : $relation->getPlainForeignKey();
526
    }
527
528
    /**
529
     * Field->mutateValue(function($value) {
530
     *     return bcrypt($value);
531
     * }).
532
     *
533
     * @param \Closure $mutator
534
     *
535
     * @return $this
536
     */
537
    public function mutateValue(\Closure $mutator)
538
    {
539
        $this->mutator = $mutator;
540
541
        return $this;
542
    }
543
544
    /**
545
     * @return bool
546
     */
547
    public function hasMutator()
548
    {
549
        return is_callable($this->mutator);
550
    }
551
552
    /**
553
     * @param mixed $value
554
     *
555
     * @return mixed
556
     */
557
    public function prepareValue($value)
558
    {
559
        if ($this->hasMutator()) {
560
            $value = call_user_func($this->mutator, $value);
561
        }
562
563
        return $value;
564
    }
565
566
    /**
567
     * @return array
568
     */
569
    public function toArray()
570
    {
571
        $this->setHtmlAttributes([
572
            'id' => $this->getName(),
573
            'name' => $this->getName(),
574
        ]);
575
576
        return array_merge(parent::toArray(), [
577
            'id' => $this->getName(),
578
            'value' => $this->getValueFromModel(),
579
            'name' => $this->getName(),
580
            'path' => $this->getPath(),
581
            'label' => $this->getLabel(),
582
            'attributes'=> $this->htmlAttributesToString(),
583
            'helpText' => $this->getHelpText(),
584
            'required' => in_array('required', $this->validationRules),
585
        ]);
586
    }
587
}
588