Completed
Pull Request — master (#28)
by Andres
02:15
created

AbstractValidator::map()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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