Test Failed
Pull Request — master (#175)
by
unknown
05:56 queued 03:39
created

Nested::validateValue()   B

Complexity

Conditions 11
Paths 7

Size

Total Lines 39
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 11

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 11
eloc 23
nc 7
nop 2
dl 0
loc 39
ccs 24
cts 24
cp 1
crap 11
rs 7.3166
c 2
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
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule;
6
7
use InvalidArgumentException;
8
use Traversable;
9
use Yiisoft\Arrays\ArrayHelper;
10
use Yiisoft\Validator\FormatterInterface;
11
use Yiisoft\Validator\ParametrizedRuleInterface;
12
use Yiisoft\Validator\Result;
13
use Yiisoft\Validator\Rule;
14
use Yiisoft\Validator\RuleInterface;
15
use Yiisoft\Validator\RuleSet;
16
use Yiisoft\Validator\ValidationContext;
17
use function is_array;
18
use function is_object;
19
20
/**
21
 * Nested rule can be used for validation of nested structures.
22
 *
23
 * For example, we have an inbound request with the following structure:
24
 *
25
 * ```php
26
 * $request = [
27
 *     'author' => [
28
 *         'name' => 'Dmitry',
29
 *         'age' => 18,
30
 *     ],
31
 * ];
32
 * ```
33
 *
34
 * So to make validation with Nested rule we can configure it like this:
35
 *
36
 * ```php
37
 * $rule = new Nested([
38
 *     'author' => new Nested([
39
 *         'name' => [new HasLength(min: 3)],
40
 *         'age' => [new Number(min: 18)],
41
 *     )];
42
 * ]);
43
 * ```
44
 */
45
final class Nested extends Rule
46
{
47
    public function __construct(
48
        /**
49
         * @var Rule[][]
50
         */
51
        private iterable $rules,
52
        private bool $errorWhenPropertyPathIsNotFound = false,
53
        private string $propertyPathIsNotFoundMessage = 'Property path "{path}" is not found.',
54 16
        ?FormatterInterface $formatter = null,
55
        bool $skipOnEmpty = false,
56 16
        bool $skipOnError = false,
57 16
        $when = null
58 1
    ) {
59
        parent::__construct(formatter: $formatter, skipOnEmpty: $skipOnEmpty, skipOnError: $skipOnError, when: $when);
60
61 15
        $rules = $rules instanceof Traversable ? iterator_to_array($rules) : $rules;
62 15
        if (empty($rules)) {
63 1
            throw new InvalidArgumentException('Rules should not be empty.');
64 1
        }
65 1
66
        if ($this->checkRules($rules)) {
0 ignored issues
show
Bug introduced by
It seems like $rules can also be of type iterable; however, parameter $rules of Yiisoft\Validator\Rule\Nested::checkRules() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

66
        if ($this->checkRules(/** @scrutinizer ignore-type */ $rules)) {
Loading history...
67
            $message = sprintf('Each rule should be an instance of %s.', RuleInterface::class);
68
            throw new InvalidArgumentException($message);
69 14
        }
70
71 14
        $this->rules = $rules;
72
    }
73
74 11
    protected function validateValue($value, ?ValidationContext $context = null): Result
75
    {
76 11
        $result = new Result();
77 11
        if (!is_object($value) && !is_array($value)) {
78 1
            $message = sprintf('Value should be an array or an object. %s given.', gettype($value));
79 1
            $result->addError($message);
80 1
81
            return $result;
82
        }
83 1
84
        $value = (array) $value;
85
86 10
        foreach ($this->rules as $valuePath => $rules) {
87
            if ($this->errorWhenPropertyPathIsNotFound && !ArrayHelper::pathExists($value, $valuePath)) {
88 10
                $message = $this->formatMessage($this->propertyPathIsNotFoundMessage, ['path' => $valuePath]);
89 10
                $result->addError($message);
90 2
91 2
                continue;
92
            }
93 2
94
            $rules = is_array($rules) ? $rules : [$rules];
95
            $ruleSet = new RuleSet($rules);
96 8
            $validatedValue = ArrayHelper::getValueByPath($value, $valuePath);
97 8
            $itemResult = $ruleSet->validate($validatedValue);
98 8
            if ($itemResult->isValid()) {
99 8
                continue;
100 8
            }
101 3
102
            foreach ($itemResult->getErrors() as $error) {
103
                $errorValuePath = is_int($valuePath) ? [$valuePath] : explode('.', $valuePath);
104 6
                if ($error->getValuePath()) {
105 6
                    $errorValuePath = array_merge($errorValuePath, $error->getValuePath());
106 6
                }
107 3
108
                $result->addError($error->getMessage(), $errorValuePath);
109
            }
110 6
        }
111
112
        return $result;
113
    }
114 10
115
    private function checkRules(array $rules): bool
116
    {
117
        return array_reduce(
118
            $rules,
119
            function (bool $carry, $rule) {
120
                return $carry || (is_array($rule) ? $this->checkRules($rule) : !$rule instanceof RuleInterface);
121
            },
122 2
            false
123
        );
124 2
    }
125 2
126 2
    public function getOptions(): array
127
    {
128
        return $this->fetchOptions($this->rules);
129
    }
130
131
    private function fetchOptions(iterable $rules): array
132
    {
133
        $result = [];
134 1
        foreach ($rules as $attribute => $rule) {
135
            if (is_array($rule)) {
136 1
                $result[$attribute] = $this->fetchOptions($rule);
137 1
            } elseif ($rule instanceof ParametrizedRuleInterface) {
138 1
                $result[$attribute] = $rule->getOptions();
139
            } elseif ($rule instanceof RuleInterface) {
140
                // Just skip the rule that doesn't support parametrizing
141 2
                continue;
142
            } else {
143 2
                throw new InvalidArgumentException(sprintf(
144
                    'Rules should be an array of rules that implements %s.',
145
                    ParametrizedRuleInterface::class,
146 15
                ));
147
            }
148 15
        }
149 15
150 15
        return $result;
151 15
    }
152
}
153