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.

NamedFormElement::save()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
crap 1
1
<?php
2
3
namespace SleepingOwl\Admin\Form\Element;
4
5
use Closure;
6
use Illuminate\Contracts\Support\Htmlable;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Database\Eloquent\Relations\BelongsTo;
9
use Illuminate\Database\Eloquent\Relations\HasOne;
10
use Illuminate\Database\Eloquent\Relations\MorphOne;
11
use Illuminate\Database\Eloquent\Relations\Relation;
12
use Illuminate\Support\Arr;
13
use Illuminate\Support\Str;
14
use KodiComponents\Support\HtmlAttributes;
15
use LogicException;
16
use SleepingOwl\Admin\Exceptions\Form\FormElementException;
17
use SleepingOwl\Admin\Form\FormElement;
18
19
abstract class NamedFormElement extends FormElement
20
{
21
    use HtmlAttributes;
22
23
    /**
24
     * @var string
25
     */
26
    protected $path;
27
28
    /**
29
     * @var string
30
     */
31
    protected $name;
32
33
    /**
34
     * @var string
35
     */
36
    protected $id;
37
38
    /**
39
     * @var string
40
     */
41
    protected $modelAttributeKey;
42
43
    /**
44
     * @var string
45
     */
46
    protected $label;
47
48
    /**
49
     * @var string
50
     */
51
    protected $helpText;
52
53
    /**
54
     * @var mixed
55
     */
56
    protected $exactValue;
57
58
    /**
59
     * @var bool
60
     */
61 27
    protected $exactValueSet = false;
62
63 27
    /**
64 1
     * @var mixed
65
     */
66
    protected $defaultValue;
67 26
68 26
    /**
69
     * @var \Closure
70 26
     */
71 26
    protected $mutator;
72 26
73
    /**
74 26
     * @param string $path
75 26
     * @param string|null $label
76
     *
77
     * @throws FormElementException
78
     */
79
    public function __construct($path, $label = null)
80
    {
81
        if (empty($path)) {
82
            throw new FormElementException('You must specify field path');
83
        }
84 26
85
        $this->setPath($path);
86 26
        $this->setLabel($label);
87
88 26
        $parts = explode('.', $path);
89 7
        $this->setName($this->composeName($parts));
90 7
        $this->setId($this->composeId($path));
91 7
        $this->setModelAttributeKey(end($parts));
92
93 26
        parent::__construct();
94
    }
95
96
    /**
97
     * Compose html name from array like this: 'first[second][third]'.
98
     *
99 13
     * @param array $parts
100
     *
101 13
     * @return string
102
     */
103
    private function composeName(array $parts)
104
    {
105
        $name = array_shift($parts);
106
107
        while (! empty($parts)) {
108
            $part = array_shift($parts);
109 26
            $name .= "[$part]";
110
        }
111 26
112
        return $name;
113 26
    }
114
115
    /**
116
     * Compose html id from array like this: 'first__second__third'.
117
     *
118
     * @param string $path
119 5
     *
120
     * @return string
121 5
     */
122
    private function composeId(string $path)
123
    {
124
        $name = strtr($path, ['.' => '__', '[' => '__', ']' => '']);
125
126
        return $name;
127
    }
128
129 26
    /**
130
     * @return string
131 26
     */
132
    public function getPath()
133 26
    {
134
        return $this->path;
135
    }
136
137
    /**
138
     * @param string $path
139 4
     *
140
     * @return $this
141 4
     */
142
    public function setPath($path)
143
    {
144
        $this->path = $path;
145
146
        return $this;
147
    }
148
149 26
    /**
150
     * @return string
151 26
     */
152
    public function getNameKey()
153 26
    {
154
        return strtr($this->getName(), ['[' => '.', ']' => '']);
155
    }
156
157
    /**
158
     * @return string
159 6
     */
160
    public function getName()
161 6
    {
162
        return $this->name;
163
    }
164
165
    /**
166
     * @param string $name
167
     *
168
     * @return $this
169 26
     */
170
    public function setName($name)
171 26
    {
172
        $this->name = $name;
173 26
174
        return $this;
175
    }
176
177
    /**
178
     * @return string
179 3
     */
180
    public function getId()
181 3
    {
182
        return $this->id;
183
    }
184
185
    /**
186
     * @param string $id
187
     *
188
     * @return $this
189 1
     */
190
    public function setId($id)
191 1
    {
192
        $this->id = $id;
193 1
194
        return $this;
195
    }
196
197
    /**
198
     * @param string $id
199 2
     *
200
     * @return $this
201 2
     */
202 1
    public function setComposeId($id)
203
    {
204
        $this->setId($this->composeId($id));
205 2
206
        return $this;
207
    }
208
209
    /**
210
     * @return string
211
     */
212
    public function getLabel()
213 1
    {
214
        return $this->label;
215 1
    }
216
217 1
    /**
218
     * @param string $label
219
     *
220
     * @return $this
221
     */
222
    public function setLabel($label)
223
    {
224
        $this->label = $label;
225 2
226
        return $this;
227 2
    }
228
229 2
    /**
230
     * @return string
231
     */
232
    public function getModelAttributeKey()
233
    {
234
        return $this->modelAttributeKey;
235
    }
236
237 2
    /**
238
     * @param string $key
239 2
     *
240
     * @return $this
241 2
     */
242 1
    public function setModelAttributeKey($key)
243 1
    {
244
        $this->modelAttributeKey = $key;
245 2
246
        return $this;
247
    }
248
249
    /**
250
     * @return mixed
251 3
     */
252
    public function getDefaultValue()
253 3
    {
254
        return $this->defaultValue;
255 3
    }
256 3
257 3
    /**
258 3
     * @param mixed $defaultValue
259
     *
260 3
     * @return $this
261
     */
262
    public function setDefaultValue($defaultValue)
263
    {
264
        $this->defaultValue = $defaultValue;
265
266 1
        return $this;
267
    }
268 1
269
    /**
270
     * @return string
271
     */
272
    public function getHelpText()
273
    {
274
        if ($this->helpText instanceof Htmlable) {
0 ignored issues
show
introduced by
$this->helpText is never a sub-type of Illuminate\Contracts\Support\Htmlable.
Loading history...
275
            return $this->helpText->toHtml();
276
        }
277 2
278
        return $this->helpText;
279 2
    }
280
281 2
    /**
282 2
     * @param string|Htmlable $helpText
283 1
     *
284
     * @return $this
285
     */
286 1
    public function setHelpText($helpText)
287 1
    {
288
        $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...
289 1
290 1
        return $this;
291
    }
292
293 2
    /**
294 2
     * @param string|null $message
295
     *
296 2
     * @return $this
297
     */
298
    public function required($message = null)
299
    {
300
        $this->addValidationRule('required', $message);
301
302
        return $this;
303
    }
304 2
305
    /**
306 2
     * @param string|null $message
307 2
     *
308 2
     * @return $this
309
     */
310 2
    public function unique($message = null)
311 2
    {
312
        $this->addValidationRule('_unique');
313
314 1
        if (! is_null($message)) {
315 1
            $this->addValidationMessage('unique', $message);
316 1
        }
317
318
        return $this;
319 1
    }
320 1
321
    /**
322 1
     * @return array
323 1
     */
324
    public function getValidationMessages()
325
    {
326 1
        $messages = parent::getValidationMessages();
327 1
328
        foreach ($messages as $rule => $message) {
329 1
            $messages[$this->getName().'.'.$rule] = $message;
330 1
            unset($messages[$rule]);
331 1
        }
332 1
333
        return $messages;
334
    }
335
336
    /**
337
     * @return array
338
     */
339
    public function getValidationLabels()
340
    {
341
        return [$this->getPath() => $this->getLabel()];
342
    }
343
344
    /**
345
     * If FormElement has `_unique` rule, it will get all appropriate
346
     * validation rules based on underlying model.
347 6
     *
348
     * @return array
349 6
     */
350 1
    public function getValidationRules()
351
    {
352
        $rules = parent::getValidationRules();
353 5
354
        foreach ($rules as &$rule) {
355
            if ($rule !== '_unique') {
356
                continue;
357
            }
358
359 3
            $model = $this->resolvePath();
360
            $table = $model->getTable();
361 3
362 1
            $rule = 'unique:'.$table.','.$this->getModelAttributeKey();
363
            if ($model->exists) {
364
                $rule .= ','.$model->getKey();
365 2
            }
366 2
        }
367 2
        unset($rule);
368
369 2
        return [$this->getPath() => $rules];
370 2
    }
371
372
    /**
373
     * Get model related to form element.
374
     *
375
     * @return mixed
376 1
     */
377
    public function resolvePath()
378
    {
379
        $model = $this->getModel();
380
        $relations = explode('.', $this->getPath());
381
        $count = count($relations);
382
383
        if ($count === 1) {
384
            return $model;
385
        }
386
387
        foreach ($relations as $relation) {
388
            if ($count === 1) {
389
                return $model;
390
            }
391
392
            if ($model->exists && ($value = $model->getAttribute($relation)) instanceof Model) {
393 1
                $model = $value;
394 1
395
                $count--;
396 1
                continue;
397 1
            }
398
399 1
            if (method_exists($model, $relation)) {
400 1
                $relation = $model->{$relation}();
401
402
                if ($relation instanceof Relation) {
403
                    $model = $relation->getModel();
404
                    $count--;
405
                    continue;
406
                }
407
            }
408
409
            break;
410
        }
411
412
        throw new LogicException("Can not resolve path for field '{$this->getPath()}'. Probably relation definition is incorrect");
413
    }
414
415
    /**
416
     * @param \Illuminate\Http\Request $request
417
     *
418
     * @return array|string
419
     */
420
    public function getValueFromRequest(\Illuminate\Http\Request $request)
421
    {
422
        if ($request->hasSession() && ! is_null($value = $request->old($this->getPath()))) {
423
            return $value;
424
        }
425
426
        return $request->input($this->getPath());
427
    }
428
429
    /**
430
     * @return mixed
431 1
     */
432
    public function getValueFromModel()
433 1
    {
434 1
        if (($value = $this->getValueFromRequest(request())) !== null) {
0 ignored issues
show
introduced by
The condition $value = $this->getValue...est(request()) !== null is always true.
Loading history...
435
            return $value;
436 1
        }
437 1
438 1
        $model = $this->getModel();
439
        $path = $this->getPath();
440
        $value = $this->getDefaultValue();
441
442
        if ($model === null || ! $model->exists) {
443
            // First check for model existence must go here, before all checks are made
444
            return $value;
445 3
        }
446
447 3
        /*
448 3
         * Implement json parsing
449 3
         */
450
        if (strpos($path, '->') !== false) {
451 3
            $casts = collect($model->getCasts());
452 1
            $jsonParts = collect(explode('->', $path));
453
454
            $jsonAttr = $model->{$jsonParts->first()};
455 3
456 3
            $cast = $casts->get($jsonParts->first(), false);
457 3
458 3
            if ($cast === 'object') {
459 3
                $jsonAttr = json_decode(json_encode($jsonAttr), true);
460
            } elseif ($cast !== 'array') {
461
                $jsonAttr = json_decode($jsonAttr);
462
            }
463
464
            return Arr::get($jsonAttr, $jsonParts->slice(1)->implode('.'));
465
        }
466 5
467
        $relations = explode('.', $path);
468 5
        $count = count($relations);
469
470 5
        if ($count === 1) {
471 5
            $attribute = $model->getAttribute($this->getModelAttributeKey());
472 5
            if (! empty($attribute) || $attribute === 0 || is_null($value)) {
473
                return $attribute;
474 5
            }
475 2
        }
476 2
477
        foreach ($relations as $relation) {
478
            if ($model->{$relation} instanceof Model) {
479 2
                $model = $model->{$relation};
480 2
                continue;
481 2
            }
482 2
            if ($count === 2) {
483 2
                if (Str::contains($relation, '->')) {
484
                    $parts = explode('->', $relation);
485
                    $relationField = array_shift($array);
486
                    $jsonPath = implode('.', $parts);
487
                    $attribute = data_get($model->{$relationField}, $jsonPath);
488
                } else {
489
                    $attribute = $model->getAttribute($relation);
490
                }
491
                if (! empty($attribute) || is_null($value)) {
492
                    return $attribute;
493
                }
494
            }
495
496
            if ($this->getDefaultValue() === null) {
497
                throw new LogicException("Can not fetch value for field '{$path}'. Probably relation definition is incorrect");
498
            }
499
        }
500
501 2
        return $value;
502 2
    }
503 1
504 1
    /**
505 1
     * @param \Illuminate\Http\Request $request
506 1
     *
507
     * @return void
508 2
     */
509
    public function save(\Illuminate\Http\Request $request)
510 1
    {
511 1
        $this->setModelAttribute($this->getValueFromRequest($request));
512
    }
513 4
514
    /**
515
     * @param mixed $value
516
     *
517
     * @return void
518
     */
519
    public function setModelAttribute($value)
520
    {
521
        $model = $this->getModelByPath($this->getPath());
522
523
        if ($this->isValueSkipped()) {
524
            return;
525
        }
526
527
        $model->setAttribute($this->getModelAttributeKey(), $this->prepareValue($value));
528
    }
529
530
    /**
531
     * @param string $path
532 2
     *
533
     * @return Model|null
534 2
     */
535
    protected function getModelByPath($path)
536 2
    {
537
        $model = $this->getModel();
538
539
        $relations = explode('.', $path);
540
        $count = count($relations);
541
        $i = 1;
542 5
543
        if ($count > 1) {
544 5
            $i++;
545
            $previousModel = $model;
546
547
            /* @var Model $model */
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
548
            foreach ($relations as $relation) {
549
                $relatedModel = null;
550
                if ($previousModel->getAttribute($relation) instanceof Model) {
551
                    $relatedModel = $previousModel->getAttribute($relation);
552 4
                } elseif (method_exists($previousModel, $relation)) {
553
554 4
                    /* @var Relation $relation */
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
555 1
                    $relationObject = $previousModel->{$relation}();
556 1
557
                    switch (get_class($relationObject)) {
558 4
                        case BelongsTo::class:
559
                            $relationObject->associate($relatedModel = $relationObject->getRelated());
560
                            break;
561
                        case HasOne::class:
562
                        case MorphOne::class:
563
                            $relatedModel = $relationObject->getRelated()->newInstance();
564 1
                            $relatedModel->setAttribute($this->getForeignKeyNameFromRelation($relationObject), $relationObject->getParentKey());
565
                            $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

565
                            $model->setRelation(/** @scrutinizer ignore-type */ $relation, $relatedModel);
Loading history...
566 1
                            break;
567 1
                    }
568 1
                }
569 1
570
                $previousModel = $relatedModel;
571 1
                if ($i === $count) {
572 1
                    break;
573 1
                } elseif (is_null($relatedModel)) {
574 1
                    throw new LogicException("Field [{$path}] can't be mapped to relations of model ".get_class($model).'. Probably some dot delimeted segment is not a supported relation type');
575 1
                }
576 1
            }
577 1
578 1
            $model = $previousModel;
579 1
        }
580 1
581
        return $model;
582
    }
583
584
    protected function getForeignKeyNameFromRelation($relation)
585
    {
586
        return method_exists($relation, 'getForeignKeyName') ? $relation->getForeignKeyName()
587
            : $relation->getPlainForeignKey();
588
    }
589
590
    /**
591
     * Field->mutateValue(function($value) {
592
     *     return bcrypt($value);
593
     * }).
594
     *
595
     * @param \Closure $mutator
596
     *
597
     * @return $this
598
     */
599
    public function mutateValue(Closure $mutator)
600
    {
601
        $this->mutator = $mutator;
602
603
        return $this;
604
    }
605
606
    /**
607
     * @return bool
608
     */
609
    public function hasMutator()
610
    {
611
        return is_callable($this->mutator);
612
    }
613
614
    /**
615
     * @param mixed $value
616
     *
617
     * @return mixed
618
     */
619
    public function prepareValue($value)
620
    {
621
        if ($this->hasMutator()) {
622
            $value = call_user_func($this->mutator, $value);
623
        }
624
625
        return $value;
626
    }
627
628
    /**
629
     * @return mixed
630
     */
631
    public function getExactValue()
632
    {
633
        return $this->exactValue;
634
    }
635
636
    /**
637
     * @param mixed $exactValue
638
     *
639
     * @return $this
640
     */
641
    public function setExactValue($exactValue)
642
    {
643
        $this->exactValue = $exactValue;
644
        $this->exactValueSet = true;
645
646
        return $this;
647
    }
648
649
    /**
650
     * @return array
651
     */
652
    public function toArray()
653
    {
654
        $this->setHtmlAttributes([
655
            'id' => $this->getId(),
656
            'name' => $this->getName(),
657
        ]);
658
659
        return array_merge(parent::toArray(), [
660
            'id' => $this->getId(),
661
            'value' => $this->exactValueSet ? $this->getExactValue() : $this->getValueFromModel(),
662
            'name' => $this->getName(),
663
            'path' => $this->getPath(),
664
            'label' => $this->getLabel(),
665
            'attributes' => $this->htmlAttributesToString(),
666
            'helpText' => $this->getHelpText(),
667
            'required' => in_array('required', $this->validationRules),
668
            'class' => $this->getHtmlAttribute('class'),
669
            'style' => $this->getHtmlAttribute('style'),
670
        ]);
671
    }
672
}
673