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 — master ( b864c9...ed9166 )
by butschster
12:41
created

NamedFormElement::getModelByPath()   C

Complexity

Conditions 10
Paths 14

Size

Total Lines 49
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 14.7084

Importance

Changes 0
Metric Value
cc 10
eloc 32
nc 14
nop 1
dl 0
loc 49
ccs 23
cts 36
cp 0.6389
crap 14.7084
rs 5.5471
c 0
b 0
f 0

How to fix   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\Database\Eloquent\Model;
7
use SleepingOwl\Admin\Form\FormElement;
8
use Illuminate\Contracts\Support\Htmlable;
9
use Illuminate\Database\Eloquent\Relations\HasOne;
10
use Illuminate\Database\Eloquent\Relations\MorphOne;
11
use Illuminate\Database\Eloquent\Relations\Relation;
12
use Illuminate\Database\Eloquent\Relations\BelongsTo;
13
use SleepingOwl\Admin\Exceptions\Form\FormElementException;
14
15
abstract class NamedFormElement extends FormElement
16
{
17
    /**
18
     * @var string
19
     */
20
    protected $path;
21
22
    /**
23
     * @var string
24
     */
25
    protected $name;
26
27
    /**
28
     * @var string
29
     */
30
    protected $modelAttributeKey;
31
32
    /**
33
     * @var string
34
     */
35
    protected $label;
36
37
    /**
38
     * @var string
39
     */
40
    protected $helpText;
41
42
    /**
43
     * @var mixed
44
     */
45
    protected $defaultValue;
46
47
    /**
48
     * @var \Closure
49
     */
50
    protected $mutator;
51
52
    /**
53
     * @param string $path
54
     * @param string|null $label
55
     *
56
     * @throws FormElementException
57
     */
58 27
    public function __construct($path, $label = null)
59
    {
60 27
        if (empty($path)) {
61 1
            throw new FormElementException('You must specify field path');
62
        }
63
64 26
        $this->setPath($path);
65 26
        $this->setLabel($label);
66
67 26
        $parts = explode('.', $path);
68 26
        $this->setName($this->composeName($parts));
69 26
        $this->setModelAttributeKey(end($parts));
70
71 26
        parent::__construct();
72 26
    }
73
74
    /**
75
     * Compose html name from array like this: 'first[second][third]'.
76
     *
77
     * @param array $parts
78
     *
79
     * @return string
80
     */
81 26
    private function composeName(array $parts)
82
    {
83 26
        $name = array_shift($parts);
84
85 26
        while (! empty($parts)) {
86 7
            $part = array_shift($parts);
87 7
            $name .= "[$part]";
88 7
        }
89
90 26
        return $name;
91
    }
92
93
    /**
94
     * @return string
95
     */
96 13
    public function getPath()
97
    {
98 13
        return $this->path;
99
    }
100
101
    /**
102
     * @param string $path
103
     *
104
     * @return $this
105
     */
106 26
    public function setPath($path)
107
    {
108 26
        $this->path = $path;
109
110 26
        return $this;
111
    }
112
113
    /**
114
     * @return string
115
     */
116 5
    public function getName()
117
    {
118 5
        return $this->name;
119
    }
120
121
    /**
122
     * @param string $name
123
     *
124
     * @return $this
125
     */
126 26
    public function setName($name)
127
    {
128 26
        $this->name = $name;
129
130 26
        return $this;
131
    }
132
133
    /**
134
     * @return string
135
     */
136 4
    public function getLabel()
137
    {
138 4
        return $this->label;
139
    }
140
141
    /**
142
     * @param string $label
143
     *
144
     * @return $this
145
     */
146 26
    public function setLabel($label)
147
    {
148 26
        $this->label = $label;
149
150 26
        return $this;
151
    }
152
153
    /**
154
     * @return string
155
     */
156 6
    public function getModelAttributeKey()
157
    {
158 6
        return $this->modelAttributeKey;
159
    }
160
161
    /**
162
     * @param string $key
163
     *
164
     * @return $this
165
     */
166 26
    public function setModelAttributeKey($key)
167
    {
168 26
        $this->modelAttributeKey = $key;
169
170 26
        return $this;
171
    }
172
173
    /**
174
     * @return mixed
175
     */
176 3
    public function getDefaultValue()
177
    {
178 3
        return $this->defaultValue;
179
    }
180
181
    /**
182
     * @param mixed $defaultValue
183
     *
184
     * @return $this
185
     */
186 1
    public function setDefaultValue($defaultValue)
187
    {
188 1
        $this->defaultValue = $defaultValue;
189
190 1
        return $this;
191
    }
192
193
    /**
194
     * @return string
195
     */
196 2
    public function getHelpText()
197
    {
198 2
        if ($this->helpText instanceof Htmlable) {
199 1
            return $this->helpText->toHtml();
200
        }
201
202 2
        return $this->helpText;
203
    }
204
205
    /**
206
     * @param string|Htmlable $helpText
207
     *
208
     * @return $this
209
     */
210 1
    public function setHelpText($helpText)
211
    {
212 1
        $this->helpText = $helpText;
0 ignored issues
show
Documentation Bug introduced by
It seems like $helpText can also be of type object<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...
213
214 1
        return $this;
215
    }
216
217
    /**
218
     * @param string|null $message
219
     *
220
     * @return $this
221
     */
222 2
    public function required($message = null)
223
    {
224 2
        $this->addValidationRule('required', $message);
225
226 2
        return $this;
227
    }
228
229
    /**
230
     * @param string|null $message
231
     *
232
     * @return $this
233
     */
234 2
    public function unique($message = null)
235
    {
236 2
        $this->addValidationRule('_unique');
237
238 2
        if (! is_null($message)) {
239 1
            $this->addValidationMessage('unique', $message);
240 1
        }
241
242 2
        return $this;
243
    }
244
245
    /**
246
     * @return array
247
     */
248 3
    public function getValidationMessages()
249
    {
250 3
        $messages = parent::getValidationMessages();
251
252 3
        foreach ($messages as $rule => $message) {
253 3
            $messages[$this->getName().'.'.$rule] = $message;
254 3
            unset($messages[$rule]);
255 3
        }
256
257 3
        return $messages;
258
    }
259
260
    /**
261
     * @return array
262
     */
263 1
    public function getValidationLabels()
264
    {
265 1
        return [$this->getPath() => $this->getLabel()];
266
    }
267
268
    /**
269
     * If FormElement has `_unique` rule, it will get all appropriate
270
     * validation rules based on underlying model.
271
     *
272
     * @return array
273
     */
274 2
    public function getValidationRules()
275
    {
276 2
        $rules = parent::getValidationRules();
277
278 2
        foreach ($rules as &$rule) {
279 2
            if ($rule !== '_unique') {
280 1
                continue;
281
            }
282
283 1
            $model = $this->resolvePath();
284 1
            $table = $model->getTable();
285
286 1
            $rule = 'unique:'.$table.','.$this->getModelAttributeKey();
287 1
            if ($model->exists) {
288
                $rule .= ','.$model->getKey();
289
            }
290 2
        }
291 2
        unset($rule);
292
293 2
        return [$this->getPath() => $rules];
294
    }
295
296
    /**
297
     * Get model related to form element.
298
     *
299
     * @return mixed
300
     */
301 2
    public function resolvePath()
302
    {
303 2
        $model = $this->getModel();
304 2
        $relations = explode('.', $this->getPath());
305 2
        $count = count($relations);
306
307 2
        if ($count === 1) {
308 2
            return $model;
309
        }
310
311 1
        foreach ($relations as $relation) {
312 1
            if ($count === 1) {
313 1
                return $model;
314
            }
315
316 1
            if ($model->exists && ($value = $model->getAttribute($relation)) instanceof Model) {
317 1
                $model = $value;
318
319 1
                $count--;
320 1
                continue;
321
            }
322
323 1
            if (method_exists($model, $relation)) {
324 1
                $relation = $model->{$relation}();
325
326 1
                if ($relation instanceof Relation) {
327 1
                    $model = $relation->getModel();
328 1
                    $count--;
329 1
                    continue;
330
                }
331
            }
332
333
            break;
334
        }
335
336
        throw new LogicException("Can not resolve path for field '{$this->getPath()}'. Probably relation definition is incorrect");
337
    }
338
339
    /**
340
     * @param \Illuminate\Http\Request $request
341
     *
342
     * @return array|string
343
     */
344 6
    public function getValueFromRequest(\Illuminate\Http\Request $request)
345
    {
346 6
        if ($request->hasSession() && ! is_null($value = $request->old($this->getPath()))) {
347 1
            return $value;
348
        }
349
350 5
        return $request->input($this->getPath());
351
    }
352
353
    /**
354
     * @return mixed
355
     */
356 3
    public function getValueFromModel()
357
    {
358 3
        if (! is_null($value = $this->getValueFromRequest(request()))) {
359 1
            return $value;
360
        }
361
362 2
        $model = $this->getModel();
363 2
        $path = $this->getPath();
364 2
        $value = $this->getDefaultValue();
365
366 2
        if (is_null($model) or ! $model->exists) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
367 2
            return $value;
368
        }
369
370 1
        $relations = explode('.', $path);
371 1
        $count = count($relations);
372
373 1 View Code Duplication
        if ($count === 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
374 1
            $attribute = $model->getAttribute($this->getModelAttributeKey());
375
376 1
            if (! empty($attribute) or is_null($value)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
377 1
                return $attribute;
378
            }
379
        }
380
381
        foreach ($relations as $relation) {
382
            if ($model->{$relation} instanceof Model) {
383
                $model = $model->{$relation};
384
                continue;
385
            }
386
387 View Code Duplication
            if ($count === 2) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
388
                $attribute = $model->getAttribute($relation);
389
390
                if (! empty($attribute) or is_null($value)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
391
                    return $attribute;
392
                }
393
            }
394
395
            if (is_null($this->getDefaultValue())) {
396
                throw new LogicException("Can not fetch value for field '{$path}'. Probably relation definition is incorrect");
397
            }
398
        }
