Passed
Push — master ( 8099cd...08f3f4 )
by Smoren
02:12
created

ContainerRule::hasOptionalAttribute()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 3
eloc 13
c 1
b 0
f 1
nc 1
nop 2
dl 0
loc 18
ccs 0
cts 14
cp 0
crap 12
rs 9.8333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Smoren\Validator\Rules;
6
7
use Smoren\Validator\Checks\Check;
8
use Smoren\Validator\Exceptions\ValidationError;
9
use Smoren\Validator\Helpers\ContainerAccessHelper;
10
use Smoren\Validator\Interfaces\RuleInterface;
11
use Smoren\Validator\Interfaces\CheckInterface;
12
use Smoren\Validator\Interfaces\ContainerRuleInterface;
13
use Smoren\Validator\Interfaces\IntegerRuleInterface;
14
15
class ContainerRule extends Rule implements ContainerRuleInterface
16
{
17
    public const ERROR_NOT_CONTAINER = 'not_container';
18
    public const ERROR_NOT_ARRAY = 'not_array';
19
    public const ERROR_NOT_INDEXED_ARRAY = 'not_array';
20
    public const ERROR_NOT_ASSOCIATIVE_ARRAY = 'not_array';
21
    public const ERROR_NOT_ITERABLE = 'not_iterable';
22
    public const ERROR_NOT_COUNTABLE = 'not_countable';
23
    public const ERROR_NOT_EMPTY = 'not_empty';
24
    public const ERROR_EMPTY = 'not_empty';
25
    public const ERROR_NOT_ARRAY_ACCESSIBLE = 'not_array_accessible';
26
    public const ERROR_NOT_OBJECT = 'not_object';
27
    public const ERROR_NOT_STD_OBJECT = 'not_std_object';
28
    public const ERROR_NOT_INSTANCE_OF = 'not_instance_of';
29
    public const ERROR_BAD_LENGTH = 'bad_length';
30
    public const ERROR_ATTRIBUTE_NOT_EXIST = 'attribute_not_exist';
31
    public const ERROR_BAD_ATTRIBUTE = 'bad_attribute';
32
    public const ERROR_SOME_KEYS_BAD = 'some_keys_bad';
33
    public const ERROR_SOME_VALUES_BAD = 'some_values_bad';
34
35
    /**
36
     * ContainerRule constructor.
37
     */
38 18
    public function __construct()
39
    {
40 18
        $this->check(new Check(
41 18
            self::ERROR_NOT_CONTAINER,
42 18
            fn ($value) => \is_array($value) || \is_object($value),
43 18
            []
44 18
        ), true);
45
    }
46
47
    /**
48
     * {@inheritDoc}
49
     *
50
     * @return static
51
     */
52
    public function array(): self
53
    {
54
        return $this->check($this->getArrayCheck());
55
    }
56
57
    /**
58
     * {@inheritDoc}
59
     *
60
     * @return static
61
     */
62 2
    public function indexedArray(): self
63
    {
64 2
        return $this->check(new Check(
65 2
            self::ERROR_NOT_INDEXED_ARRAY,
66 2
            fn ($value) => (\array_values($value) === $value),
67 2
            [],
68 2
            [$this->getArrayCheck()]
69 2
        ));
70
    }
71
72
    /**
73
     * {@inheritDoc}
74
     *
75
     * @return static
76
     */
77 1
    public function associativeArray(): self
78
    {
79 1
        return $this->check(new Check(
80 1
            self::ERROR_NOT_ASSOCIATIVE_ARRAY,
81 1
            fn ($value) => \array_values($value) !== $value,
82 1
            [],
83 1
            [$this->getArrayCheck()]
84 1
        ));
85
    }
86
87
    /**
88
     * {@inheritDoc}
89
     *
90
     * @return static
91
     */
92
    public function arrayAccessible(): self
93
    {
94
        return $this->check(new Check(
95
            self::ERROR_NOT_ARRAY_ACCESSIBLE,
96
            fn ($value) => \is_array($value) || $value instanceof \ArrayAccess
97
        ));
98
    }
99
100
    /**
101
     * {@inheritDoc}
102
     *
103
     * @return static
104
     */
105
    public function iterable(): self
106
    {
107
        return $this->check($this->getIterableCheck());
108
    }
109
110
    /**
111
     * {@inheritDoc}
112
     *
113
     * @return static
114
     */
115
    public function countable(): self
116
    {
117
        return $this->check($this->getCountableCheck());
118
    }
119
120
    /**
121
     * {@inheritDoc}
122
     *
123
     * @return static
124
     */
125
    public function empty(): self
126
    {
127
        return $this->check(new Check(
128
            self::ERROR_NOT_EMPTY,
129
            fn ($value) => \count($value) === 0,
130
            [],
131
            [$this->getCountableCheck()]
132
        ));
133
    }
134
135
    /**
136
     * {@inheritDoc}
137
     *
138
     * @return static
139
     */
140
    public function notEmpty(): self
141
    {
142
        return $this->check(new Check(
143
            self::ERROR_EMPTY,
144
            fn ($value) => \count($value) > 0,
145
            [],
146
            [$this->getCountableCheck()]
147
        ));
148
    }
149
150
    /**
151
     * {@inheritDoc}
152
     *
153
     * @return static
154
     */
155 2
    public function object(): self
156
    {
157 2
        return $this->check(new Check(
158 2
            self::ERROR_NOT_OBJECT,
159 2
            fn ($value) => \is_object($value)
160 2
        ));
161
    }
162
163
    /**
164
     * {@inheritDoc}
165
     *
166
     * @return static
167
     */
168
    public function stdObject(): self
169
    {
170
        return $this->check(new Check(
171
            self::ERROR_NOT_STD_OBJECT,
172
            fn ($value) => $value instanceof \stdClass
173
        ));
174
    }
175
176
    /**
177
     * {@inheritDoc}
178
     *
179
     * @return static
180
     */
181
    public function instanceOf(string $class): self
182
    {
183
        return $this->check(new Check(
184
            self::ERROR_NOT_INSTANCE_OF,
185
            fn ($value) => $value instanceof $class
186
        ));
187
    }
188
189
    /**
190
     * {@inheritDoc}
191
     *
192
     * @return static
193
     */
194 2
    public function lengthIs(IntegerRuleInterface $rule): self
195
    {
196
        $violations = [];
197 2
        return $this->check(new Check(
198 2
            self::ERROR_BAD_LENGTH,
199 2
            static function ($value) use ($rule, &$violations) {
200
                try {
201
                    /** @var \Countable $value */
202 2
                    $rule->validate(\count($value));
203 1
                    return true;
204 1
                } catch (ValidationError $e) {
205 1
                    $violations = $e->getSummary();
206 1
                    return false;
207
                }
208 2
            },
209 2
            ['violations' => &$violations],
210 2
            [$this->getCountableCheck()]
211 2
        ));
212
    }
213
214
    /**
215
     * {@inheritDoc}
216
     *
217
     * @return static
218
     */
219 2
    public function hasAttribute(string $name, ?RuleInterface $rule = null): self
220
    {
221
        if ($rule === null) {
222
            return $this->check($this->getHasAttributeCheck($name));
223
        }
224
225
        $violations = [];
226 2
        return $this->check(new Check(
227 2
            self::ERROR_BAD_ATTRIBUTE,
228 2
            function ($value) use ($name, $rule, &$violations) {
229
                try {
230 2
                    $rule->validate(ContainerAccessHelper::getAttributeValue($value, $name));
231 1
                    return true;
232 1
                } catch (ValidationError $e) {
233 1
                    $violations = $e->getSummary();
234 1
                    return false;
235
                }
236 2
            },
237 2
            ['name' => $name, 'violations' => &$violations],
238 2
            [$this->getHasAttributeCheck($name)]
239 2
        ));
240
    }
241
242
    /**
243
     * {@inheritDoc}
244
     *
245
     * @return static
246
     */
247
    public function hasOptionalAttribute(string $name, RuleInterface $rule): self
248
    {
249
        $violations = [];
250
        return $this->check(new Check(
251
            self::ERROR_BAD_ATTRIBUTE,
252
            function ($value) use ($name, $rule, &$violations) {
253
                if (!ContainerAccessHelper::hasAccessibleAttribute($value, $name)) {
254
                    return true;
255
                }
256
                try {
257
                    $rule->validate(ContainerAccessHelper::getAttributeValue($value, $name));
258
                    return true;
259
                } catch (ValidationError $e) {
260
                    $violations = $e->getSummary();
261
                    return false;
262
                }
263
            },
264
            ['name' => $name, 'violations' => &$violations]
265
        ));
266
    }
267
268
    /**
269
     * {@inheritDoc}
270
     *
271
     * @return static
272
     */
273
    public function everyKeyIs(RuleInterface $rule): self
274
    {
275
        $violations = [];
276
        return $this->check(
277
            new Check(
278
                self::ERROR_SOME_KEYS_BAD,
279
                static function ($value) use ($rule, &$violations) {
280
                    foreach ($value as $k => $v) {
281
                        try {
282
                            $rule->validate($k);
283
                        } catch (ValidationError $e) {
284
                            $violations = $e->getSummary();
285
                            return false;
286
                        }
287
                    }
288
                    return true;
289
                },
290
                ['violations' => &$violations],
291
                [$this->getIterableCheck()]
292
            )
293
        );
294
    }
295
296
    /**
297
     * {@inheritDoc}
298
     *
299
     * @return static
300
     */
301 2
    public function everyValueIs(RuleInterface $rule): self
302
    {
303
        $violations = [];
304 2
        return $this->check(
305 2
            new Check(
306 2
                self::ERROR_SOME_VALUES_BAD,
307 2
                static function ($value) use ($rule, &$violations) {
308 2
                    foreach ($value as $v) {
309
                        try {
310 2
                            $rule->validate($v);
311 1
                        } catch (ValidationError $e) {
312 1
                            $violations = $e->getSummary();
313 1
                            return false;
314
                        }
315
                    }
316 1
                    return true;
317 2
                },
318 2
                ['violations' => &$violations],
319 2
                [$this->getIterableCheck()]
320 2
            )
321 2
        );
322
    }
323
324 5
    protected function getArrayCheck(): CheckInterface
325
    {
326 5
        return new Check(
327 5
            self::ERROR_NOT_ARRAY,
328 5
            fn ($value) => \is_array($value)
329 5
        );
330
    }
331
332 2
    protected function getCountableCheck(): CheckInterface
333
    {
334 2
        return new Check(
335 2
            self::ERROR_NOT_COUNTABLE,
336 2
            fn ($value) => \is_countable($value)
337 2
        );
338
    }
339
340 2
    protected function getIterableCheck(): CheckInterface
341
    {
342 2
        return new Check(
343 2
            self::ERROR_NOT_ITERABLE,
344 2
            fn ($value) => \is_iterable($value)
345 2
        );
346
    }
347
348 5
    protected function getHasAttributeCheck(string $name): CheckInterface
349
    {
350 5
        return new Check(
351 5
            self::ERROR_ATTRIBUTE_NOT_EXIST,
352 5
            fn ($value) => ContainerAccessHelper::hasAccessibleAttribute($value, $name),
353 5
            ['name' => $name]
354 5
        );
355
    }
356
}
357