1 <?php
2
3 /**
4 * The MIT License
5 *
6 * Copyright 2014 George Marques <george at georgemarques.com.br>.
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 * THE SOFTWARE.
25 */
26
27 namespace Flikore\Validator;
28
29 use Flikore\Validator\Exception\ValidatorException;
30
31 /**
32 * A set of validation rules to be checked at the same input set.
33 *
34 * @author George Marques <george at georgemarques.com.br>
35 * @version 0.5.2
36 * @since 0.1
37 * @license http://opensource.org/licenses/MIT MIT
38 * @copyright (c) 2014, George Marques
39 * @package Flikore\Validator
40 */
41 class ValidationSet implements Interfaces\IValidator
42 {
43
44 /**
45 * An array of validation objects.
46 * @var Validator[][] An array of validation objects.
47 */
48 protected $validators;
49
50 /**
51 * Stores the key-value pairs for the inner validators.
52 * @var array Stores the key-value pairs for the inner validators.
53 */
54 protected $values = array();
55
56 /**
57 * Creates a new validation set.
58 * @param array $rules An array of rules with the key being the name of the attribute
59 * and the value being a Validator instance.
60 * @param array $labels The association of name => label to show in error messages.
61 */
62 public function __construct($rules = array(), $labels = array())
63 {
64 foreach ($rules as $name => $rule)
65 {
66 $label = isset($labels[$name]) ? $labels[$name] : null;
67 if (is_array($rule))
68 {
69 $this->addRules($name, $rule, $label);
70 }
71 else
72 {
73 $this->addRule($name, $rule, $label);
74 }
75 }
76 }
77
78 /**
79 * Adds a new rule for a given property or key name.
80 * @param string $name The name of the key or property.
81 * @param Validator $rule The validation rule.
82 * @param string $label The label to be shown in the error message (intead of the name).
83 */
84 public function addRule($name, $rule, $label = null)
85 {
86 if ($label === null)
87 {
88 $label = $name;
89 }
90 $rule->addKeyValue('key', $label);
91 $this->validators[$name][] = $rule;
92 $this->updateSingleKeyValue($rule);
93 }
94
95 /**
96 * Adds a new set rules for a given property or key name.
97 * @param string $name The name of the key or property.
98 * @param Validator[] $rules The array of rules.
99 * @param string $label The label to be shown in the error message (intead of the name).
100 */
101 public function addRules($name, $rules, $label = null)
102 {
103 foreach ($rules as $rule)
104 {
105 $this->addRule($name, $rule, $label);
106 }
107 }
108
109 /**
110 * Gets the set of rules prepared for a given key.
111 *
112 * @param string $key The key to get.
113 * @return array The array with the rules (may be emtpy).
114 */
115 public function getRulesFor($key)
116 {
117 return isset($this->validators[$key]) ? $this->validators[$key] : array();
118 }
119
120 /**
121 * Gets all the rules in this set.
122 *
123 * @return array The collection of rules by fields.
124 */
125 public function getAllRules()
126 {
127 return $this->validators;
128 }
129
130 /**
131 * Checks if the object or array passes all the validation tests.
132 *
133 * @param mixed $object The object or array to test.
134 * @param array $fields A list of fields to check, ignoring the others.
135 * @return boolean Whether it passes the tests or not.
136 * @throws \InvalidArgumentException If the fields parameter is not string nor array.
137 * @throws \OutOfBoundsException If there's a rule for a key that is not set.
138 */
139 public function validate($object, $fields = null)
140 {
141 $fields = $this->getFields($fields);
142
143 foreach ($fields as $att)
144 {
145 // This ignores the input fields that haven't any rules.
146 if(! isset($this->validators[$att]))
147 {
148 continue;
149 }
150
151 $rules = $this->validators[$att];
152 $value = $this->getKeyValue($object, $att);
153
154 foreach ($rules as $rule)
155 {
156 if ($rule instanceof ValidationValue)
157 {
158 $rule = $this->getRealValidator($rule, $object);
159 }
160 if (!$rule->validate($value))
161 {
162 return false;
163 }
164 }
165 }
166 return true;
167 }
168
169 /**
170 * Tests whether the given object or array pass all the given rules.
171 *
172 * @param mixed $object The object or array to test.
173 * @param array $fields A list of fields to check, ignoring the others.
174 * @throws ValidatorException If there's a validation error.
175 * @throws \OutOfBoundsException If there's a rule for a key that is not set.
176 * @throws \InvalidArgumentException If the fields parameter is not string nor array.
177 */
178 public function assert($object, $fields = null)
179 {
180 $fields = $this->getFields($fields);
181
182 $exceptions = array();
183 foreach ($fields as $att)
184 {
185 // This ignores the input fields that haven't any rules.
186 if(! isset($this->validators[$att]))
187 {
188 continue;
189 }
190
191 $value = $this->getKeyValue($object, $att);
192 $rules = $this->validators[$att];
193
194 foreach ($rules as $rule)
195 {
196 if ($rule instanceof ValidationValue)
197 {
198 $rule = $this->getRealValidator($rule, $object);
199 }
200 try
201 {
202 $rule->assert($value, $att);
203 }
204 catch (ValidatorException $e)
205 {
206 $exceptions[$att] = $e;
207 break;
208 }
209 }
210 }
211 if (!empty($exceptions))
212 {
213 $e = new ValidatorException();
214 $e->setErrors($exceptions);
215 throw $e;
216 }
217 }
218
219 /**
220 * Adds a new key-value pair to be replaced by the templating engine.
221 * This does not check if it's replacing a specific validator value.
222 *
223 * @param string $key The key to replace (in the template as "%key%")
224 * @param string $value The value to be inserted instead of the key.
225 */
226 public function addKeyValue($key, $value)
227 {
228 if (is_object($value) && !method_exists($value, '__toString'))
229 {
230 $value = get_class($value);
231 }
232 $this->values[$key] = (string) $value;
233 $this->updateKeyValues();
234 }
235
236 /**
237 * Gets a value for a given key in an object or array.
238 * @param mixed $object The object or array.
239 * @param string $key The name of the key or property.
240 * @return mixed The value.
241 * @throws \OutOfBoundsException
242 */
243 protected function getKeyValue($object, $key)
244 {
245 if (is_array($object) || ($object instanceof \ArrayAccess))
246 {
247 if (!isset($object[$key]))
248 {
249 if (!($object instanceof \ArrayAccess))
250 {
251 throw new \OutOfBoundsException(sprintf(Intl\GetText::_d('Flikore.Validator', 'The key %s does not exist.'), $key));
252 }
253 }
254 else
255 {
256 return $object[$key];
257 }
258 }
259
260 if (is_object($object))
261 {
262 try
263 {
264 $prop = new \ReflectionProperty($object, $key);
265 }
266 catch (\ReflectionException $e)
267 {
268 throw new \OutOfBoundsException(sprintf(Intl\GetText::_d('Flikore.Validator', 'The property %s does not exist.'), $key), 0, $e);
269 }
270 $prop->setAccessible(true);
271 return $prop->getValue($object);
272 }
273 throw new \InvalidArgumentException('The value to validate must be an array or an object.');
274 }
275
276 /**
277 * Generates a validator object based on a ValidationValue.
278 *
279 * @param ValidationValue $validationValue The value to use as generator.
280 * @param mixed $object The object or array being validated.
281 */
282 protected function getRealValidator($validationValue, $object)
283 {
284 $needed = $validationValue->getFields();
285 $values = array();
286 foreach ($needed as $key)
287 {
288 $values[$key] = $this->getKeyValue($object, $key);
289 }
290 return $validationValue->createRule($values);
291 }
292
293 /**
294 * Updates the inner key-value pairs to reflect this object's values.
295 */
296 protected function updateKeyValues()
297 {
298 if (!empty($this->validators))
299 {
300 foreach ($this->validators as $rules)
301 {
302 foreach ($rules as $rule)
303 {
304 $this->updateSingleKeyValue($rule);
305 }
306 }
307 }
308 }
309
310 /**
311 * Updates a single validator to have the same key-values added to the set.
312 *
313 * @param Interfaces\IValidator $rule The rule to update
314 */
315 protected function updateSingleKeyValue($rule)
316 {
317 foreach ($this->values as $key => $value)
318 {
319 $rule->addKeyValue($key, $value);
320 }
321 }
322
323 /**
324 * Gets the fields from the input. As function to avoid repeat on validate and assert.
325 *
326 * @param string|array $fields The input fields.
327 * @throws \InvalidArgumentException If the fields parameter is not string nor array.
328 */
329 protected function getFields($fields)
330 {
331 if ($fields === null)
332 {
333 $fields = array_keys($this->validators);
334 }
335 elseif (is_string($fields) || is_int($fields))
336 {
337 $fields = array($fields);
338 }
339 elseif (!is_array($fields))
340 {
341 throw new \InvalidArgumentException(sprintf(
342 Intl\GetText::_d('Flikore.Validator', 'The argument "%s" must be either %s, %s or %s.'),
343 'fields', Intl\GetText::_d('Flikore.Validator','a string'), Intl\GetText::_d('Flikore.Validator','an array'), Intl\GetText::_d('Flikore.Validator','an integer')));
344 }
345 return $fields;
346 }
347
348 }
349