399
400
        return $value;
401
    }
402
403
    /**
404
     * @param \Illuminate\Http\Request $request
405
     *
406
     * @return void
407
     */
408 1
    public function save(\Illuminate\Http\Request $request)
409
    {
410 1
        $this->setModelAttribute(
411 1
            $this->getValueFromRequest(
412
                $request
413 1
            )
414 1
        );
415 1
    }
416
417
    /**
418
     * @param mixed  $value
419
     *
420
     * @return void
421
     */
422 3
    public function setModelAttribute($value)
423
    {
424 3
        $model = $this->getModelByPath(
425 3
            $this->getPath()
426 3
        );
427
428 3
        if ($this->isValueSkipped()) {
429 1
            return;
430
        }
431
432 3
        $model->setAttribute(
433 3
            $this->getModelAttributeKey(),
434 3
            $this->prepareValue($value)
435 3
        );
436 3
    }
437
438
    /**
439
     * @param string $path
440
     *
441
     * @return Model|null
442
     */
443 5
    protected function getModelByPath($path)
444
    {
445 5
        $model = $this->getModel();
446
447 5
        $relations = explode('.', $path);
448 5
        $count = count($relations);
449 5
        $i = 1;
450
451 5
        if ($count > 1) {
452 2
            $i++;
453 2
            $previousModel = $model;
454
455
            /* @var Model $model */
456 2
            foreach ($relations as $relation) {
457 2
                $relatedModel = null;
458 2
                if ($previousModel->getAttribute($relation) instanceof Model) {
459 2
                    $relatedModel = $previousModel->getAttribute($relation);
460 2
                } elseif (method_exists($previousModel, $relation)) {
461
462
                    /* @var Relation $relation */
463
                    $relationObject = $previousModel->{$relation}();
464
465
                    switch (get_class($relationObject)) {
466
                        case BelongsTo::class:
467
                            $relationObject->associate($relatedModel = $relationObject->getRelated());
468
                            break;
469
                        case HasOne::class:
470
                        case MorphOne::class:
471
                            $relatedModel = $relationObject->getRelated()->newInstance();
472
                            $relatedModel->setAttribute($relationObject->getPlainForeignKey(), $relationObject->getParentKey());
473
                            $model->setRelation($relation, $relatedModel);
0 ignored issues
show
Documentation introduced by
$relation is of type object<Illuminate\Databa...ent\Relations\Relation>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
474
                            break;
475
                    }
476
                }
477
478 2
                $previousModel = $relatedModel;
479 2
                if ($i === $count) {
480 1
                    break;
481 1
                } elseif (is_null($relatedModel)) {
482 1
                    throw new LogicException("Field [{$path}] can't be mapped to relations of model ".get_class($model)
483 1
                        .'. Probably some dot delimeted segment is not a supported relation type');
484
                }
485 2
            }
486
487 1
            $model = $previousModel;
488 1
        }
489
490 4
        return $model;
491
    }
