Completed
Push — master ( 24d5f6...b3c94d )
by Andres
02:29
created

AbstractValidator::optional()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 6
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 = true;
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
            return $value;
83
        }catch (ValidationException $validationException) {
84
            if ($this->toBool) {
85
                return false;
86
            }
87
88
            return ValidationError::fromException($validationException);
89
        }
90
    }
91
92
    public function __call($name, $arguments)
93
    {
94
        return call_user_func_array([$this->comfort, $name], $arguments);
95
    }
96
97
    /**
98
     * Declare the value as being required
99
     *
100
     * @return $this
101
     */
102
    public function required()
103
    {
104
        $this->optional = false;
105
106
        $this->add(function($value, $nameKey) {
107
            if (is_null($value)) {
108
                $this->createError('required', $value, $nameKey);
109
            }
110
        });
111
112
        return $this;
113
    }
114
115
116
    /**
117
     * Declare data is optional
118
     *
119
     * @return $this
120
     */
121
    public function optional()
122
    {
123
        $this->optional = true;
124
125
        return $this;
126
    }
127
128
    /**
129
     * Add adhoc validator to validation stack
130
     *
131
     * @param callable $validation
132
     * @return $this
133
     */
134
    public function add(callable $validation)
135
    {
136
        $this->validationStack[] = $validation;
137
138
        return $this;
139
    }
140
141
    /**
142
     * Validate given value matches any of the provided strings
143
     *
144
     * @param array $vals
145
     * @return $this
146
     */
147
    public function anyOf(array $vals)
148
    {
149
        $this->add(function($value, $nameKey) use ($vals) {
150
            if (!in_array($value, $vals)) {
151
                return $this->createError('anyof', $value, $nameKey);
152
            }
153
        });
154
155
        return $this;
156
    }
157
158
    /**
159
     * On validation failure whether to return false or a validation error
160
     *
161
     * @param bool $val
162
     * @return $this
163
     */
164
    public function toBool($val = true)
165
    {
166
        $this->toBool = (boolean)$val;
167
168
        return $this;
169
    }
170
171
    /**
172
     * Set default value
173
     *
174
     * @param $value
175
     * @return $this
176
     */
177
    public function defaultValue($value)
178
    {
179
        $this->defaultValue = $value;
180
181
        return $this;
182
    }
183
184
    /**
185
     * Provide conditional validation
186
     *
187
     * @param $conditions
188
     * @return $this
189
     */
190
    public function alternatives($conditions)
191
    {
192
        $this->add(function($value, $nameKey) use($conditions) {
193
            foreach ($conditions as $condition) {
194
195
                $is = $condition['is'];
196
                $is->toBool(true);
197
198
                if (!isset($condition['then'])) {
199
                    $this->createError('alternatives.missing_then', $value, $nameKey);
200
                }
201
202
                if ($is($value)) {
203 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...
204
                        $reflObject = new \ReflectionObject($condition['then']);
205
                        $validationStack = $reflObject->getProperty('validationStack');
206
                        $validationStack->setAccessible(true);
207
                        foreach ($validationStack->getValue($condition['then']) as $validator) {
208
                            $this->validationStack[] = $validator;
209
                        }
210
                    } elseif (!is_null($condition['then'])) {
211
                        return $condition['then'];
212
                    }
213 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...
214
                    if ($condition['else'] instanceof AbstractValidator) {
215
                        $reflObject = new \ReflectionObject($condition['else']);
216
                        $validationStack = $reflObject->getProperty('validationStack');
217
                        $validationStack->setAccessible(true);
218
                        foreach ($validationStack->getValue($condition['else']) as $validator) {
219
                            $this->validationStack[] = $validator;
220
                        }
221
                    } elseif (!is_null($condition['else'])) {
222
                        return $condition['else'];
223
                    }
224
                }
225
            }
226
        });
227
228
        return $this;
229
    }
230
231
    /**
232
     * Set error messages
233
     *
234
     * @param array $errorMessages
235
     * @return $this
236
     */
237
    public function errorMessages(array $errorMessages)
238
    {
239
        $errorMessages = array_map(function($errorMessage) {
240
            if (is_string($errorMessage)) {
241
                $errorMessage = ['message' => $errorMessage];
242
            }
243
244
            return $errorMessage;
245
        }, $errorMessages);
246
247
        $this->errorHandlers = array_merge($this->errorHandlers, $errorMessages);
248
        return $this;
249
    }
250
251
    /**
252
     * Create an error with a formatted message
253
     *
254
     * @param string $key
255
     * @param null|string $value
256
     * @param null|string $valueKey
257
     * @param mixed $validationValue
258
     * @throws DiscomfortException
259
     * @throws ValidationException
260
     */
261
    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...
262
    {
263
        if (!array_key_exists($key, $this->errorHandlers)) {
264
            throw new ValidationException(
265
                $key,
266
                $this->errorHandlers['default']['message']
267
            );
268
        }
269
270
        $errorHandler = $this->errorHandlers[$key];
271
        if (!array_key_exists('message_formatter', $errorHandler)) {
272
            $messageFormatter = function($template, $value, $validationValue = null) {
273
                return sprintf($template, $value, $validationValue);
274
            };
275
        } else {
276
            $messageFormatter = $errorHandler['message_formatter'];
277
            if (!is_callable($messageFormatter)) {
278
                throw new DiscomfortException('"message_formatter" must be callable');
279
            }
280
        }
281
282
        if (!is_null($valueKey)) {
283
            $templateValue = $valueKey;
284
        } else {
285
            if (!is_string($value)) {
286
                $valueType = gettype($value);
287
                $templateValue = "'{$valueType}'";
288
            } else {
289
                $templateValue = "'{$value}'";
290
            }
291
        }
292
293
        $errorMessage = $messageFormatter(
294
            $errorHandler['message'],
295
            $templateValue ?: $errorHandler['value']
296
        );
297
298
        throw new ValidationException($key, $errorMessage);
299
    }
300
}