Passed
Push — main ( 6de128...92895e )
by Breno
01:53
created

ValidationSet::setAllowsEmpty()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace BrenoRoosevelt\Validation;
5
6
use BrenoRoosevelt\Validation\Rules\AllowsEmpty;
7
use BrenoRoosevelt\Validation\Rules\AllowsNull;
8
use BrenoRoosevelt\Validation\Rules\NotRequired;
9
use Countable;
10
use IteratorAggregate;
11
use ReflectionAttribute;
12
use ReflectionClass;
13
use ReflectionException;
14
use ReflectionMethod;
15
use ReflectionProperty;
16
use SplObjectStorage;
17
18
class ValidationSet implements Validation, IteratorAggregate, Countable
19
{
20
    use GuardForValidation,
21
        MaybeBelongsToField;
22
23
    private SplObjectStorage $rules;
24
25
    final public function __construct(?string $field = null, Validation|ValidationSet ...$rules)
26
    {
27
        $this->rules = new SplObjectStorage;
28
        $this->withField($field);
29
        $this->attachRules(...$rules);
30
    }
31
32
    public static function empty(): self
33
    {
34
        return new self;
35
    }
36
37
    public static function forField(string $field, Validation|ValidationSet ...$rules): self
38
    {
39
        return new self($field, ...$rules);
40
    }
41
42
    public static function withRules(Validation|ValidationSet ...$rules): self
43
    {
44
        return new self(null, ...$rules);
45
    }
46
47
    public function add(Validation|ValidationSet ...$rules): self
48
    {
49
        $instance = clone $this;
50
        $instance->attachRules(...$rules);
51
        return $instance;
52
    }
53
54
    private function attachRules(Validation|ValidationSet ...$rules): void
55
    {
56
        foreach ($rules as $validationOrSet) {
57
            if ($validationOrSet instanceof Validation) {
58
                $this->rules->attach($validationOrSet);
59
            }
60
61
            if ($validationOrSet instanceof ValidationSet) {
62
                foreach ($validationOrSet as $validation) {
63
                    $this->rules->attach($validation);
64
                }
65
            }
66
        }
67
    }
68
69
    public function validate(mixed $input, array $context = []): ValidationResult|ValidationResultByField
70
    {
71
        $violations = $empty = $this->newEmptyValidationResult();
72
        if (null === $input && $this->allowsNull()) {
73
            return $empty;
74
        }
75
76
        if (empty($input) && $this->allowsEmpty()) {
77
            return $empty;
78
        }
79
80
        foreach ($this->rules as $rule) {
81
            $violations = $violations->error(...$rule->validate($input, $context)->getErrors());
82
        }
83
84
        return $violations;
85
    }
86
87
    public function isRequired(): bool
88
    {
89
        if ($this->isEmpty()) {
90
            return false;
91
        }
92
93
        foreach ($this->rules as $rule) {
94
            if ($rule instanceof NotRequired) {
95
                return false;
96
            }
97
        }
98
99
        return true;
100
    }
101
102
    public function allowsEmpty(): bool
103
    {
104
        if ($this->isEmpty()) {
105
            return true;
106
        }
107
108
        foreach ($this->rules as $rule) {
109
            if ($rule instanceof AllowsEmpty) {
110
                return true;
111
            }
112
        }
113
114
        return false;
115
    }
116
117
    public function allowsNull(): bool
118
    {
119
        if ($this->isEmpty()) {
120
            return true;
121
        }
122
123
        foreach ($this->rules as $rule) {
124
            if ($rule instanceof AllowsNull) {
125
                return true;
126
            }
127
        }
128
129
        return false;
130
    }
131
132
    public function setNotRequired(): self
133
    {
134
        return $this->add(NotRequired::instance());
135
    }
136
137
    public function setAllowsEmpty(): self
138
    {
139
        return $this->add(AllowsEmpty::instance());
140
    }
141
142
    public function setAllowsNull(): self
143
    {
144
        return $this->add(AllowsNull::instance());
145
    }
146
147
    public function isEmpty(): bool
148
    {
149
        return $this->rules->count() === 0;
150
    }
151
152
    /**
153
     * @param string|object $objectOrClass
154
     * @param int|null $filter filter properties, ex: ReflectionProperty::IS_PUBLIC|ReflectionProperty::IS_PRIVATE
155
     * @return ValidationSet[]
156
     * @throws ReflectionException if the class does not exist
157
     */
158
    public static function fromProperties(string|object $objectOrClass, ?int $filter = null): array
159
    {
160
        $ruleSets = [];
161
        foreach ((new ReflectionClass($objectOrClass))->getProperties($filter) as $property) {
162
            $ruleSets[$property->getName()] = ValidationSet::fromReflectionProperty($property);
163
        }
164
165
        return array_filter($ruleSets, fn(ValidationSet $c) => !$c->isEmpty());
166
    }
167
168
    /**
169
     * @param string|object $objectOrClass
170
     * @param int|null $filter
171
     * @return ValidationSet[]
172
     * @throws ReflectionException
173
     */
174
    public static function fromMethods(string|object $objectOrClass, ?int $filter = null): array
175
    {
176
        $ruleSets = [];
177
        foreach ((new ReflectionClass($objectOrClass))->getMethods($filter) as $method) {
178
            $ruleSets[$method->getName()] = ValidationSet::fromReflectionMethod($method);
179
        }
180
181
        return array_filter($ruleSets, fn(ValidationSet $c) => !$c->isEmpty());
182
    }
183
184
    /**
185
     * @param string|object $objectOrClass
186
     * @param string $property
187
     * @return static
188
     * @throws ReflectionException if the class or property does not exist.
189
     */
190
    public static function fromProperty(string|object $objectOrClass, string $property): self
191
    {
192
        return self::fromReflectionProperty(new ReflectionProperty($objectOrClass, $property));
193
    }
194
195
    public static function fromMethod(string|object $objectOrClass, string $method): self
196
    {
197
        return self::fromReflectionMethod(new ReflectionMethod($objectOrClass, $method));
198
    }
199
200
    /**
201
     * @param ReflectionProperty $property
202
     * @return static
203
     */
204
    public static function fromReflectionProperty(ReflectionProperty $property): self
205
    {
206
        return
207
            ValidationSet::forField(
208
                $property->getName(),
209
                ...array_map(
210
                    fn(ReflectionAttribute $attribute) => $attribute->newInstance(),
211
                    $property->getAttributes(Validation::class, ReflectionAttribute::IS_INSTANCEOF)
212
                )
213
            );
214
    }
215
216
    /**
217
     * @param ReflectionMethod $method
218
     * @return static
219
     */
220
    public static function fromReflectionMethod(ReflectionMethod $method): self
221
    {
222
        return
223
            ValidationSet::withRules(
224
                ...array_map(
225
                    fn(ReflectionAttribute $attribute) => $attribute->newInstance(),
226
                    $method->getAttributes(Validation::class, ReflectionAttribute::IS_INSTANCEOF)
227
                )
228
            );
229
    }
230
231
    /** @return Validation[] */
232
    public function toArray(): array
233
    {
234
        return iterator_to_array($this->rules);
235
    }
236
237
    public function getIterator(): SplObjectStorage
238
    {
239
        return clone $this->rules;
240
    }
241
242
    public function count(): int
243
    {
244
        return $this->rules->count();
245
    }
246
}
247