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.
Passed
Push — analysis-q2kEyg ( 372ea2 )
by butschster
09:24 queued 26s
created

NamedFormElement::mutateValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SleepingOwl\Admin\Form\Element;
4
5
use Closure;
6
use LogicException;
7
use Illuminate\Support\Arr;
8
use Illuminate\Support\Str;
9
use Illuminate\Database\Eloquent\Model;
10
use SleepingOwl\Admin\Form\FormElement;
11
use Illuminate\Contracts\Support\Htmlable;
12
use KodiComponents\Support\HtmlAttributes;
13
use Illuminate\Database\Eloquent\Relations\HasOne;
14
use Illuminate\Database\Eloquent\Relations\MorphOne;
15
use Illuminate\Database\Eloquent\Relations\Relation;
16
use Illuminate\Database\Eloquent\Relations\BelongsTo;
17
use SleepingOwl\Admin\Exceptions\Form\FormElementException;
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
    protected $exactValueSet = false;
62
63
    /**
64
     * @var mixed
65
     */
66
    protected $defaultValue;
67
68
    /**
69
     * @var \Closure
70
     */
71
    protected $mutator;
72
73
    /**
74
     * @param string $path
75
     * @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
85
        $this->setPath($path);
86
        $this->setLabel($label);
87
88
        $parts = explode('.', $path);
89
        $this->setName($this->composeName($parts));
90
        $this->setModelAttributeKey(end($parts));
91
92
        parent::__construct();
93
    }
94
95
    /**
96
     * Compose html name from array like this: 'first[second][third]'.
97
     *
98
     * @param array $parts
99
     *
100
     * @return string
101
     */
102
    private function composeName(array $parts)
103
    {
104
        $name = array_shift($parts);
105
106
        while (! empty($parts)) {
107
            $part = array_shift($parts);
108
            $name .= "[$part]";
109
        }
110
111
        return $name;
112
    }
113
114
    /**
115
     * @return string
116
     */
117
    public function getPath()
118
    {
119
        return $this->path;
120
    }
121
122
    /**
123
     * @param string $path
124
     *
125
     * @return $this
126
     */
127
    public function setPath($path)
128
    {
129
        $this->path = $path;
130
131
        return $this;
132
    }
133
134
    /**
135
     * @return string
136
     */
137
    public function getNameKey()
138
    {
139
        return strtr($this->getName(), ['[' => '.', ']' => '']);
140
    }
141
142
    /**
143
     * @return string
144
     */
145
    public function getName()
146
    {
147
        return $this->name;
148
    }
149
150
    /**
151
     * @param string $name
152
     *
153
     * @return $this
154
     */
155
    public function setName($name)
156
    {
157
        $this->name = $name;
158
159
        return $this;
160
    }
161
162
    /**
163
     * @return string
164
     */
165
    public function getId()
166
    {
167
        return $this->id;
168
    }
169
170
    /**
171
     * @return string
172
     */
173
    public function getLabel()
174
    {
175
        return $this->label;
176
    }
177
178
    /**
179
     * @param string $label
180
     *
181
     * @return $this
182
     */
183
    public function setLabel($label)
184
    {
185
        $this->label = $label;
186
187
        return $this;
188
    }
189
190
    /**
191
     * @return string
192
     */
193
    public function getModelAttributeKey()
194
    {
195
        return $this->modelAttributeKey;
196
    }
197
198
    /**
199
     * @param string $key
200
     *
201
     * @return $this
202
     */
203
    public function setModelAttributeKey($key)
204
    {
205
        $this->modelAttributeKey = $key;
206
207
        return $this;
208
    }
209
210
    /**
211
     * @return mixed
212
     */
213
    public function getDefaultValue()
214
    {
215
        return $this->defaultValue;
216
    }
217
218
    /**
219
     * @param mixed $defaultValue
220
     *
221
     * @return $this
222
     */
223
    public function setDefaultValue($defaultValue)
224
    {
225
        $this->defaultValue = $defaultValue;
226
227
        return $this;
228
    }
229
230
    /**
231
     * @return string|Htmlable $helpText
232
     */
233
    public function getHelpText()
234
    {
235
        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...
236
            return $this->helpText->toHtml();
237
        }
238
239
        return $this->helpText;
240
    }
241
242
    /**
243
     * @param string|Htmlable $helpText
244
     *
245
     * @return $this
246
     */
247
    public function setHelpText($helpText)
248
    {
249
        $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...
250
251
        return $this;
252
    }
253
254
    /**
255
     * @param string|null $message
256
     *
257
     * @return $this
258
     */
259
    public function required($message = null)
260
    {
261
        $this->addValidationRule('required', $message);
262
263
        return $this;
264
    }
265
266
    /**
267
     * @param string|null $message
268
     *
269
     * @return $this
270
     */
271
    public function unique($message = null)
