Completed
Push — master ( 24f7cd...f01c7e )
by Andres
04:31 queued 02:04
created

AbstractValidator::defaultValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 1
eloc 2
c 2
b 0
f 1
nc 1
nop 1
dl 0
loc 4
rs 10
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
    public function defaultValue($value)
172
    {
173
        $this->defaultValue = $value;
174
    }
175
176
    /**
177
     * Provide conditional validation
178
     *
179
     * @param $conditions
180
     * @return $this
181
     */
182
    public function alternatives($conditions)
183
    {
184
        $this->add(function($value, $nameKey) use($conditions) {
185
            foreach ($conditions as $condition) {
186
187
                $is = $condition['is'];
188
                $is->toBool(true);
189
190
                if (!isset($condition['then'])) {
191
                    $this->createError('alternatives.missing_then', $value, $nameKey);
192
                }
193
194
                if ($is($value)) {
195 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...
196
                        $reflObject = new \ReflectionObject($condition['then']);
197
                        $validationStack = $reflObject->getProperty('validationStack');
198
                        $validationStack->setAccessible(true);
199
                        foreach ($validationStack->getValue($condition['then']) as $validator) {
200
                            $this->validationStack[] = $validator;
201
                        }
202
                    } elseif (!is_null($condition['then'])) {
203
                        return $condition['then'];
204
                    }
205 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...
206
                    if ($condition['else'] instanceof AbstractValidator) {
207
                        $reflObject = new \ReflectionObject($condition['else']);
208
                        $validationStack = $reflObject->getProperty('validationStack');
209
                        $validationStack->setAccessible(true);
210
                        foreach ($validationStack->getValue($condition['else']) as $validator) {
211
                            $this->validationStack[] = $validator;
212
                        }
213
                    } elseif (!is_null($condition['else'])) {
214
                        return $condition['else'];
215
                    }
216
                }
217
            }
218
        });
219
220
        return $this;
221
    }
222
223
    /**
224
     * Create an error with a formatted message
225
     *
226
     * @param string $key
227
     * @param null|string $value
228
     * @param null|string $valueKey
229
     * @throws DiscomfortException
230
     * @throws ValidationException
231
     */
232
    protected function createError($key, $value = null, $valueKey = null)
233
    {
234
        if (!array_key_exists($key, $this->errorHandlers)) {
235
            throw new ValidationException(
236
                $key,
237
                $this->errorHandlers['default']['message']
238
            );
239
        }
240
241
        $errorHandler = $this->errorHandlers[$key];
242
        if (!array_key_exists('message_formatter', $errorHandler)) {
243
            $messageFormatter = function($template, $value) {
244
                return sprintf($template, $value);
245
            };
246
        } else {
247
            $messageFormatter = $errorHandler['message_formatter'];
248
            if (!is_callable($messageFormatter)) {
249
                throw new DiscomfortException('"message_formatter" must be callable');
250
            }
251
        }
252
253
        $templateValue = "'{$value}'";
254
        if (!is_null($valueKey)) {
255
            $templateValue = $valueKey;
256
        }
257
258
        $errorMessage = $messageFormatter(
259
            $errorHandler['message'],
260
            $templateValue ?: $errorHandler['value']
261
        );
262
263
        throw new ValidationException($key, $errorMessage);
264
    }
265
}