Файл: onlinepoisk.wm-scripts.ru/vendor/AR/lib/Validations.php
Строк: 847
<?php
/**
* These two classes have been <i>heavily borrowed</i> from Ruby on Rails' ActiveRecord so much that
* this piece can be considered a straight port. The reason for this is that the vaildation process is
* tricky due to order of operations/events. The former combined with PHP's odd typecasting means
* that it was easier to formulate this piece base on the rails code.
*
* @package ActiveRecord
*/
namespace ActiveRecord;
use ActiveRecordModel;
use IteratorAggregate;
use ArrayIterator;
/**
* Manages validations for a {@link Model}.
*
* This class isn't meant to be directly used. Instead you define
* validators thru static variables in your {@link Model}. Example:
*
* <code>
* class Person extends ActiveRecordModel {
* static $validates_length_of = array(
* array('name', 'within' => array(30,100),
* array('state', 'is' => 2)
* );
* }
*
* $person = new Person();
* $person->name = 'Tito';
* $person->state = 'this is not two characters';
*
* if (!$person->is_valid())
* print_r($person->errors);
* </code>
*
* @package ActiveRecord
* @see Errors
* @link http://www.phpactiverecord.org/guides/validations
*/
class Validations
{
private $model;
private $options = array();
private $validators = array();
private $record;
private static $VALIDATION_FUNCTIONS = array(
'validates_presence_of',
'validates_size_of',
'validates_length_of',
'validates_inclusion_of',
'validates_exclusion_of',
'validates_format_of',
'validates_numericality_of',
'validates_uniqueness_of'
);
private static $DEFAULT_VALIDATION_OPTIONS = array(
'on' => 'save',
'allow_null' => false,
'allow_blank' => false,
'message' => null,
);
private static $ALL_RANGE_OPTIONS = array(
'is' => null,
'within' => null,
'in' => null,
'minimum' => null,
'maximum' => null,
);
private static $ALL_NUMERICALITY_CHECKS = array(
'greater_than' => null,
'greater_than_or_equal_to' => null,
'equal_to' => null,
'less_than' => null,
'less_than_or_equal_to' => null,
'odd' => null,
'even' => null
);
/**
* Constructs a {@link Validations} object.
*
* @param Model $model The model to validate
* @return Validations
*/
public function __construct(Model $model)
{
$this->model = $model;
$this->record = new Errors($this->model);
$this->validators = array_intersect(array_keys(Reflections::instance()->get(get_class($this->model))->getStaticProperties()), self::$VALIDATION_FUNCTIONS);
}
/**
* Returns validator data.
*
* @return array
*/
public function rules()
{
$data = array();
$reflection = Reflections::instance()->get(get_class($this->model));
foreach ($this->validators as $validate)
{
$attrs = $reflection->getStaticPropertyValue($validate);
foreach ($attrs as $attr)
{
$field = $attr[0];
if (!isset($data[$field]) || !is_array($data[$field]))
$data[$field] = array();
$attr['validator'] = $validate;
unset($attr[0]);
array_push($data[$field],$attr);
}
}
return $data;
}
/**
* Runs the validators.
*
* @return Errors the validation errors if any
*/
public function validate()
{
$reflection = Reflections::instance()->get(get_class($this->model));
foreach ($this->validators as $validate)
$this->$validate($reflection->getStaticPropertyValue($validate));
$this->record->clear_model();
return $this->record;
}
/**
* Validates a field is not null and not blank.
*
* <code>
* class Person extends ActiveRecordModel {
* static $validates_presence_of = array(
* array('first_name'),
* array('last_name')
* );
* }
* </code>
*
* Available options:
*
* <ul>
* <li><b>message:</b> custom error message</li>
* </ul>
*
* @param array $attrs Validation definition
*/
public function validates_presence_of($attrs)
{
$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('message' => Errors::$DEFAULT_ERROR_MESSAGES['blank'], 'on' => 'save'));
foreach ($attrs as $attr)
{
$options = array_merge($configuration, $attr);
$this->record->add_on_blank($options[0], $options['message']);
}
}
/**
* Validates that a value is included the specified array.
*
* <code>
* class Car extends ActiveRecordModel {
* static $validates_inclusion_of = array(
* array('fuel_type', 'in' => array('hyrdogen', 'petroleum', 'electric')),
* );
* }
* </code>
*
* Available options:
*
* <ul>
* <li><b>in/within:</b> attribute should/shouldn't be a value within an array</li>
* <li><b>message:</b> custome error message</li>
* </ul>
*
* @param array $attrs Validation definition
*/
public function validates_inclusion_of($attrs)
{
$this->validates_inclusion_or_exclusion_of('inclusion', $attrs);
}
/**
* This is the opposite of {@link validates_include_of}.
*
* @param array $attrs Validation definition
* @see validates_inclusion_of
*/
public function validates_exclusion_of($attrs)
{
$this->validates_inclusion_or_exclusion_of('exclusion', $attrs);
}
/**
* Validates that a value is in or out of a specified list of values.
*
* @see validates_inclusion_of
* @see validates_exclusion_of
* @param string $type Either inclusion or exclusion
* @param $attrs Validation definition
*/
public function validates_inclusion_or_exclusion_of($type, $attrs)
{
$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('message' => Errors::$DEFAULT_ERROR_MESSAGES[$type], 'on' => 'save'));
foreach ($attrs as $attr)
{
$options = array_merge($configuration, $attr);
$attribute = $options[0];
$var = $this->model->$attribute;
if (isset($options['in']))
$enum = $options['in'];
elseif (isset($options['within']))
$enum = $options['within'];
if (!is_array($enum))
array($enum);
$message = str_replace('%s', $var, $options['message']);
if ($this->is_null_with_option($var, $options) || $this->is_blank_with_option($var, $options))
continue;
if (('inclusion' == $type && !in_array($var, $enum)) || ('exclusion' == $type && in_array($var, $enum)))
$this->record->add($attribute, $message);
}
}
/**
* Validates that a value is numeric.
*
* <code>
* class Person extends ActiveRecordModel {
* static $validates_numericality_of = array(
* array('salary', 'greater_than' => 19.99, 'less_than' => 99.99)
* );
* }
* </code>
*
* Available options:
*
* <ul>
* <li><b>only_integer:</b> value must be an integer (e.g. not a float)</li>
* <li><b>even:</b> must be even</li>
* <li><b>odd:</b> must be odd"</li>
* <li><b>greater_than:</b> must be greater than specified number</li>
* <li><b>greater_than_or_equal_to:</b> must be greater than or equal to specified number</li>
* <li><b>equal_to:</b> ...</li>
* <li><b>less_than:</b> ...</li>
* <li><b>less_than_or_equal_to:</b> ...</li>
* </ul>
*
* @param array $attrs Validation definition
*/
public function validates_numericality_of($attrs)
{
$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('only_integer' => false));
// Notice that for fixnum and float columns empty strings are converted to nil.
// Validates whether the value of the specified attribute is numeric by trying to convert it to a float with Kernel.Float
// (if only_integer is false) or applying it to the regular expression /A[+-]?d+Z/ (if only_integer is set to true).
foreach ($attrs as $attr)
{
$options = array_merge($configuration, $attr);
$attribute = $options[0];
$var = $this->model->$attribute;
$numericalityOptions = array_intersect_key(self::$ALL_NUMERICALITY_CHECKS, $options);
if ($this->is_null_with_option($var, $options))
continue;
if (true === $options['only_integer'] && !is_integer($var))
{
if (!preg_match('/A[+-]?d+Z/', (string)($var)))
{
if (isset($options['message']))
$message = $options['message'];
else
$message = Errors::$DEFAULT_ERROR_MESSAGES['not_a_number'];
$this->record->add($attribute, $message);
continue;
}
}
else
{
if (!is_numeric($var))
{
$this->record->add($attribute, Errors::$DEFAULT_ERROR_MESSAGES['not_a_number']);
continue;
}
$var = (float)$var;
}
foreach ($numericalityOptions as $option => $check)
{
$option_value = $options[$option];
if ('odd' != $option && 'even' != $option)
{
$option_value = (float)$options[$option];
if (!is_numeric($option_value))
throw new ValidationsArgumentError("$option must be a number");
if (isset($options['message']))
$message = $options['message'];
else
$message = Errors::$DEFAULT_ERROR_MESSAGES[$option];
$message = str_replace('%d', $option_value, $message);
if ('greater_than' == $option && !($var > $option_value))
$this->record->add($attribute, $message);
elseif ('greater_than_or_equal_to' == $option && !($var >= $option_value))
$this->record->add($attribute, $message);
elseif ('equal_to' == $option && !($var == $option_value))
$this->record->add($attribute, $message);
elseif ('less_than' == $option && !($var < $option_value))
$this->record->add($attribute, $message);
elseif ('less_than_or_equal_to' == $option && !($var <= $option_value))
$this->record->add($attribute, $message);
}
else
{
if (isset($options['message']))
$message = $options['message'];
else
$message = Errors::$DEFAULT_ERROR_MESSAGES[$option];
if ( ('odd' == $option && !( Utils::is_odd($var))) || ('even' == $option && ( Utils::is_odd($var))))
$this->record->add($attribute, $message);
}
}
}
}
/**
* Alias of {@link validates_length_of}
*
* @param array $attrs Validation definition
*/
public function validates_size_of($attrs)
{
$this->validates_length_of($attrs);
}
/**
* Validates that a value is matches a regex.
*
* <code>
* class Person extends ActiveRecordModel {
* static $validates_format_of = array(
* array('email', 'with' => '/^.*?@.*$/')
* );
* }
* </code>
*
* Available options:
*
* <ul>
* <li><b>with:</b> a regular expression</li>
* <li><b>message:</b> custom error message</li>
* </ul>
*
* @param array $attrs Validation definition
*/
public function validates_format_of($attrs)
{
$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('message' => Errors::$DEFAULT_ERROR_MESSAGES['invalid'], 'on' => 'save', 'with' => null));
foreach ($attrs as $attr)
{
$options = array_merge($configuration, $attr);
$attribute = $options[0];
$var = $this->model->$attribute;
if (is_null($options['with']) || !is_string($options['with']) || !is_string($options['with']))
throw new ValidationsArgumentError('A regular expression must be supplied as the [with] option of the configuration array.');
else
$expression = $options['with'];
if ($this->is_null_with_option($var, $options) || $this->is_blank_with_option($var, $options))
continue;
if (!@preg_match($expression, $var))
$this->record->add($attribute, $options['message']);
}
}
/**
* Validates the length of a value.
*
* <code>
* class Person extends ActiveRecordModel {
* static $validates_length_of = array(
* array('name', 'within' => array(1,50))
* );
* }
* </code>
*
* Available options:
*
* <ul>
* <li><b>is:</b> attribute should be exactly n characters long</li>
* <li><b>in/within:</b> attribute should be within an range array(min,max)</li>
* <li><b>maximum/minimum:</b> attribute should not be above/below respectively</li>
* </ul>
*
* @param array $attrs Validation definition
*/
public function validates_length_of($attrs)
{
$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array(
'too_long' => Errors::$DEFAULT_ERROR_MESSAGES['too_long'],
'too_short' => Errors::$DEFAULT_ERROR_MESSAGES['too_short'],
'wrong_length' => Errors::$DEFAULT_ERROR_MESSAGES['wrong_length']
));
foreach ($attrs as $attr)
{
$options = array_merge($configuration, $attr);
$range_options = array_intersect(array_keys(self::$ALL_RANGE_OPTIONS), array_keys($attr));
sort($range_options);
switch (sizeof($range_options))
{
case 0:
throw new ValidationsArgumentError('Range unspecified. Specify the [within], [maximum], or [is] option.');
case 1:
break;
default:
throw new ValidationsArgumentError('Too many range options specified. Choose only one.');
}
$attribute = $options[0];
$var = $this->model->$attribute;
$range_option = $range_options[0];
if ($this->is_null_with_option($var, $options) || $this->is_blank_with_option($var, $options))
continue;
if ('within' == $range_option || 'in' == $range_option)
{
$range = $options[$range_options[0]];
if (!(Utils::is_a('range', $range)))
throw new ValidationsArgumentError("$range_option must be an array composing a range of numbers with key [0] being less than key [1]");
if (is_float($range[0]) || is_float($range[1]))
throw new ValidationsArgumentError("Range values cannot use floats for length.");
if ((int)$range[0] <= 0 || (int)$range[1] <= 0)
throw new ValidationsArgumentError("Range values cannot use signed integers.");
$too_short = isset($options['message']) ? $options['message'] : $options['too_short'];
$too_long = isset($options['message']) ? $options['message'] : $options['too_long'];
$too_short = str_replace('%d', $range[0], $too_short);
$too_long = str_replace('%d', $range[1], $too_long);
if (strlen($this->model->$attribute) < (int)$range[0])
$this->record->add($attribute, $too_short);
elseif (strlen($this->model->$attribute) > (int)$range[1])
$this->record->add($attribute, $too_long);
}
elseif ('is' == $range_option || 'minimum' == $range_option || 'maximum' == $range_option)
{
$option = $options[$range_option];
if ((int)$option <= 0)
throw new ValidationsArgumentError("$range_option value cannot use a signed integer.");
if (is_float($option))
throw new ValidationsArgumentError("$range_option value cannot use a float for length.");
if (!is_null($this->model->$attribute))
{
$messageOptions = array('is' => 'wrong_length', 'minimum' => 'too_short', 'maximum' => 'too_long');
if (isset($options[$messageOptions[$range_option]]))
$message = $options[$messageOptions[$range_option]];
else
$message = $options['message'];
$message = str_replace('%d', $option, $message);
$attribute_value = $this->model->$attribute;
$len = strlen($attribute_value);
$value = (int)$attr[$range_option];
if ('maximum' == $range_option && $len > $value)
$this->record->add($attribute, $message);
if ('minimum' == $range_option && $len < $value)
$this->record->add($attribute, $message);
if ('is' == $range_option && $len !== $value)
$this->record->add($attribute, $message);
}
}
}
}
/**
* Validates the uniqueness of a value.
*
* <code>
* class Person extends ActiveRecordModel {
* static $validates_uniqueness_of = array(
* array('name'),
* array(array('blah','bleh'), 'message' => 'blech')
* );
* }
* </code>
*
* @param array $attrs Validation definition
*/
public function validates_uniqueness_of($attrs)
{
$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array(
'message' => Errors::$DEFAULT_ERROR_MESSAGES['unique']
));
foreach ($attrs as $attr)
{
$options = array_merge($configuration, $attr);
$pk = $this->model->get_primary_key();
$pk_value = $this->model->$pk[0];
if (is_array($options[0]))
{
$add_record = join("_and_", $options[0]);
$fields = $options[0];
}
else
{
$add_record = $options[0];
$fields = array($options[0]);
}
$sql = "";
$conditions = array("");
if ($pk_value === null)
$sql = "{$pk[0]} is not null";
else
{
$sql = "{$pk[0]}!=?";
array_push($conditions,$pk_value);
}
foreach ($fields as $field)
{
$field = $this->model->get_real_attribute_name($field);
$sql .= " and {$field}=?";
array_push($conditions,$this->model->$field);
}
$conditions[0] = $sql;
if ($this->model->exists(array('conditions' => $conditions)))
$this->record->add($add_record, $options['message']);
}
}
private function is_null_with_option($var, &$options)
{
return (is_null($var) && (isset($options['allow_null']) && $options['allow_null']));
}
private function is_blank_with_option($var, &$options)
{
return (Utils::is_blank($var) && (isset($options['allow_blank']) && $options['allow_blank']));
}
}
/**
* Class that holds {@link Validations} errors.
*
* @package ActiveRecord
*/
class Errors implements IteratorAggregate
{
private $model;
private $errors;
public static $DEFAULT_ERROR_MESSAGES = array(
'inclusion' => "is not included in the list",
'exclusion' => "is reserved",
'invalid' => "is invalid",
'confirmation' => "doesn't match confirmation",
'accepted' => "must be accepted",
'empty' => "can't be empty",
'blank' => "can't be blank",
'too_long' => "is too long (maximum is %d characters)",
'too_short' => "is too short (minimum is %d characters)",
'wrong_length' => "is the wrong length (should be %d characters)",
'taken' => "has already been taken",
'not_a_number' => "is not a number",
'greater_than' => "must be greater than %d",
'equal_to' => "must be equal to %d",
'less_than' => "must be less than %d",
'odd' => "must be odd",
'even' => "must be even",
'unique' => "must be unique",
'less_than_or_equal_to' => "must be less than or equal to %d",
'greater_than_or_equal_to' => "must be greater than or equal to %d"
);
/**
* Constructs an {@link Errors} object.
*
* @param Model $model The model the error is for
* @return Errors
*/
public function __construct(Model $model)
{
$this->model = $model;
}
/**
* Nulls $model so we don't get pesky circular references. $model is only needed during the
* validation process and so can be safely cleared once that is done.
*/
public function clear_model()
{
$this->model = null;
}
/**
* Add an error message.
*
* @param string $attribute Name of an attribute on the model
* @param string $msg The error message
*/
public function add($attribute, $msg)
{
if (is_null($msg))
$msg = self :: $DEFAULT_ERROR_MESSAGES['invalid'];
if (!isset($this->errors[$attribute]))
$this->errors[$attribute] = array($msg);
else
$this->errors[$attribute][] = $msg;
}
/**
* Adds an error message only if the attribute value is {@link http://www.php.net/empty empty}.
*
* @param string $attribute Name of an attribute on the model
* @param string $msg The error message
*/
public function add_on_empty($attribute, $msg)
{
if (empty($msg))
$msg = self::$DEFAULT_ERROR_MESSAGES['empty'];
if (empty($this->model->$attribute))
$this->add($attribute, $msg);
}
/**
* Retrieve error message for an attribute.
*
* @param string $attribute Name of an attribute on the model
* @return string
*/
public function __get($attribute)
{
if (!isset($this->errors[$attribute]))
return null;
return $this->errors[$attribute];
}
/**
* Adds the error message only if the attribute value was null or an empty string.
*
* @param string $attribute Name of an attribute on the model
* @param string $msg The error message
*/
public function add_on_blank($attribute, $msg)
{
if (!$msg)
$msg = self::$DEFAULT_ERROR_MESSAGES['blank'];
if (($value = $this->model->$attribute) === '' || $value === null)
$this->add($attribute, $msg);
}
/**
* Returns true if the specified attribute had any error messages.
*
* @param string $attribute Name of an attribute on the model
* @return boolean
*/
public function is_invalid($attribute)
{
return isset($this->errors[$attribute]);
}
/**
* Returns the error message for the specified attribute or null if none.
*
* @param string $attribute Name of an attribute on the model
* @return string
*/
public function on($attribute)
{
if (!isset($this->errors[$attribute]))
return null;
$errors = $this->errors[$attribute];
if (null === $errors)
return null;
else
return count($errors) == 1 ? $errors[0] : $errors;
}
/**
* Returns all the error messages as an array.
*
* <code>
* $model->errors->full_messages();
*
* # array(
* # "Name can't be blank",
* # "State is the wrong length (should be 2 chars)"
* # )
* </code>
*
* @param array $options Options for messages
* @return array
*/
public function full_messages()
{
$full_messages = array();
if ($this->errors)
{
foreach ($this->errors as $attribute => $messages)
{
foreach ($messages as $msg)
{
if (is_null($msg))
continue;
$full_messages[] = Utils::human_attribute($attribute) . ' ' . $msg;
}
}
}
return $full_messages;
}
/**
* Returns true if there are no error messages.
* @return boolean
*/
public function is_empty()
{
return empty($this->errors);
}
/**
* Clears out all error messages.
*/
public function clear()
{
$this->errors = array();
}
/**
* Returns the number of error messages there are.
* @return int
*/
public function size()
{
if ($this->is_empty())
return 0;
$count = 0;
foreach ($this->errors as $attribute => $error)
$count += count($error);
return $count;
}
/**
* Returns an iterator to the error messages.
*
* This will allow you to iterate over the {@link Errors} object using foreach.
*
* <code>
* foreach ($model->errors as $msg)
* echo "$msgn";
* </code>
*
* @return ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->full_messages());
}
};
?>