272
    {
273
        $this->addValidationRule('_unique');
274
275
        if (! is_null($message)) {
276
            $this->addValidationMessage('unique', $message);
277
        }
278
279
        return $this;
280
    }
281
282
    /**
283
     * @return array
284
     */
285
    public function getValidationMessages()
286
    {
287
        $messages = parent::getValidationMessages();
288
289
        foreach ($messages as $rule => $message) {
290
            $messages[$this->getName().'.'.$rule] = $message;
291
            unset($messages[$rule]);
292
        }
293
294
        return $messages;
295
    }
296
297
    /**
298
     * @return array
299
     */
300
    public function getValidationLabels()
301
    {
302
        return [$this->getPath() => $this->getLabel()];
303
    }
304
305
    /**
306
     * If FormElement has `_unique` rule, it will get all appropriate
307
     * validation rules based on underlying model.
308
     *
309
     * @return array
310
     */
311
    public function getValidationRules()
312
    {
313
        $rules = parent::getValidationRules();
314
315
        foreach ($rules as &$rule) {
316
            if ($rule !== '_unique') {
317
                continue;
318
            }
319
320
            $model = $this->resolvePath();
321
            $table = $model->getTable();
322
323
            $rule = 'unique:'.$table.','.$this->getModelAttributeKey();
324
            if ($model->exists) {
325
                $rule .= ','.$model->getKey();
326
            }
327
        }
328
        unset($rule);
329
330
        return [$this->getPath() => $rules];
331
    }
332
333
    /**
334
     * Get model related to form element.
335
     *
336
     * @return mixed
337
     */
338
    public function resolvePath()
339
    {
340
        $model = $this->getModel();
341
        $relations = explode('.', $this->getPath());
342
        $count = count($relations);
343
344
        if ($count === 1) {
345
            return $model;
346
        }
347
348
        foreach ($relations as $relation) {
349
            if ($count === 1) {
350
                return $model;
351
            }
352
353
            if ($model->exists && ($value = $model->getAttribute($relation)) instanceof Model) {
354
                $model = $value;
355
356
                $count--;
357
                continue;
358
            }
359
360
            if (method_exists($model, $relation)) {
361
                $relation = $model->{$relation}();
362
363
                if ($relation instanceof Relation) {
364
                    $model = $relation->getModel();
365
                    $count--;
366
                    continue;
367
                }
368
            }
369
370
            break;
371
        }
372
373
        throw new LogicException("Can not resolve path for field '{$this->getPath()}'. Probably relation definition is incorrect");
374
    }
375
376
    /**
377
     * @param \Illuminate\Http\Request $request
378
     *
379
     * @return array|string
380
     */
381
    public function getValueFromRequest(\Illuminate\Http\Request $request)
382
    {
383
        if ($request->hasSession() && ! is_null($value = $request->old($this->getPath()))) {
384
            return $value;
385
        }
386
387
        return $request->input($this->getPath());
388
    }
389
390
    /**
391
     * @return mixed
392
     */
393
    public function getValueFromModel()
394
    {
395
        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...
396
            return $value;
397
        }
398
399
        $model = $this->getModel();
400
        $path = $this->getPath();
401
        $value = $this->getDefaultValue();
402
403
        if ($model === null || ! $model->exists) {
404
            /*
405
              * First check for model existence must go here, before all checks are made
406
              */
407
            return $value;
408
        }
409
410
        /*
411
          * Implement json parsing
412
          */
413
        if (strpos($path, '->') !== false) {
414
            $casts = collect($model->getCasts());
415
            $jsonParts = collect(explode('->', $path));
416
417
            $jsonAttr = $model->{$jsonParts->first()};
418
419
            $cast = $casts->get($jsonParts->first(), false);
420
421
            if ($cast === 'object') {
422
                $jsonAttr = json_decode(json_encode($jsonAttr), true);
423
            } elseif ($cast !== 'array') {
424
                $jsonAttr = json_decode($jsonAttr);
425
            }
426
427
            return Arr::get($jsonAttr, $jsonParts->slice(1)->implode('.'));
428
        }
429
430
        $relations = explode('.', $path);
431
        $count = count($relations);
432
433
        if ($count === 1) {
434
            $attribute = $model->getAttribute($this->getModelAttributeKey());
435
            if (! empty($attribute) || $attribute === 0 || is_null($value)) {
436
                return $attribute;
437
            }
438
        }
439
440
        foreach ($relations as $relation) {
441
            if ($model->{$relation} instanceof Model) {
442
                $model = $model->{$relation};
443
                continue;
444
            }
445
            if ($count === 2) {
446
                if (Str::contains($relation, '->')) {
447
                    $parts = explode('->', $relation);
448
                    $relationField = array_shift($array);
449
                    $jsonPath = implode('.', $parts);
450
                    $attribute = data_get($model->{$relationField}, $jsonPath);
451
                } else {
452
                    $attribute = $model->getAttribute($relation);
453
                }
454
                if (! empty($attribute) || is_null($value)) {
455
                    return $attribute;
456
                }
457
            }
458
459
            if ($this->getDefaultValue() === null) {
460
                throw new LogicException("Can not fetch value for field '{$path}'. Probably relation definition is incorrect");
461
            }
462
        }
