Passed
Push — 4 ( 34eb17...84b405 )
by Robbie
08:27 queued 12s
created

CompositeValidator::validate()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 30
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 5
nop 0
dl 0
loc 30
rs 9.5555
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Forms;
4
5
use InvalidArgumentException;
6
use SilverStripe\ORM\ValidationResult;
7
8
/**
9
 * CompositeValidator can contain between 0 and many different types of Validators. Each Validator is itself still
10
 * responsible for Validating its form and generating its ValidationResult.
11
 *
12
 * Once each Validator has generated its ValidationResult, the CompositeValidator will combine these results into a
13
 * single ValidationResult. This single ValidationResult is what will be returned by the CompositeValidator.
14
 *
15
 * You can add Validators to the CompositeValidator in any DataObject by extending the getCMSCompositeValidator()
16
 * method:
17
 *
18
 * public function getCMSCompositeValidator(): CompositeValidator
19
 * {
20
 *   $compositeValidator = parent::getCMSCompositeValidator();
21
 *
22
 *   $compositeValidator->addValidator(RequiredFields::create(['MyRequiredField']));
23
 *
24
 *   return $compositeValidator
25
 * }
26
 *
27
 * Or by implementing the updateCMSCompositeValidator() method in a DataExtension:
28
 *
29
 * public function updateCMSCompositeValidator(CompositeValidator $compositeValidator): void
30
 * {
31
 *   $compositeValidator->addValidator(RequiredFields::create(['AdditionalContent']));
32
 * }
33
 *
34
 * Class CompositeValidator
35
 *
36
 * @package SilverStripe\Forms
37
 */
38
class CompositeValidator extends Validator
39
{
40
    /**
41
     * @var array|Validator[]
42
     */
43
    private $validators;
44
45
    /**
46
     * CompositeValidator constructor.
47
     *
48
     * @param array|Validator[] $validators
49
     */
50
    public function __construct(array $validators = [])
51
    {
52
        $this->validators = array_values($validators);
53
54
        parent::__construct();
55
    }
56
57
    /**
58
     * Set the provided Form to the CompositeValidator and each Validator that has been added.
59
     *
60
     * @param Form $form
61
     * @return Validator
62
     */
63
    public function setForm($form)
64
    {
65
        foreach ($this->getValidators() as $validator) {
66
            $validator->setForm($form);
67
        }
68
69
        return parent::setForm($form);
70
    }
71
72
    /**
73
     * @param Validator $validator
74
     * @return CompositeValidator
75
     */
76
    public function addValidator(Validator $validator): CompositeValidator
77
    {
78
        $this->validators[] = $validator;
79
80
        return $this;
81
    }
82
83
    /**
84
     * Returns any errors there may be. This method considers the enabled status of the CompositeValidator as a whole
85
     * (exiting early if the Composite is disabled), as well as the enabled status of each individual Validator.
86
     *
87
     * @return ValidationResult
88
     */
89
    public function validate()
90
    {
91
        $this->resetResult();
92
93
        // This CompositeValidator has been disabled in full
94
        if (!$this->getEnabled()) {
95
            return $this->result;
96
        }
97
98
        $data = $this->form->getData();
99
100
        foreach ($this->getValidators() as $validator) {
101
            // Reset the validation results for this Validator
102
            $validator->resetResult();
103
104
            // This Validator has been disabled, so skip it
105
            if (!$validator->getEnabled()) {
106
                continue;
107
            }
108
109
            // Run validation, and exit early if it's valid
110
            if ($validator->php($data)) {
111
                continue;
112
            }
113
114
            // Validation result was invalid. Combine our ValidationResult messages
115
            $this->getResult()->combineAnd($validator->getResult());
116
        }
117
118
        return $this->result;
119
    }
120
121
    /**
122
     * Note: The existing implementations for the php() method (@see RequiredFields) does not check whether the
123
     * Validator is enabled or not, and it also does not reset the validation result - so, neither does this.
124
     *
125
     * @param array $data
126
     * @return bool
127
     */
128
    public function php($data)
129
    {
130
        foreach ($this->getValidators() as $validator) {
131
            // Run validation, and exit early if it's valid
132
            if ($validator->php($data)) {
133
                continue;
134
            }
135
136
            // Validation result was invalid. Combine our ValidationResult messages
137
            $this->getResult()->combineAnd($validator->getResult());
138
        }
139
140
        // After collating results, return whether or not everything was valid
141
        return $this->getResult()->isValid();
142
    }
143
144
    /**
145
     * Returns whether the field in question is required. This will usually display '*' next to the
146
     * field.
147
     *
148
     * @param string $fieldName
149
     *
150
     * @return bool
151
     */
152
    public function fieldIsRequired($fieldName)
153
    {
154
        foreach ($this->getValidators() as $validator) {
155
            if ($validator->fieldIsRequired($fieldName)) {
156
                return true;
157
            }
158
        }
159
160
        return false;
161
    }
162
163
    /**
164
     * @return array|Validator[]
165
     */
166
    public function getValidators(): array
167
    {
168
        return $this->validators;
169
    }
170
171
    /**
172
     * Return all Validators that match a certain class name. EG: RequiredFields::class
173
     *
174
     * @param string $className
175
     * @return array|Validator[]
176
     */
177
    public function getValidatorsByType(string $className): array
178
    {
179
        $validators = [];
180
181
        foreach ($this->getValidators() as $key => $validator) {
182
            if (!$validator instanceof $className) {
183
                continue;
184
            }
185
186
            $validators[$key] = $validator;
187
        }
188
189
        return $validators;
190
    }
191
192
    /**
193
     * Remove all Validators that match a certain class name. EG: RequiredFields::class
194
     *
195
     * @param string $className
196
     * @return CompositeValidator
197
     */
198
    public function removeValidatorsByType(string $className): CompositeValidator
199
    {
200
        foreach ($this->getValidatorsByType($className) as $key => $validator) {
201
            $this->removeValidatorByKey($key);
202
        }
203
204
        return $this;
205
    }
206
207
    /**
208
     * Each Validator is aware of whether or not it can be cached. If even one Validator cannot be cached, then the
209
     * CompositeValidator as a whole also cannot be cached.
210
     *
211
     * @return bool
212
     */
213
    public function canBeCached(): bool
214
    {
215
        foreach ($this->getValidators() as $validator) {
216
            if (!$validator->canBeCached()) {
217
                return false;
218
            }
219
        }
220
221
        return true;
222
    }
223
224
    /**
225
     * @internal This method may be updated to public in the future. Let us know if you feel there's a use case for it
226
     * @param int $key
227
     * @return CompositeValidator
228
     */
229
    protected function removeValidatorByKey(int $key): CompositeValidator
230
    {
231
        if (!array_key_exists($key, $this->validators)) {
232
            throw new InvalidArgumentException(
233
                sprintf('Key "%s" does not exist in $validators array', $key)
234
            );
235
        }
236
237
        unset($this->validators[$key]);
238
239
        return $this;
240
    }
241
}
242