1<?php 2 3/* 4 * This file is part of the symfony package. 5 * (c) Fabien Potencier <fabien.potencier@symfony-project.com> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11/** 12 * sfValidatorSchema represents an array of fields. 13 * 14 * A field is a named validator. 15 * 16 * @package symfony 17 * @subpackage validator 18 * @author Fabien Potencier <fabien.potencier@symfony-project.com> 19 * @version SVN: $Id$ 20 */ 21class sfValidatorSchema extends sfValidatorBase implements ArrayAccess 22{ 23 protected 24 $fields = array(), 25 $preValidator = null, 26 $postValidator = null; 27 28 /** 29 * Constructor. 30 * 31 * The first argument can be: 32 * 33 * * null 34 * * an array of named sfValidatorBase instances 35 * 36 * @param mixed $fields Initial fields 37 * @param array $options An array of options 38 * @param array $messages An array of error messages 39 * 40 * @see sfValidatorBase 41 */ 42 public function __construct($fields = null, $options = array(), $messages = array()) 43 { 44 if (is_array($fields)) 45 { 46 foreach ($fields as $name => $validator) 47 { 48 $this[$name] = $validator; 49 } 50 } 51 else if (null !== $fields) 52 { 53 throw new InvalidArgumentException('sfValidatorSchema constructor takes an array of sfValidatorBase objects.'); 54 } 55 56 parent::__construct($options, $messages); 57 } 58 59 /** 60 * Configures the validator. 61 * 62 * Available options: 63 * 64 * * allow_extra_fields: if false, the validator adds an error if extra fields are given in the input array of values (default to false) 65 * * filter_extra_fields: if true, the validator filters extra fields from the returned array of cleaned values (default to true) 66 * 67 * Available error codes: 68 * 69 * * extra_fields 70 * 71 * @param array $options An array of options 72 * @param array $messages An array of error messages 73 * 74 * @see sfValidatorBase 75 */ 76 protected function configure($options = array(), $messages = array()) 77 { 78 $this->addOption('allow_extra_fields', false); 79 $this->addOption('filter_extra_fields', true); 80 81 $this->addMessage('extra_fields', 'Unexpected extra form field named "%field%".'); 82 $this->addMessage('post_max_size', 'The form submission cannot be processed. It probably means that you have uploaded a file that is too big.'); 83 } 84 85 /** 86 * @see sfValidatorBase 87 */ 88 public function clean($values) 89 { 90 return $this->doClean($values); 91 } 92 93 /** 94 * @see sfValidatorBase 95 */ 96 protected function doClean($values) 97 { 98 if (null === $values) 99 { 100 $values = array(); 101 } 102 103 if (!is_array($values)) 104 { 105 throw new InvalidArgumentException('You must pass an array parameter to the clean() method'); 106 } 107 108 $clean = array(); 109 $unused = array_keys($this->fields); 110 $errorSchema = new sfValidatorErrorSchema($this); 111 112 // check that post_max_size has not been reached 113 if (isset($_SERVER['CONTENT_LENGTH']) && (int) $_SERVER['CONTENT_LENGTH'] > $this->getBytes(ini_get('post_max_size')) && ini_get('post_max_size') != 0) 114 { 115 $errorSchema->addError(new sfValidatorError($this, 'post_max_size')); 116 117 throw $errorSchema; 118 } 119 120 // pre validator 121 try 122 { 123 $values = $this->preClean($values); 124 } 125 catch (sfValidatorErrorSchema $e) 126 { 127 $errorSchema->addErrors($e); 128 } 129 catch (sfValidatorError $e) 130 { 131 $errorSchema->addError($e); 132 } 133 134 // validate given values 135 foreach ($values as $name => $value) 136 { 137 // field exists in our schema? 138 if (!array_key_exists($name, $this->fields)) 139 { 140 if (!$this->options['allow_extra_fields']) 141 { 142 $errorSchema->addError(new sfValidatorError($this, 'extra_fields', array('field' => $name))); 143 } 144 else if (!$this->options['filter_extra_fields']) 145 { 146 $clean[$name] = $value; 147 } 148 149 continue; 150 } 151 152 unset($unused[array_search($name, $unused, true)]); 153 154 // validate value 155 try 156 { 157 $clean[$name] = $this->fields[$name]->clean($value); 158 } 159 catch (sfValidatorError $e) 160 { 161 $clean[$name] = null; 162 163 $errorSchema->addError($e, (string) $name); 164 } 165 catch (Exception $e) 166 { 167 $class = get_class($e); 168 169 throw new $class($e->getMessage().' of "'.$name.'" field'); 170 } 171 } 172 173 // are non given values required? 174 foreach ($unused as $name) 175 { 176 // validate value 177 try 178 { 179 $clean[$name] = $this->fields[$name]->clean(null); 180 } 181 catch (sfValidatorError $e) 182 { 183 $clean[$name] = null; 184 185 $errorSchema->addError($e, (string) $name); 186 } 187 } 188 189 // post validator 190 try 191 { 192 $clean = $this->postClean($clean); 193 } 194 catch (sfValidatorErrorSchema $e) 195 { 196 $errorSchema->addErrors($e); 197 } 198 catch (sfValidatorError $e) 199 { 200 $errorSchema->addError($e); 201 } 202 203 if (count($errorSchema)) 204 { 205 throw $errorSchema; 206 } 207 208 return $clean; 209 } 210 211 /** 212 * Cleans the input values. 213 * 214 * This method is the first validator executed by doClean(). 215 * 216 * It executes the validator returned by getPreValidator() 217 * on the global array of values. 218 * 219 * @param array $values The input values 220 * 221 * @return array The cleaned values 222 * 223 * @throws sfValidatorError 224 */ 225 public function preClean($values) 226 { 227 if (null === $validator = $this->getPreValidator()) 228 { 229 return $values; 230 } 231 232 return $validator->clean($values); 233 } 234 235 /** 236 * Cleans the input values. 237 * 238 * This method is the last validator executed by doClean(). 239 * 240 * It executes the validator returned by getPostValidator() 241 * on the global array of cleaned values. 242 * 243 * @param array $values The input values 244 * 245 * @throws sfValidatorError 246 */ 247 public function postClean($values) 248 { 249 if (null === $validator = $this->getPostValidator()) 250 { 251 return $values; 252 } 253 254 return $validator->clean($values); 255 } 256 257 /** 258 * Sets the pre validator. 259 * 260 * @param sfValidatorBase $validator An sfValidatorBase instance 261 * 262 * @return sfValidatorBase The current validator instance 263 */ 264 public function setPreValidator(sfValidatorBase $validator) 265 { 266 $this->preValidator = clone $validator; 267 268 return $this; 269 } 270 271 /** 272 * Returns the pre validator. 273 * 274 * @return sfValidatorBase A sfValidatorBase instance 275 */ 276 public function getPreValidator() 277 { 278 return $this->preValidator; 279 } 280 281 /** 282 * Sets the post validator. 283 * 284 * @param sfValidatorBase $validator An sfValidatorBase instance 285 * 286 * @return sfValidatorBase The current validator instance 287 */ 288 public function setPostValidator(sfValidatorBase $validator) 289 { 290 $this->postValidator = clone $validator; 291 292 return $this; 293 } 294 295 /** 296 * Returns the post validator. 297 * 298 * @return sfValidatorBase An sfValidatorBase instance 299 */ 300 public function getPostValidator() 301 { 302 return $this->postValidator; 303 } 304 305 /** 306 * Returns true if the schema has a field with the given name (implements the ArrayAccess interface). 307 * 308 * @param string $name The field name 309 * 310 * @return bool true if the schema has a field with the given name, false otherwise 311 */ 312 public function offsetExists($name) 313 { 314 return isset($this->fields[$name]); 315 } 316 317 /** 318 * Gets the field associated with the given name (implements the ArrayAccess interface). 319 * 320 * @param string $name The field name 321 * 322 * @return sfValidatorBase The sfValidatorBase instance associated with the given name, null if it does not exist 323 */ 324 public function offsetGet($name) 325 { 326 return isset($this->fields[$name]) ? $this->fields[$name] : null; 327 } 328 329 /** 330 * Sets a field (implements the ArrayAccess interface). 331 * 332 * @param string $name The field name 333 * @param sfValidatorBase $validator An sfValidatorBase instance 334 */ 335 public function offsetSet($name, $validator) 336 { 337 if (!$validator instanceof sfValidatorBase) 338 { 339 throw new InvalidArgumentException('A validator must be an instance of sfValidatorBase.'); 340 } 341 342 $this->fields[$name] = clone $validator; 343 } 344 345 /** 346 * Removes a field by name (implements the ArrayAccess interface). 347 * 348 * @param string $name 349 */ 350 public function offsetUnset($name) 351 { 352 unset($this->fields[$name]); 353 } 354 355 /** 356 * Returns an array of fields. 357 * 358 * @return sfValidatorBase[] An array of sfValidatorBase instances 359 */ 360 public function getFields() 361 { 362 return $this->fields; 363 } 364 365 /** 366 * @see sfValidatorBase 367 */ 368 public function asString($indent = 0) 369 { 370 throw new Exception('Unable to convert a sfValidatorSchema to string.'); 371 } 372 373 public function __clone() 374 { 375 foreach ($this->fields as $name => $field) 376 { 377 $this->fields[$name] = clone $field; 378 } 379 380 if (null !== $this->preValidator) 381 { 382 $this->preValidator = clone $this->preValidator; 383 } 384 385 if (null !== $this->postValidator) 386 { 387 $this->postValidator = clone $this->postValidator; 388 } 389 } 390 391 protected function getBytes($value) 392 { 393 $value = trim($value); 394 $number = (float) $value; 395 $modifier = strtolower($value[strlen($value) - 1]); 396 397 $exp_by_modifier = array( 398 'k' => 1, 399 'm' => 2, 400 'g' => 3, 401 ); 402 403 if (array_key_exists($modifier, $exp_by_modifier)) { 404 $exp = $exp_by_modifier[$modifier]; 405 $number = $number * pow(1024, $exp); 406 } 407 408 return $number; 409 } 410} 411