StdValidator   A
last analyzed

Complexity

Total Complexity 29

Size/Duplication

Total Lines 153
Duplicated Lines 0 %

Test Coverage

Coverage 93.65%

Importance

Changes 0
Metric Value
eloc 57
dl 0
loc 153
ccs 59
cts 63
cp 0.9365
rs 10
c 0
b 0
f 0
wmc 29

9 Methods

Rating   Name   Duplication   Size   Complexity  
A isValid() 0 3 1
A registerRuleValidator() 0 3 1
A processSingleField() 0 15 4
A fromConfig() 0 10 3
A processField() 0 10 3
B getMessages() 0 26 7
A processArrayField() 0 11 3
A detectEmpty() 0 15 5
A getRuleValidator() 0 7 2
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Hop\Validator;
6
7
use Hop\Validator\Message\MessageInterface;
8
use Hop\Validator\Message\FieldMessage;
9
use Hop\Validator\Message\MessagesContainer;
10
use Hop\Validator\Strategy\Field;
11
use Hop\Validator\Strategy\FieldInterface;
12
use Hop\Validator\Strategy\Strategy;
13
use Hop\Validator\Strategy\StructureField;
14
use Hop\Validator\Validator\RuleValidator;
15
16
/**
17
 * Class StdValidator
18
 * @package Application\Service\Validator
19
 */
20
class StdValidator implements Validator
21
{
22
    /**
23
     * @var RuleValidator[]
24
     */
25
    private $validators = [];
26
27
    /**
28
     * @param string $validatorName
29
     * @param RuleValidator $validator
30
     */
31 10
    public function registerRuleValidator(string $validatorName, RuleValidator $validator): void
32
    {
33 10
        $this->validators[$validatorName] = $validator;
34 10
    }
35
36
    /**
37
     * @param array $config
38
     * @return StdValidator
39
     */
40 1
    public static function fromConfig(array $config): self
41
    {
42 1
        $validator = new static;
43 1
        foreach ($config as $validatorName => $validatorClassName) {
44 1
            if (!\class_exists($validatorClassName)) {
45 1
                throw new \InvalidArgumentException(sprintf('Validator class %s does not exist', $validatorClassName));
46
            }
47
            $validator->registerRuleValidator($validatorName, new $validatorClassName);
48
        }
49
        return $validator;
50
    }
51
52
    /**
53
     * @param array $data
54
     * @param Strategy $strategy
55
     * @return bool
56
     * @throws \InvalidArgumentException
57
     */
58 11
    public function isValid(array $data, Strategy $strategy): bool
59
    {
60 11
        return \count($this->getMessages($data, $strategy)) === 0;
61
    }
62
63
    /**
64
     * @param array $data
65
     * @param Strategy $strategy
66
     * @return \Hop\Validator\Message\MessagesContainer
67
     */
68 11
    public function getMessages(array $data, Strategy $strategy): MessagesContainer
69
    {
70 11
        $messages = new MessagesContainer();
71
72 11
        foreach ($strategy->getFields() as $field) {
73 11
            if ($field->condition() !== null && !$field->condition()($data)) {
74 1
                continue;
75
            }
76
77 10
            if ($this->detectEmpty($data, $field->fieldName())) {
78 3
                if (!$field->required()) {
79 1
                    continue;
80
                }
81
82 2
                $messages->attachMessage($field->fieldName(), (new FieldMessage())->attachMessage('NotEmpty', 'Value cannot be empty'));
83 2
                continue;
84
            }
85
86 7
            $messages->attachMessage(
87 7
                $field->fieldName(),
88 7
                $field->isArray() ?
89 3
                    $this->processArrayField($field, $data[$field->fieldName()]) :
90 7
                    $this->processSingleField(@$data[$field->fieldName()], $field)
91
            );
92
        }
93 10
        return $messages;
94
    }
95
96
    /**
97
     * @param string $name
98
     * @return RuleValidator
99
     * @throws \InvalidArgumentException
100
     */
101 7
    private function getRuleValidator(string $name): RuleValidator
102
    {
103 7
        if (!isset($this->validators[$name])) {
104 1
            throw new \InvalidArgumentException(sprintf('Validator %s not registered', $name));
105
        }
106
107 6
        return $this->validators[$name];
108
    }
109
110
    /**
111
     * @param array $data
112
     * @param string $fieldName
113
     * @return bool
114
     */
115 10
    private function detectEmpty(array $data, string $fieldName): bool
116
    {
117 10
        if (!array_key_exists($fieldName, $data)) {
118 3
            return true;
119
        }
120
121 8
        if ($data[$fieldName] === null) {
122 1
            return true;
123
        }
124
125 8
        if (\is_string($data[$fieldName]) && $data[$fieldName] === '') {
126 1
            return true;
127
        }
128
129 7
        return false;
130
    }
131
132 7
    private function processSingleField($row, FieldInterface $field): MessageInterface
133
    {
134
        // TODO introduce policies/strategies if required
135
        // for now it might be overengineering
136
        switch (true) {
137 7
            case $field instanceof Field:
138 7
                return $this->processField($field, $row);
139 2
            case $field instanceof StructureField:
140 2
                if ($row === null) {
141
                    return new MessagesContainer();
142
                }
143 2
                $data = (array)$row;
144 2
                return $this->getMessages($data, $field->strategy($data));
145
            default:
146
                throw new \DomainException('Unknown field type');
147
        }
148
    }
149
150 7
    private function processField(Field $field, $row): FieldMessage
151
    {
152 7
        $messages = new FieldMessage();
153 7
        foreach ($field->validators() as $validatorName => $options) {
154 7
            $ruleValidator = $this->getRuleValidator($validatorName);
155 6
            if (!$ruleValidator->isValid($row, $options)) {
156 6
                $messages->attachMessage($validatorName, $ruleValidator->getMessage($row, $options));
157
            }
158
        }
159 6
        return $messages;
160
    }
161
162 3
    private function processArrayField(FieldInterface $field, $data) : MessageInterface
163
    {
164 3
        $messages = new MessagesContainer();
165 3
        if (!\is_array($data)) {
166 1
            return (new FieldMessage())->attachMessage('NotArray', 'Value is not an array');
167
        }
168
169 3
        foreach ($data as $index => $row) {
170 3
            $messages->attachMessage((string)$index, $this->processSingleField($row, $field));
171
        }
172 3
        return $messages;
173
    }
174
}
175