Completed
Push — master ( 486e29...bb0168 )
by Andres
02:22
created

AbstractValidator::anyOf()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 10
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 2
eloc 5
c 1
b 0
f 1
nc 1
nop 1
dl 10
loc 10
rs 9.4285
1
<?php
2
namespace Comfort\Validator;
3
4
use Comfort\Comfort;
5
use Comfort\Error;
6
use Comfort\Exception\DiscomfortException;
7
use Comfort\Exception\ValidationException;
8
use Comfort\ValidationError;
9
10
/**
11
 * Class AbstractValidator
12
 * @package Comfort\Validator
13
 */
14
abstract class AbstractValidator
15
{
16
    /**
17
     * @var \Closure[]
18
     */
19
    protected $validationStack = [];
20
    /**
21
     * @var Comfort
22
     */
23
    private $comfort;
24
    /**
25
     * @var boolean
26
     */
27
    private $toBool = false;
28
    /**
29
     * @var bool
30
     */
31
    private $optional = true;
32
    /**
33
     * @var mixed
34
     */
35
    private $defaultValue;
36
    /**
37
     * @var array
38
     */
39
    protected $errorHandlers = [
40
        'default' => [
41
            'message' => 'There was a validation error'
42
        ],
43
        'required' => [
44
            'message' => '%s is required',
45
            'default' => 'value'
46
        ]
47
    ];
48
49
    public function __construct(Comfort $comfort)
50
    {
51
52
        $this->comfort = $comfort;
53
    }
54
55
    /**
56
     * Execute validation stack
57
     *
58
     * @param mixed $value
59
     * @param null|string $key
60
     * @return bool|ValidationError
61
     */
62
    public function __invoke($value, $key = null)
63
    {
64
        if (is_null($value) && $this->optional) {
65
            if (is_null($this->defaultValue)) {
66
                return;
67
            } else {
68
                $value = $this->defaultValue;
69
            }
70
        }
71
72
        try {
73
            reset($this->validationStack);
74
75
            do {
76
                /** @var callable $validator */
77
                $validator = current($this->validationStack);
78
                $retVal = $validator($value, $key);
79
                $value = $retVal === null ? $value : $retVal;
80
            } while (next($this->validationStack));
81
82
            if ($this->toBool) {
83
                return true;
84
            }
85
86
            return $value;
87
        } catch (ValidationException $validationException) {
88
            if ($this->toBool) {
89
                return false;
90
            }
91
92
            return ValidationError::fromException($validationException);
93
        }
94
    }
95
96
    public function __call($name, $arguments)
97
    {
98
        return call_user_func_array([$this->comfort, $name], $arguments);
99
    }
100
101
    /**
102
     * Declare the value as being required
103
     *
104
     * @return $this
105
     */
106
    public function required()
107
    {
108
        $this->optional = false;
109
110
        $this->add(function ($value, $nameKey) {
111
            if (is_null($value)) {
112
                $this->createError('required', $value, $nameKey);
113
            }
114
        });
115
116
117
        return $this;
118
    }
119
120
121
    /**
122
     * Declare data is optional
123
     *
124
     * @return $this
125
     */
126
    public function optional()
127
    {
128
        $this->optional = true;
129
130
        return $this;
131
    }
132
133
    /**
134
     * Add adhoc validator to validation stack
135
     *
136
     * @param callable $validation
137
     * @return $this
138
     */
139
    public function add(callable $validation)
140
    {
141
        $this->validationStack[] = $validation;
142
143
        return $this;
144
    }
145
146
    /**
147
<<<<<<< HEAD
148
=======
149
     * Validate given value matches any of the provided strings
150
     *
151
     * @param array $vals
152
     * @return $this
153
     */
154 View Code Duplication
    public function anyOf(array $vals)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
155
    {
156
        $this->add(function ($value, $nameKey) use ($vals) {
157
            if (!in_array($value, $vals)) {
158
                return $this->createError('anyof', $value, $nameKey);
159
            }
160
        });
161
162
        return $this;
163
    }
164
165
    /**
166
>>>>>>> feature/phpcs
167
     * On validation failure whether to return false or a validation error
168
     *
169
     * @param bool $val
170
     * @return $this
171
     */
172
    public function toBool($val = true)
173
    {
174
        $this->toBool = (boolean)$val;
175
176
        return $this;
177
    }
178
179
    /**
180
     * Set default value
181
     *
182
     * @param $value
183
     * @return $this
184
     */
185
    public function defaultValue($value)
186
    {
187
        $this->defaultValue = $value;
188
189
        return $this;
190
    }
191
192
    /**
193
     * Provide conditional validation
194
     *
195
     * @param $conditions
196
     * @return $this
197
     */
198
    public function alternatives($conditions)
199
    {
200
        $this->add(function ($value, $nameKey) use ($conditions) {
201
202
            foreach ($conditions as $condition) {
203
                if (!isset($condition['is'])) {
204
                    return $this->createError('alternatives.missing_is', $value, $nameKey);
205
                }
206
207
                if (!$condition['is'] instanceof AbstractValidator) {
208
                    return $this->createError('alternatives.invalid_is', $value, $nameKey);
209
                }
210
211
                /** @var AbstractValidator $is */
212
                $is = $condition['is'];
213
                $is->toBool(true);
214
215
                if (!isset($condition['then'])) {
216
                    return $this->createError('alternatives.missing_then', $value, $nameKey);
217
                }
218
219
                if ($is($value)) {
220 View Code Duplication
                    if ($condition['then'] instanceof AbstractValidator) {
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...
221
                        $validationStack = $condition['then']->validationStack;
222
                        foreach ($validationStack as $validator) {
223
                            $this->validationStack[] = $validator;
224
                        }
225
                    } elseif (!is_null($condition['then'])) {
226
                        return $condition['then'];
227
                    }
228 View Code Duplication
                } elseif (isset($condition['else'])) {
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
                    if ($condition['else'] instanceof AbstractValidator) {
230
                        $validationStack = $condition['else']->validationStack;
231
                        foreach ($validationStack as $validator) {
232
                            $this->validationStack[] = $validator;
233
                        }
234
                    } elseif (!is_null($condition['else'])) {
235
                        return $condition['else'];
236
                    }
237
                }
238
            }
239
        });
240
241
        return $this;
242
    }
243
244
    /**
245
     * Set error messages
246
     *
247
     * @param array $errorMessages
248
     * @return $this
249
     */
250
    public function errorMessages(array $errorMessages)
251
    {
252
        $errorMessages = array_map(function ($errorMessage) {
253
            if (is_string($errorMessage)) {
254
                $errorMessage = ['message' => $errorMessage];
255
            }
256
257
            return $errorMessage;
258
        }, $errorMessages);
259
260
        $this->errorHandlers = array_merge($this->errorHandlers, $errorMessages);
261
        return $this;
262
    }
263
264
    /**
265
     * Create an error with a formatted message
266
     *
267
     * @param string $key
268
     * @param null|string $value
269
     * @param null|string $valueKey
270
     * @param mixed $validationValue
271
     * @throws DiscomfortException
272
     * @throws ValidationException
273
     */
274
    protected function createError($key, $value = null, $valueKey = null, $validationValue = null)
0 ignored issues
show
Unused Code introduced by
The parameter $validationValue is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
275
    {
276
        if (!array_key_exists($key, $this->errorHandlers)) {
277
            throw new ValidationException(
278
                $key,
279
                $this->errorHandlers['default']['message']
280
            );
281
        }
282
283
        $errorHandler = $this->errorHandlers[$key];
284
        if (!array_key_exists('message_formatter', $errorHandler)) {
285
            $messageFormatter = function ($template, $value, $validationValue = null) {
286
                return sprintf($template, $value, $validationValue);
287
            };
288
        } else {
289
            $messageFormatter = $errorHandler['message_formatter'];
290
            if (!is_callable($messageFormatter)) {
291
                throw new DiscomfortException('"message_formatter" must be callable');
292
            }
293
        }
294
295
        if (!is_null($valueKey)) {
296
            $templateValue = $valueKey;
297
        } else {
298
            if (!is_string($value)) {
299
                $valueType = gettype($value);
300
                $templateValue = "'{$valueType}'";
301
            } else {
302
                $templateValue = "'{$value}'";
303
            }
304
        }
305
306
        $errorMessage = $messageFormatter(
307
            $errorHandler['message'],
308
            $templateValue ?: $errorHandler['value']
309
        );
310
311
        throw new ValidationException($key, $errorMessage);
312
    }
313
}
314