Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
86 / 86 |
|
100.00% |
15 / 15 |
CRAP | |
100.00% |
1 / 1 |
Field | |
100.00% |
86 / 86 |
|
100.00% |
15 / 15 |
43 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
required | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
isRequired | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
appendFilter | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
prependFilter | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
addFilter | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
addFiltersFromArray | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
appendValidator | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
prependValidator | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
addValidator | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
addValidatorsFromArray | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
addFilterOrValidator | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
7 | |||
filter | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
6 | |||
validate | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
8 | |||
getErrors | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace Verja; |
4 | |
5 | use Verja\Exception\FilterNotFound; |
6 | use Verja\Exception\InvalidValue; |
7 | use Verja\Exception\NotFound; |
8 | use Verja\Exception\ValidatorNotFound; |
9 | |
10 | class 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 | } |