Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
86 / 86
100.00% covered (success)
100.00%
15 / 15
CRAP
100.00% covered (success)
100.00%
1 / 1
Field
100.00% covered (success)
100.00%
86 / 86
100.00% covered (success)
100.00%
15 / 15
43
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 required
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isRequired
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 appendFilter
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 prependFilter
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addFilter
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 addFiltersFromArray
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 appendValidator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 prependValidator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addValidator
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 addValidatorsFromArray
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 addFilterOrValidator
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
7
 filter
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
6
 validate
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
8
 getErrors
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Verja;
4
5use Verja\Exception\FilterNotFound;
6use Verja\Exception\InvalidValue;
7use Verja\Exception\NotFound;
8use Verja\Exception\ValidatorNotFound;
9
10class Field
11{
12    /** @var FilterInterface[] */
13    protected $filters = [];
14
15    /** @var ValidatorInterface[] */
16    protected $validators = [];
17
18    /** @var array */
19    protected $filterCache = [];
20
21    /** @var array */
22    protected $validationCache = [];
23
24    /** @var array */
25    protected $errors;
26
27    /** @var bool */
28    protected $required = false;
29
30    /** @var bool */
31    protected $filterFailed = false;
32
33    /**
34     * Field constructor.
35     *
36     * Adds filters and validators given in $definitions in the exact order.
37     *
38     * @param array $definitions
39     */
40    public function __construct(array $definitions = [])
41    {
42        $p = array_search('required', $definitions);
43        if ($p !== false) {
44            array_splice($definitions, $p, 1);
45            $this->required();
46        }
47
48        foreach ($definitions as $definition) {
49            $this->addFilterOrValidator($definition);
50        }
51    }
52
53    /**
54     * Set required flag
55     *
56     * @param bool $required
57     * @return $this
58     */
59    public function required($required = true)
60    {
61        $this->required = $required;
62        return $this;
63    }
64
65    /**
66     * @return bool
67     */
68    public function isRequired()
69    {
70        return $this->required;
71    }
72
73    /**
74     * Append $filter to the list of filters
75     *
76     * @param FilterInterface|string $filter
77     * @return $this
78     */
79    public function appendFilter($filter)
80    {
81        return $this->addFilter($filter, false);
82    }
83
84    /**
85     * Prepend $filter to the list of filters
86     *
87     * @param FilterInterface|string $filter
88     * @return $this
89     */
90    public function prependFilter($filter)
91    {
92        return $this->addFilter($filter, true);
93    }
94
95    /**
96     * Add $filter to the list of filters
97     *
98     * Appends by default prepends when $prepend == true
99     *
100     * @param FilterInterface|string $filter
101     * @param bool $prepend
102     * @return $this
103     */
104    public function addFilter($filter, $prepend = false)
105    {
106        $filter = Filter::getFilter($filter);
107
108        if (count($this->filterCache) > 0) {
109            $this->filterCache = [];
110        }
111
112        if ($prepend) {
113            array_unshift($this->filters, $filter->assign($this));
114        } else {
115            array_push($this->filters, $filter->assign($this));
116        }
117
118        return $this;
119    }
120
121    /**
122     * Add Filters from $filterDefinitions
123     *
124     * Returns an array of Classes that where not found.
125     *
126     * @param array $filterDefinitions
127     * @return array
128     */
129    public function addFiltersFromArray(array $filterDefinitions)
130    {
131        $notFound = [];
132
133        foreach ($filterDefinitions as $filterDefinition) {
134            try {
135                $this->addFilter($filterDefinition);
136            } catch (FilterNotFound $exception) {
137                $notFound[$exception->getFilter()] = $filterDefinition;
138            }
139        }
140
141        return $notFound;
142    }
143
144    /**
145     * Append $validator to the list of validators
146     *
147     * @param ValidatorInterface|string $validator
148     * @return $this
149     */
150    public function appendValidator($validator)
151    {
152        return $this->addValidator($validator, false);
153    }
154
155    /**
156     * Prepend $validator to the list of validators
157     *
158     * @param ValidatorInterface|string $validator
159     * @return $this
160     */
161    public function prependValidator($validator)
162    {
163        return $this->addValidator($validator, true);
164    }
165
166    /**
167     * Add $validator to the list of filters
168     *
169     * Appends by default prepends when $prepend == true
170     *
171     * @param ValidatorInterface|string|callable $validator
172     * @param bool                               $prepend
173     * @return $this
174     */
175    public function addValidator($validator, $prepend = false)
176    {
177        $validator = Validator::getValidator($validator);
178
179        if (count($this->validationCache) > 0) {
180            $this->validationCache = [];
181        }
182
183        if ($prepend) {
184            array_unshift($this->validators, $validator->assign($this));
185        } else {
186            array_push($this->validators, $validator->assign($this));
187        }
188        return $this;
189    }
190
191    /**
192     * Add Filters from $validatorDefinitions
193     *
194     * Returns an array of Classes that where not found.
195     *
196     * @param array $validatorDefinitions
197     * @return array
198     */
199    public function addValidatorsFromArray(array $validatorDefinitions)
200    {
201        $notFound = [];
202
203        foreach ($validatorDefinitions as $validatorDefinition) {
204            try {
205                $this->addValidator($validatorDefinition, false);
206            } catch (ValidatorNotFound $exception) {
207                $notFound[$exception->getValidator()] = $validatorDefinition;
208            }
209        }
210
211        return $notFound;
212    }
213
214    /**
215     * Add a filter or validator
216     *
217     * @param FilterInterface|ValidatorInterface|string $definition
218     * @return $this
219     * @throws NotFound
220     */
221    public function addFilterOrValidator($definition)
222    {
223        if ($definition instanceof FilterInterface) {
224            $this->addFilter($definition);
225        } elseif ($definition instanceof ValidatorInterface) {
226            $this->addValidator($definition);
227        } elseif (is_callable($definition) && !is_string($definition)) {
228            $this->addValidator($definition);
229        } else {
230            try {
231                $this->addFilter($definition);
232            } catch (FilterNotFound $exception) {
233                try {
234                    $this->addValidator($definition);
235                } catch (ValidatorNotFound $exception) {
236                    throw new NotFound(sprintf(
237                        'No filter or validator named \'%s\' found',
238                        $exception->getValidator()
239                    ));
240                }
241            }
242        }
243
244        return $this;
245    }
246
247    /**
248     * Filter $value with predefined filters
249     *
250     * Some filters may need context pass it with $context.
251     *
252     * @param mixed $value
253     * @param array $context
254     * @return mixed
255     */
256    public function filter($value, array $context = [])
257    {
258        try {
259            $hash = md5(serialize([$value, $context]));
260            if (isset($this->filterCache[$hash])) {
261                return $this->filterCache[$hash];
262            }
263        } catch (\Exception $exception) {
264        }
265
266        $this->filterFailed = false;
267        foreach ($this->filters as $filter) {
268            try {
269                $value = $filter->filter($value, $context);
270            } catch (InvalidValue $e) {
271                $this->filterFailed = true;
272                $this->errors = $e->errors;
273                break;
274            }
275        }
276
277        if (isset($hash)) {
278            $this->filterCache[$hash] = $value;
279        }
280        return $value;
281    }
282
283    /**
284     * Validate $value with predefined validators
285     *
286     * Some validators may need context pass it with $context.
287     *
288     * @param mixed $value
289     * @param array $context
290     * @return bool
291     */
292    public function validate($value, array $context = [])
293    {
294        $filtered = $this->filter($value, $context);
295
296        if ($this->filterFailed) {
297            return false;
298        }
299
300        try {
301            $hash = md5(serialize([$filtered, $context]));
302            if (isset($this->validationCache[$hash])) {
303                return $this->validationCache[$hash];
304            }
305        } catch (\Exception $exception) {
306            // we catch the exception when the value or context can not be serialized
307        }
308
309
310        $valid = true;
311        $this->errors = [];
312        foreach ($this->validators as $validator) {
313            if (!$validator->validate($filtered, $context)) {
314                $valid = false;
315                if ($error = $validator->getError()) {
316                    $this->errors[] = $error;
317                }
318            }
319        }
320
321        if (isset($hash)) {
322            $this->validationCache[$hash] = $valid;
323        }
324        return $valid;
325    }
326
327    /**
328     * @return Error[]
329     */
330    public function getErrors()
331    {
332        return $this->errors;
333    }
334}