492
493
    /**
494
     * Field->mutateValue(function($value) {
495
     *     return bcrypt($value);
496
     * }).
497
     *
498
     * @param \Closure $mutator
499
     *
500
     * @return $this
501
     */
502 2
    public function mutateValue(\Closure $mutator)
503
    {
504 2
        $this->mutator = $mutator;
505
506 2
        return $this;
507
    }
508
509
    /**
510
     * @return bool
511
     */
512 5
    public function hasMutator()
513
    {
514 5
        return is_callable($this->mutator);
515
    }
516
517
    /**
518
     * @param mixed $value
519
     *
520
     * @return mixed
521
     */
522 4
    public function prepareValue($value)
523
    {
524 4
        if ($this->hasMutator()) {
525 1
            $value = call_user_func($this->mutator, $value);
526 1
        }
527
528 4
        return $value;
529
    }
530
531
    /**
532
     * @return array
533
     */
534 1
    public function toArray()
535
    {
536 1
        return array_merge(parent::toArray(), [
537 1
            'id' => $this->getName(),
538 1
            'value' => $this->getValueFromModel(),
539 1
            'name' => $this->getName(),
540 1
            'path' => $this->getPath(),
541 1
            'label' => $this->getLabel(),
542 1
            'helpText' => $this->getHelpText(),
543 1
            'required' => in_array('required', $this->validationRules),
544 1
        ]);
545
    }
546
}
547