Passed
Push — main ( c8bc71...34003d )
by Breno
01:35
created

RuleSet   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 174
Duplicated Lines 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 59
dl 0
loc 174
rs 9.36
c 6
b 0
f 0
wmc 38

19 Methods

Rating   Name   Duplication   Size   Complexity  
A new() 0 3 1
A __construct() 0 5 1
A getIterator() 0 3 1
A setAllowsNull() 0 3 1
A toArray() 0 3 1
A attachRules() 0 10 5
A isRequired() 0 13 4
A forField() 0 3 1
A count() 0 3 1
A validate() 0 12 3
A allowsEmpty() 0 13 4
A withRules() 0 3 1
A add() 0 5 1
A allowsNull() 0 13 4
A isEmptyInput() 0 3 1
A isEmpty() 0 3 1
A shouldValidate() 0 11 5
A setNotRequired() 0 3 1
A setAllowsEmpty() 0 3 1
1
<?php
2
declare(strict_types=1);
3
4
namespace BrenoRoosevelt\Validation;
5
6
use BrenoRoosevelt\Validation\Factories\ComparisonFactory;
7
use BrenoRoosevelt\Validation\Rules\AllowsEmpty;
8
use BrenoRoosevelt\Validation\Rules\AllowsNull;
9
use BrenoRoosevelt\Validation\Rules\IsEmpty;
10
use BrenoRoosevelt\Validation\Rules\NotRequired;
11
use Countable;
12
use IteratorAggregate;
13
use SplObjectStorage;
14
15
class RuleSet implements Rule, IteratorAggregate, Countable
16
{
17
    use GuardForValidation,
18
        ComparisonFactory,
19
        MaybeBelongsToField {
20
        setField as private;
21
    }
22
23
    private SplObjectStorage $rules;
24
25
    /**
26
     * @throws ValidationException if the field is provided and is blank
27
     */
28
    final public function __construct(?string $field = null, Rule|RuleSet ...$rules)
29
    {
30
        $this->rules = new SplObjectStorage;
31
        $this->field = $field;
32
        $this->attachRules(...$rules);
33
    }
34
35
    public static function new(): self
36
    {
37
        return new self;
38
    }
39
40
    public static function forField(string $field, Rule|RuleSet ...$rules): self
41
    {
42
        return new self($field, ...$rules);
43
    }
44
45
    public static function withRules(Rule|RuleSet ...$rules): self
46
    {
47
        return new self(null, ...$rules);
48
    }
49
50
    private function attachRules(Rule|RuleSet ...$rules): void
51
    {
52
        foreach ($rules as $validationOrSet) {
53
            if ($validationOrSet instanceof Rule) {
54
                $this->rules->attach($validationOrSet);
55
            }
56
57
            if ($validationOrSet instanceof RuleSet) {
58
                foreach ($validationOrSet as $validation) {
59
                    $this->rules->attach($validation);
60
                }
61
            }
62
        }
63
    }
64
65
    public function add(Rule|RuleSet ...$rules): self
66
    {
67
        $instance = clone $this;
68
        $instance->attachRules(...$rules);
69
        return $instance;
70
    }
71
72
    public function validate(mixed $input, array $context = []): ValidationResult|ValidationResultByField
73
    {
74
        $violations = $empty = $this->newEmptyValidationResult();
75
        if (!$this->shouldValidate($input)) {
76
            return $empty;
77
        }
78
79
        foreach ($this->rules as $rule) {
80
            $violations = $violations->error(...$rule->validate($input, $context)->getErrors());
81
        }
82
83
        return $violations;
84
    }
85
86
    private function shouldValidate(mixed $input): bool
87
    {
88
        if (null === $input && $this->allowsNull()) {
89
            return false;
90
        }
91
92
        if ($this->isEmptyInput($input) && $this->allowsEmpty()) {
93
            return false;
94
        }
95
96
        return true;
97
    }
98
99
    private function isEmptyInput($input): bool
100
    {
101
        return (new IsEmpty)->isValid($input);
102
    }
103
104
    public function isRequired(): bool
105
    {
106
        if ($this->isEmpty()) {
107
            return false;
108
        }
109
110
        foreach ($this->rules as $rule) {
111
            if ($rule instanceof NotRequired) {
112
                return false;
113
            }
114
        }
115
116
        return true;
117
    }
118
119
    public function allowsEmpty(): bool
120
    {
121
        if ($this->isEmpty()) {
122
            return true;
123
        }
124
125
        foreach ($this->rules as $rule) {
126
            if ($rule instanceof AllowsEmpty) {
127
                return true;
128
            }
129
        }
130
131
        return false;
132
    }
133
134
    public function allowsNull(): bool
135
    {
136
        if ($this->isEmpty()) {
137
            return true;
138
        }
139
140
        foreach ($this->rules as $rule) {
141
            if ($rule instanceof AllowsNull) {
142
                return true;
143
            }
144
        }
145
146
        return false;
147
    }
148
149
    public function setNotRequired(): self
150
    {
151
        return $this->add(NotRequired::instance());
152
    }
153
154
    /**
155
     * Allows empty arrays or strings (only)
156
     */
157
    public function setAllowsEmpty(): self
158
    {
159
        return $this->add(AllowsEmpty::instance());
160
    }
161
162
    /**
163
     * Allows null values
164
     */
165
    public function setAllowsNull(): self
166
    {
167
        return $this->add(AllowsNull::instance());
168
    }
169
170
    public function isEmpty(): bool
171
    {
172
        return $this->rules->count() === 0;
173
    }
174
175
    /** @return Rule[] */
176
    public function toArray(): array
177
    {
178
        return iterator_to_array($this->rules);
179
    }
180
181
    public function getIterator(): SplObjectStorage
182
    {
183
        return clone $this->rules;
184
    }
185
186
    public function count(): int
187
    {
188
        return $this->rules->count();
189
    }
190
}
191