Completed
Push — master ( 704002...c70156 )
by Andreas
08:14
created

Validator::validateValue()   F

Complexity

Conditions 61
Paths 396

Size

Total Lines 186
Code Lines 111

Duplication

Lines 69
Ratio 37.1 %

Code Coverage

Tests 133
CRAP Score 61

Importance

Changes 0
Metric Value
dl 69
loc 186
ccs 133
cts 133
cp 1
rs 3.3333
c 0
b 0
f 0
cc 61
eloc 111
nc 396
nop 2
crap 61

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Utils.
4
 *
5
 * @copyright Copyright (c) 2016 Starweb / Ehandelslogik i Lund AB
6
 * @license   BSD 3-Clause
7
 */
8
9
namespace Starlit\Utils\Validation;
10
11
use Symfony\Component\Translation\TranslatorInterface as SymfonyTranslatorInterface;
12
13
/**
14
 * A crude validator.
15
 */
16
class Validator
17
{
18
    /**
19
     * @var array
20
     */
21
    protected static $validRuleProperties = [
22
        'required',
23
        'nonEmpty',
24
        'min',
25
        'max',
26
        'minLength',
27
        'maxLength',
28
        'regexp',
29
        'regexpExpl',
30
        'email',
31
        'textKey',
32
        'date',
33
        'dateTime',
34
        'custom'
35
    ];
36
37
    /**
38
     * @var ValidatorTranslatorInterface
39
     */
40
    protected $translator;
41
42
    /**
43
     * @var array
44
     */
45
    protected $validatedData = [];
46
47
    /**
48
     * @var array
49
     */
50
    protected $fieldsRuleProperties = [];
51
52
    /**
53
     * Constructor.
54
     *
55
     * @param array                                                        $fieldsRuleProperties
56
     * @param ValidatorTranslatorInterface|SymfonyTranslatorInterface|null $translator
57
     */
58 33
    public function __construct(array $fieldsRuleProperties = [], $translator = null)
59
    {
60 33
        $this->fieldsRuleProperties = $fieldsRuleProperties;
61
62 33
        if (!$translator) {
63 1
            $this->translator = new DefaultValidatorTranslator();
64 33
        } elseif ($translator instanceof ValidatorTranslatorInterface) {
65 2
            $this->translator = $translator;
66 33
        } elseif ($translator instanceof SymfonyTranslatorInterface) {
67 32
            $this->translator = new SymfonyTranslatorProxy($translator);
68 32
        } else {
69 1
            throw new \InvalidArgumentException("Translator must implement ValidatorTranslatorInterface");
70
        }
71 33
    }
72
73
    /**
74
     * @param array $newFieldsRuleProperties
75
     */
76 3
    public function addFieldsRuleProperties(array $newFieldsRuleProperties)
77
    {
78 3
        foreach ($newFieldsRuleProperties as $fieldName => $newRuleProperties) {
79 3
            if (isset($this->fieldsRuleProperties[$fieldName])) {
80 1
                $this->fieldsRuleProperties[$fieldName] = array_merge(
81 1
                    $this->fieldsRuleProperties[$fieldName],
82
                    $newRuleProperties
83 1
                );
84 1
            } else {
85 3
                $this->fieldsRuleProperties[$fieldName] = $newRuleProperties;
86
            }
87 3
        }
88 3
    }
89
90
    /**
91
     * @param string $fieldName
92
     */
93 1
    public function removeFieldRuleProperties($fieldName)
94
    {
95 1
        unset($this->fieldsRuleProperties[$fieldName]);
96 1
    }
97
98
    /**
99
     * @param string $fieldName
100
     * @return array
101
     */
102 2
    public function getFieldRuleProperties($fieldName)
103
    {
104 2
        if (isset($this->fieldsRuleProperties[$fieldName])) {
105 2
            return $this->fieldsRuleProperties[$fieldName];
106
        }
107
108 1
        return [];
109
    }
110
111
    /**
112
     * Validate and return error messages (if any).
113
     *
114
     * @param array|null $data The data (e.g. from a form post) to be validated and set
115
     * @return array An array with all (if any) of error messages
116
     */
117 3
    public function validate($data)
118
    {
119 3
        $errorMsgs = [];
120 3
        foreach ($this->fieldsRuleProperties as $fieldName => $ruleProperties) {
121
            // Get value to validate
122 3
            if (isset($data[$fieldName])) {
123 3
                $value = $data[$fieldName];
124
125
                // Trim all values unless explicitly set to not
126 3
                if (is_string($value) && (!isset($ruleProperties['trim']) || $ruleProperties['trim'] === true)) {
127 3
                    $value = trim($value);
128 3
                }
129
            // Don't validate empty values that are not set and not required
130 3
            } elseif (empty($ruleProperties['required']) && empty($ruleProperties['nonEmpty'])) {
131 3
                continue;
132
            } else {
133
                // Empty string as default (not null because that adds complexity to checks)
134 1
                $value = '';
135
            }
136
137
            // Validate value against each of the fields' rules
138 3
            $errorMsg = $this->validateValue($value, $ruleProperties);
139
140
            // If field has any error messages, add to error array, otherwise add value to validated data
141 3
            if ($errorMsg) {
142 1
                $errorMsgs[$fieldName] = $errorMsg;
143 1
            } else {
144 2
                $this->validatedData[$fieldName] = $value;
145
            }
146 3
        }
147
148 3
        return $errorMsgs;
149
    }
150
151
    /**
152
     * @param mixed $value
153
     * @param array $ruleProperties
154
     * @return string|null
155
     */
156 28
    public function validateValue($value, array $ruleProperties)
157
    {
158
        // Field name
159 28
        if (isset($ruleProperties['textKey'])) {
160 1
            $fieldName = $this->translator->trans($ruleProperties['textKey']);
161 1
            unset($ruleProperties['textKey']);
162 1
        } else {
163 27
            $fieldName = $this->translator->trans('errorTheNoneSpecifiedField');
164
        }
165
166
        // Unset rule properties that are not rules
167 28
        $rules = $ruleProperties;
168 28
        unset($rules['textKey']);
169 28
        unset($rules['regexpExpl']);
170 28
        unset($rules['trim']);
171
172 28
        $isValueSet = (is_scalar($value) || is_null($value)) && ((string) $value) !== '';
173
174 28
        foreach ($rules as $rule => $ruleContents) {
175 28
            $errorMsg = null;
176
177
            switch ($rule) {
178 28
                case 'required':
179 6
                    if (!is_bool($ruleContents)) {
180 1
                        throw new \InvalidArgumentException("Invalid required validation rule[{$rule}]");
181
                    }
182
183 5
                    if ($ruleContents && !$isValueSet) {
184 4
                        $errorMsg = $this->translator->trans('errorFieldXIsRequired', ['%field%' => $fieldName]);
185 4
                    }
186
187 5
                    break;
188 23
                case 'nonEmpty':
189 4
                    if (!is_bool($ruleContents)) {
190 1
                        throw new \InvalidArgumentException("Invalid nonEmpty validation rule[{$rule}]");
191
                    }
192
193 3
                    if ($ruleContents && empty($value)) {
194 2
                        $errorMsg = $this->translator->trans('errorFieldXIsRequired', ['%field%' => $fieldName]);
195 2
                    }
196
197 3
                    break;
198 19 View Code Duplication
                case 'min':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
199 2
                    if (!is_numeric($ruleContents)) {
200 1
                        throw new \InvalidArgumentException("Invalid min validation rule[{$ruleContents}]");
201
                    }
202
203 1
                    if (!is_numeric($value) || $value < $ruleContents) {
204
                        $fieldName =
205 1
                        $errorMsg = $this->translator->trans(
206 1
                            'errorFieldMustBeMinNumber',
207 1
                            ['%field%' => $fieldName, '%number%' => $ruleContents]
208 1
                        );
209 1
                    }
210
211 1
                    break;
212 18 View Code Duplication
                case 'max':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
213 2
                    if (!is_numeric($ruleContents)) {
214 1
                        throw new \InvalidArgumentException("Invalid max validation rule[{$ruleContents}]");
215
                    }
216
217 1
                    if (!is_numeric($value) || $value > $ruleContents) {
218 1
                        $errorMsg = $this->translator->trans(
219 1
                            'errorFieldMustBeMaxNumber',
220
                            [
221 1
                                '%field%'  => $fieldName,
222
                                '%number%' => $ruleContents
223 1
                            ]
224 1
                        );
225 1
                    }
226
227 1
                    break;
228 17 View Code Duplication
                case 'minLength':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
229 4
                    if (!is_int($ruleContents) || $ruleContents < 1) {
230 1
                        throw new \InvalidArgumentException("Invalid min length validation rule[{$ruleContents}]");
231
                    }
232
233 3
                    if (mb_strlen($value) < $ruleContents) {
234 1
                        $errorMsg = $this->translator->trans(
235 1
                            'errorFieldMustBeMinXLength',
236 1
                            ['%field%' => $fieldName, '%numberOf%' => $ruleContents]
237 1
                        );
238 1
                    }
239
240 3
                    break;
241 14 View Code Duplication
                case 'maxLength':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
242 2
                    if (!is_int($ruleContents) || $ruleContents < 1) {
243 1
                        throw new \InvalidArgumentException("Invalid max length validation rule[{$ruleContents}]");
244
                    }
245
246 1
                    if ($isValueSet && mb_strlen($value) > $ruleContents) {
247 1
                        $errorMsg = $this->translator->trans(
248 1
                            'errorFieldMustBeMaxXLength',
249 1
                            ['%field%' => $fieldName, '%numberOf%' => $ruleContents]
250 1
                        );
251 1
                    }
252
253 1
                    break;
254 13 View Code Duplication
                case 'length':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
255 2
                    if (!is_int($ruleContents) || $ruleContents < 1) {
256 1
                        throw new \InvalidArgumentException("Invalid length validation rule[{$ruleContents}]");
257
                    }
258
259 1
                    if ($isValueSet && mb_strlen($value) !== $ruleContents) {
260 1
                        $errorMsg = $this->translator->trans(
261 1
                            'errorFieldMustBeXLength',
262 1
                            ['%field%' => $fieldName, '%numberOf%' => $ruleContents]
263 1
                        );
264 1
                    }
265
266 1
                    break;
267 11
                case 'regexp':
268 2
                    if (!$ruleContents) {
269 1
                        throw new \InvalidArgumentException("Invalid regexp validation rule[{$ruleContents}]");
270
                    }
271
272 1
                    if ($isValueSet && !preg_match('/' . $ruleContents . '/', $value)) {
273 1
                        $errorMsg = $this->translator->trans(
274 1
                            'errorFieldInvalidFormat',
275 1
                            ['%field%' => $fieldName]
276 1
                        );
277
278 1
                        if (isset($ruleProperties['regexpExpl'])) {
279 1
                            $errorMsg .= $this->translator->trans(
280 1
                                'errorFieldValidCharactersAreX',
281 1
                                ['%characters%' => $ruleProperties['regexpExpl']]
282 1
                            );
283 1
                        }
284 1
                    }
285
286 1
                    break;
287 10
                case 'email':
288 3
                    if (!is_bool($ruleContents)) {
289 1
                        throw new \InvalidArgumentException("Invalid email validation rule[{$rule}]");
290
                    }
291
292 2
                    if ($ruleContents && $isValueSet && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
293 1
                        $errorMsg = $this->translator->trans('errorInvalidEmail');
294 1
                    }
295
296 2
                    break;
297 8
                case 'date':
298
                    // No break
299 8
                case 'dateTime':
300 5
                    if (!is_bool($ruleContents)) {
301 1
                        throw new \InvalidArgumentException("Invalid date validation rule[{$rule}]");
302
                    }
303
304 4
                    if ($ruleContents && $isValueSet) {
305 4
                        $isValueOk = function ($format) use ($value) {
306 4
                            return (\DateTime::createFromFormat($format, $value) !== false
307 4
                                && !\DateTime::getLastErrors()["warning_count"]
308 4
                                && !\DateTime::getLastErrors()["error_count"]);
309 4
                        };
310
311
                        // Allow datetime with and without seconds
312 4
                        if (($rule === 'date' && !$isValueOk('Y-m-d'))
313 3
                            || ($rule === 'dateTime' && !$isValueOk('Y-m-d H:i')
314 3
                                && !$isValueOk('Y-m-d H:i:s'))
315 4
                        ) {
316 2
                            $errorMsg = $this->translator->trans(
317 2
                                ($rule === 'date') ? 'errorInvalidDate' : 'errorInvalidDateTime'
318 2
                            );
319 2
                        }
320 4
                    }
321
322 4
                    break;
323 3
                case 'custom':
324 2
                    if (!is_callable($ruleContents)) {
325 1
                        throw new \InvalidArgumentException("Invalid custom validation rule[{$rule}]");
326
                    }
327
328 1
                    $errorMsg = $ruleContents($value);
329
330 1
                    break;
331 1
                default:
332 1
                    throw new \InvalidArgumentException("Unknown validation rule[{$rule}]");
333 1
            }
334
335 16
            if ($errorMsg) {
336 9
                return $errorMsg;
337
            }
338 9
        }
339
340 9
        return null;
341
    }
342
343
    /**
344
     * @param string $key
345
     * @param mixed  $default
346
     * @return array|mixed
347
     */
348 1
    public function getValidatedData($key = null, $default = null)
349
    {
350 1
        if ($key !== null) {
351 1
            return isset($this->validatedData[$key]) ? $this->validatedData[$key] : $default;
352
        } else {
353 1
            return $this->validatedData;
354
        }
355
    }
356
357
    /**
358
     * @return array
359
     */
360 1
    public static function getValidRuleProperties()
361
    {
362 1
        return static::$validRuleProperties;
363
    }
364
}
365