463
464
        return $value;
465
    }
466
467
    /**
468
     * @param \Illuminate\Http\Request $request
469
     *
470
     * @return void
471
     */
472
    public function save(\Illuminate\Http\Request $request)
473
    {
474
        $this->setModelAttribute($this->getValueFromRequest($request));
475
    }
476
477
    /**
478
     * @param mixed $value
479
     *
480
     * @return void
481
     */
482
    public function setModelAttribute($value)
483
    {
484
        $model = $this->getModelByPath($this->getPath());
485
486
        if ($this->isValueSkipped()) {
487
            return;
488
        }
489
490
        $model->setAttribute($this->getModelAttributeKey(), $this->prepareValue($value));
491
    }
492
493
    /**
494
     * @param string $path
495
     *
496
     * @return Model|null
497
     */
498
    protected function getModelByPath($path)
499
    {
500
        $model = $this->getModel();
501
502
        $relations = explode('.', $path);
503
        $count = count($relations);
504
        $i = 1;
505
506
        if ($count > 1) {
507
            $i++;
508
            $previousModel = $model;
509
510
            /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% 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...
511
              * @var Model $model
512
              */
513
            foreach ($relations as $relation) {
514
                $relatedModel = null;
515
                if ($previousModel->getAttribute($relation) instanceof Model) {
516
                    $relatedModel = $previousModel->getAttribute($relation);
517
                } elseif (method_exists($previousModel, $relation)) {
518
519
                    /**
520
                     * @var Relation
521
                     */
522
                    $relationObject = $previousModel->{$relation}();
523
524
                    switch (get_class($relationObject)) {
525
                        case BelongsTo::class:
526
                            $relationObject->associate($relatedModel = $relationObject->getRelated());
527
                            break;
528
                        case HasOne::class:
529
                        case MorphOne::class:
530
                            $relatedModel = $relationObject->getRelated()->newInstance();
531
                            $relatedModel->setAttribute($this->getForeignKeyNameFromRelation($relationObject), $relationObject->getParentKey());
532
                            $model->setRelation($relation, $relatedModel);
533
                            break;
534
                    }
535
                }
536
537
                $previousModel = $relatedModel;
538
                if ($i === $count) {
539
                    break;
540
                } elseif (is_null($relatedModel)) {
541
                    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');
542
                }
543
            }
544
545
            $model = $previousModel;
546
        }
547
548
        return $model;
549
    }
550
551
    protected function getForeignKeyNameFromRelation($relation)
552
    {
553
        return method_exists($relation, 'getForeignKeyName') ? $relation->getForeignKeyName()
554
            : $relation->getPlainForeignKey();
555
    }
556
557
    /**
558
     * Field->mutateValue(function($value) {
559
     *     return bcrypt($value);
560
     * }).
561
     *
562
     * @param \Closure $mutator
563
     *
564
     * @return $this
565
     */
566
    public function mutateValue(Closure $mutator)
567
    {
568
        $this->mutator = $mutator;
569
570
        return $this;
571
    }
572
573
    /**
574
     * @return bool
575
     */
576
    public function hasMutator()
577
    {
578
        return is_callable($this->mutator);
579
    }
580
581
    /**
582
     * @param mixed $value
583
     *
584
     * @return mixed
585
     */
586
    public function prepareValue($value)
587
    {
588
        if ($this->hasMutator()) {
589
            $value = call_user_func($this->mutator, $value);
590
        }
591
592
        return $value;
593
    }
594
595
    /**
596
     * @return mixed
597
     */
598
    public function getExactValue()
599
    {
600
        return $this->exactValue;
601
    }
602
603
    /**
604
     * @param mixed $exactValue
605
     *
606
     * @return $this
607
     */
608
    public function setExactValue($exactValue)
609
    {
610
        $this->exactValue = $exactValue;
611
        $this->exactValueSet = true;
612
613
        return $this;
614
    }
615
616
    /**
617
     * @return array
618
     */
619
    public function toArray()
620
    {
621
        $this->setHtmlAttributes([
622
            'id' => $this->getName(),
623
            'name' => $this->getName(),
624
        ]);
625
626
        return array_merge(parent::toArray(), [
627
            'id' => $this->getId(),
628
            'value' => $this->exactValueSet ? $this->getExactValue() : $this->getValueFromModel(),
629
            'name' => $this->getName(),
630
            'path' => $this->getPath(),
631
            'label' => $this->getLabel(),
632
            'attributes' => $this->htmlAttributesToString(),
633
            'helpText' => $this->getHelpText(),
634
            'required' => in_array('required', $this->validationRules),
635
        ]);
636
    }
637
}
638