From c3b8524e1b97bbe14e3b5130d3c9b5d75eea75d7 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 12 Jul 2011 20:04:14 -0500 Subject: [PATCH] rewrote validation library. --- application/lang/en/validation.php | 65 +-- system/db/eloquent.php | 11 + system/input.php | 12 + system/lang.php | 18 +- system/str.php | 8 +- .../{error_collector.php => errors.php} | 14 +- system/validation/message.php | 155 ------ system/validation/nullable_rule.php | 94 ---- system/validation/rangable_rule.php | 145 ------ system/validation/rule.php | 72 --- system/validation/rules/acceptance_of.php | 39 -- system/validation/rules/confirmation_of.php | 25 - system/validation/rules/exclusion_of.php | 43 -- system/validation/rules/format_of.php | 43 -- system/validation/rules/inclusion_of.php | 43 -- system/validation/rules/length_of.php | 49 -- system/validation/rules/numericality_of.php | 116 ----- system/validation/rules/presence_of.php | 29 -- system/validation/rules/uniqueness_of.php | 62 --- system/validation/rules/upload_of.php | 160 ------ system/validation/rules/with_callback.php | 48 -- system/validator.php | 493 ++++++++++++++++-- 22 files changed, 508 insertions(+), 1236 deletions(-) rename system/validation/{error_collector.php => errors.php} (78%) delete mode 100644 system/validation/message.php delete mode 100644 system/validation/nullable_rule.php delete mode 100644 system/validation/rangable_rule.php delete mode 100644 system/validation/rule.php delete mode 100644 system/validation/rules/acceptance_of.php delete mode 100644 system/validation/rules/confirmation_of.php delete mode 100644 system/validation/rules/exclusion_of.php delete mode 100644 system/validation/rules/format_of.php delete mode 100644 system/validation/rules/inclusion_of.php delete mode 100644 system/validation/rules/length_of.php delete mode 100644 system/validation/rules/numericality_of.php delete mode 100644 system/validation/rules/presence_of.php delete mode 100644 system/validation/rules/uniqueness_of.php delete mode 100644 system/validation/rules/upload_of.php delete mode 100644 system/validation/rules/with_callback.php diff --git a/application/lang/en/validation.php b/application/lang/en/validation.php index fa09a0be..46a72f4d 100644 --- a/application/lang/en/validation.php +++ b/application/lang/en/validation.php @@ -2,50 +2,25 @@ return array( - /* - |-------------------------------------------------------------------------- - | General Validation Messages - |-------------------------------------------------------------------------- - */ - - "acceptance_of" => "The :attribute must be accepted.", - "confirmation_of" => "The :attribute confirmation does not match.", - "exclusion_of" => "The :attribute value is invalid.", - "format_of" => "The :attribute format is invalid.", - "inclusion_of" => "The :attribute value is invalid.", - "presence_of" => "The :attribute can't be empty.", - "uniqueness_of" => "The :attribute has already been taken.", - "with_callback" => "The :attribute is invalid.", - - /* - |-------------------------------------------------------------------------- - | Numericality_Of Validation Messages - |-------------------------------------------------------------------------- - */ - - "number_not_valid" => "The :attribute must be a number.", - "number_not_integer" => "The :attribute must be an integer.", - "number_wrong_size" => "The :attribute must be :size.", - "number_too_big" => "The :attribute must be no more than :max.", - "number_too_small" => "The :attribute must be at least :min.", - - /* - |-------------------------------------------------------------------------- - | Length_Of Validation Messages - |-------------------------------------------------------------------------- - */ - - "string_wrong_size" => "The :attribute must be :size characters.", - "string_too_big" => "The :attribute must be no more than :max characters.", - "string_too_small" => "The :attribute must be at least :min characters.", - - /* - |-------------------------------------------------------------------------- - | Upload_Of Validation Messages - |-------------------------------------------------------------------------- - */ - - "file_wrong_type" => "The :attribute must be a file of type: :types.", - "file_too_big" => "The :attribute exceeds size limit of :maxkb.", + "accepted" => "The :attribute must be accepted.", + "active_url" => "The :attribute does not exist.", + "alpha" => "The :attribute may only contain letters.", + "alpha_dash" => "The :attribute may only contain letters, numbers, dashes, and underscores.", + "alpha_num" => "The :attribute may only contain letters and numbers.", + "between" => "The :attribute must be between :min - :max.", + "confirmed" => "The :attribute confirmation does not match.", + "email" => "The :attribute format is invalid.", + "image" => "The :attribute must be an image.", + "in" => "The selected :attribute is invalid.", + "integer" => "The :attribute must be an integer.", + "max" => "The :attribute must be less than :max.", + "mimes" => "The :attribute must be a file of type: :values.", + "min" => "The :attribute must be at least :min.", + "not_in" => "The selected :attribute is invalid.", + "numeric" => "The :attribute must be a number.", + "required" => "The :attribute field is required.", + "size" => "The :attribute must be :size.", + "unique" => "The :attribute has already been taken.", + "url" => "The :attribute format is invalid.", ); \ No newline at end of file diff --git a/system/db/eloquent.php b/system/db/eloquent.php index f63d265c..625d7559 100644 --- a/system/db/eloquent.php +++ b/system/db/eloquent.php @@ -83,6 +83,17 @@ abstract class Eloquent { * @return void */ public function __construct($attributes = array()) + { + $this->fill($attributes); + } + + /** + * Set the attributes of the model using an array. + * + * @param array $attributes + * @return void + */ + public function fill($attributes) { foreach ($attributes as $key => $value) { diff --git a/system/input.php b/system/input.php index fac8aa54..cabe1f96 100644 --- a/system/input.php +++ b/system/input.php @@ -9,6 +9,18 @@ class Input { */ public static $input; + /** + * Get all of the input data for the request. + * + * This method returns a merged array containing Input::get and Input::file. + * + * @return array + */ + public static function all() + { + return array_merge(static::get(), static::file()); + } + /** * Determine if the input data contains an item. * diff --git a/system/lang.php b/system/lang.php index 40dd37e9..1611bd56 100644 --- a/system/lang.php +++ b/system/lang.php @@ -54,26 +54,28 @@ public static function line($key) /** * Get the language line. * + * @param string $language * @param mixed $default * @return string */ - public function get($default = null) + public function get($language = null, $default = null) { - $language = Config::get('application.language'); + if (is_null($language)) + { + $language = Config::get('application.language'); + } list($file, $line) = $this->parse($this->key); $this->load($file, $language); - if ( ! array_key_exists($language.$file, static::$lines)) + if ( ! isset(static::$lines[$language.$file][$line])) { - $line = is_callable($default) ? call_user_func($default) : $default; - } - else - { - $line = Arr::get(static::$lines[$language.$file], $line, $default); + return is_callable($default) ? call_user_func($default) : $default; } + $line = static::$lines[$language.$file][$line]; + foreach ($this->replacements as $key => $value) { $line = str_replace(':'.$key, $value, $line); diff --git a/system/str.php b/system/str.php index c9782279..1c1d05eb 100644 --- a/system/str.php +++ b/system/str.php @@ -60,13 +60,13 @@ public static function length($value) /** * Generate a random alpha or alpha-numeric string. * - * Supported types: 'alnum' and 'alpha'. + * Supported types: 'alpha_num' and 'alpha'. * * @param int $length * @param string $type * @return string */ - public static function random($length = 16, $type = 'alnum') + public static function random($length = 16, $type = 'alpha_num') { $value = ''; @@ -86,11 +86,11 @@ public static function random($length = 16, $type = 'alnum') * @param string $type * @return string */ - private static function pool($type = 'alnum') + private static function pool($type = 'alpha_num') { switch ($type) { - case 'alnum': + case 'alpha_num': return '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; default: diff --git a/system/validation/error_collector.php b/system/validation/errors.php similarity index 78% rename from system/validation/error_collector.php rename to system/validation/errors.php index b0bbee9b..722eaa84 100644 --- a/system/validation/error_collector.php +++ b/system/validation/errors.php @@ -1,6 +1,6 @@ messages) or ! is_array($this->messages[$attribute]) or ! in_array($message, $this->messages[$attribute])) { $this->messages[$attribute][] = $message; @@ -94,10 +88,6 @@ public function all($format = ':message') { $all = array(); - // --------------------------------------------------------- - // Add each error message to the array of messages. Each - // messages will have the specified format applied to it. - // --------------------------------------------------------- foreach ($this->messages as $messages) { $all = array_merge($all, $this->format($messages, $format)); diff --git a/system/validation/message.php b/system/validation/message.php deleted file mode 100644 index 34fc95a1..00000000 --- a/system/validation/message.php +++ /dev/null @@ -1,155 +0,0 @@ -error)) - { - $class = explode('\\', get_class($rule)); - - $rule->error = strtolower(end($class)); - } - - return (is_null($rule->message)) ? Lang::line('validation.'.$rule->error)->get() : $rule->message; - } - - /** - * Get the error message for a Rangable rule. - * - * @param Rule $rule - * @return string - */ - private static function get_rangable_message($rule) - { - // --------------------------------------------------------- - // Rangable rules sometimes set a "presence_of" error. - // - // This occurs when an attribute is null and the option to - // allow null values has not been set. - // --------------------------------------------------------- - if ($rule->error == 'presence_of') - { - return static::get_message($rule); - } - - // --------------------------------------------------------- - // Slice "number_" or "string_" off of the error type. - // --------------------------------------------------------- - $error_type = substr($rule->error, 7); - - return (is_null($rule->$error_type)) ? Lang::line('validation.'.$rule->error)->get() : $rule->$error_type; - } - - /** - * Get the error message for an Upload_Of rule. - * - * @param Rule $rule - * @return string - */ - private static function get_upload_of_message($rule) - { - // --------------------------------------------------------- - // Upload_Of rules sometimes set a "presence_of" error. - // - // This occurs when the uploaded file didn't exist and the - // "not_required" method was not called. - // --------------------------------------------------------- - if ($rule->error == 'presence_of') - { - return static::get_message($rule); - } - - // --------------------------------------------------------- - // Slice "file_" off of the error type. - // --------------------------------------------------------- - $error_type = substr($rule->error, 5); - - return (is_null($rule->$error_type)) ? Lang::line('validation.'.$rule->error)->get() : $rule->$error_type; - } - - /** - * Prepare an error message for display. All place-holders will be replaced - * with their actual values. - * - * @param Rule $rule - * @param string $attribute - * @param string $message - * @return string - */ - private static function prepare($rule, $attribute, $message) - { - // --------------------------------------------------------- - // The rangable rule messages have three place-holders that - // must be replaced. - // - // :max = The maximum size of the attribute. - // :min = The minimum size of the attribute. - // :size = The exact size the attribute must be. - // --------------------------------------------------------- - if ($rule instanceof Rangable_Rule) - { - $message = str_replace(':max', $rule->maximum, $message); - $message = str_replace(':min', $rule->minimum, $message); - $message = str_replace(':size', $rule->size, $message); - } - // --------------------------------------------------------- - // The Upload_Of rule message have two place-holders taht - // must be replaced. - // - // :max = The maximum file size of the upload (kilobytes). - // :types = The allowed file types for the upload. - // --------------------------------------------------------- - elseif ($rule instanceof Rules\Upload_Of) - { - $message = str_replace(':max', $rule->maximum, $message); - - if (is_array($rule->types)) - { - $message = str_replace(':types', implode(', ', $rule->types), $message); - } - } - - return str_replace(':attribute', Lang::line('attributes.'.$attribute)->get(str_replace('_', ' ', $attribute)), $message); - } - -} \ No newline at end of file diff --git a/system/validation/nullable_rule.php b/system/validation/nullable_rule.php deleted file mode 100644 index 40f43f6e..00000000 --- a/system/validation/nullable_rule.php +++ /dev/null @@ -1,94 +0,0 @@ -allow_null) - { - $this->error = 'presence_of'; - } - - return is_null($this->error); - } - - // ------------------------------------------------------------- - // Make sure the attribute is not an empty string. An error - // will be raised if the attribute is empty and empty strings - // are not allowed, halting the child's validation. - // ------------------------------------------------------------- - elseif (Str::length((string) $attributes[$attribute]) == 0 and ! $this->allow_empty) - { - $this->error = 'presence_of'; - - return false; - } - } - - /** - * Allow a empty and null to be considered valid. - * - * @return Nullable_Rule - */ - public function not_required() - { - return $this->allow_empty()->allow_null(); - } - - /** - * Allow empty to be considered valid. - * - * @return Nullable_Rule - */ - public function allow_empty() - { - $this->allow_empty = true; - return $this; - } - - /** - * Allow null to be considered valid. - * - * @return Nullable_Rule - */ - public function allow_null() - { - $this->allow_null = true; - return $this; - } - -} \ No newline at end of file diff --git a/system/validation/rangable_rule.php b/system/validation/rangable_rule.php deleted file mode 100644 index 1845ca29..00000000 --- a/system/validation/rangable_rule.php +++ /dev/null @@ -1,145 +0,0 @@ -size = $size; - return $this; - } - - /** - * Set the minimum and maximum size of the attribute. - * - * @param int $minimum - * @param int $maximum - * @return Rangable_Rule - */ - public function between($minimum, $maximum) - { - $this->minimum = $minimum; - $this->maximum = $maximum; - - return $this; - } - - /** - * Set the minimum size the attribute. - * - * @param int $minimum - * @return Rangable_Rule - */ - public function minimum($minimum) - { - $this->minimum = $minimum; - return $this; - } - - /** - * Set the maximum size the attribute. - * - * @param int $maximum - * @return Rangable_Rule - */ - public function maximum($maximum) - { - $this->maximum = $maximum; - return $this; - } - - /** - * Set the validation error message. - * - * @param string $message - * @return Rangable_Rule - */ - public function message($message) - { - return $this->wrong_size($message)->too_big($message)->too_small($message); - } - - /** - * Set the "wrong size" error message. - * - * @param string $message - * @return Rangable_Rule - */ - public function wrong_size($message) - { - $this->wrong_size = $message; - return $this; - } - - /** - * Set the "too big" error message. - * - * @param string $message - * @return Rangable_Rule - */ - public function too_big($message) - { - $this->too_big = $message; - return $this; - } - - /** - * Set the "too small" error message. - * - * @param string $message - * @return Rangable_Rule - */ - public function too_small($message) - { - $this->too_small = $message; - return $this; - } - -} \ No newline at end of file diff --git a/system/validation/rule.php b/system/validation/rule.php deleted file mode 100644 index fa0c6692..00000000 --- a/system/validation/rule.php +++ /dev/null @@ -1,72 +0,0 @@ -attributes = $attributes; - } - - /** - * Run the validation rule. - * - * @param array $attributes - * @param Error_Collector $errors - * @return void - */ - public function validate($attributes, $errors) - { - foreach ($this->attributes as $attribute) - { - $this->error = null; - - if ( ! $this->check($attribute, $attributes)) - { - $errors->add($attribute, Message::get($this, $attribute)); - } - } - } - - /** - * Set the validation error message. - * - * @param string $message - * @return Rule - */ - public function message($message) - { - $this->message = $message; - return $this; - } - -} \ No newline at end of file diff --git a/system/validation/rules/acceptance_of.php b/system/validation/rules/acceptance_of.php deleted file mode 100644 index 02b505a9..00000000 --- a/system/validation/rules/acceptance_of.php +++ /dev/null @@ -1,39 +0,0 @@ -accepts; - } - - /** - * Set the accepted value. - * - * @param string $value - * @return Acceptance_Of - */ - public function accepts($value) - { - $this->accepts = $value; - return $this; - } - -} \ No newline at end of file diff --git a/system/validation/rules/confirmation_of.php b/system/validation/rules/confirmation_of.php deleted file mode 100644 index a6d7de0e..00000000 --- a/system/validation/rules/confirmation_of.php +++ /dev/null @@ -1,25 +0,0 @@ -reserved); - } - - /** - * Set the reserved values for the attribute - * - * @param array $reserved - * @return Exclusion_Of - */ - public function from($reserved) - { - $this->reserved = $reserved; - return $this; - } - -} \ No newline at end of file diff --git a/system/validation/rules/format_of.php b/system/validation/rules/format_of.php deleted file mode 100644 index dfcebb16..00000000 --- a/system/validation/rules/format_of.php +++ /dev/null @@ -1,43 +0,0 @@ -expression, $attributes[$attribute]); - } - - /** - * Set the regular expression. - * - * @param string $expression - * @return Format_Of - */ - public function using($expression) - { - $this->expression = $expression; - return $this; - } - -} \ No newline at end of file diff --git a/system/validation/rules/inclusion_of.php b/system/validation/rules/inclusion_of.php deleted file mode 100644 index f92ace6f..00000000 --- a/system/validation/rules/inclusion_of.php +++ /dev/null @@ -1,43 +0,0 @@ -accepted); - } - - /** - * Set the accepted values for the attribute. - * - * @param array $accepted - * @return Inclusion_Of - */ - public function in($accepted) - { - $this->accepted = $accepted; - return $this; - } - -} \ No newline at end of file diff --git a/system/validation/rules/length_of.php b/system/validation/rules/length_of.php deleted file mode 100644 index f53ff5aa..00000000 --- a/system/validation/rules/length_of.php +++ /dev/null @@ -1,49 +0,0 @@ -size) and Str::length($value) !== $this->size) - { - $this->error = 'string_wrong_size'; - } - // --------------------------------------------------------- - // Validate the maximum length of the attribute. - // --------------------------------------------------------- - elseif ( ! is_null($this->maximum) and Str::length($value) > $this->maximum) - { - $this->error = 'string_too_big'; - } - // --------------------------------------------------------- - // Validate the minimum length of the attribute. - // --------------------------------------------------------- - elseif ( ! is_null($this->minimum) and Str::length($value) < $this->minimum) - { - $this->error = 'string_too_small'; - } - - return is_null($this->error); - } - -} \ No newline at end of file diff --git a/system/validation/rules/numericality_of.php b/system/validation/rules/numericality_of.php deleted file mode 100644 index 43813ba7..00000000 --- a/system/validation/rules/numericality_of.php +++ /dev/null @@ -1,116 +0,0 @@ -error = 'number_not_valid'; - } - // --------------------------------------------------------- - // Validate the attribute is an integer. - // --------------------------------------------------------- - elseif ($this->only_integer and filter_var($attributes[$attribute], FILTER_VALIDATE_INT) === false) - { - $this->error = 'number_not_integer'; - } - // --------------------------------------------------------- - // Validate the exact size of the attribute. - // --------------------------------------------------------- - elseif ( ! is_null($this->size) and $attributes[$attribute] != $this->size) - { - $this->error = 'number_wrong_size'; - } - // --------------------------------------------------------- - // Validate the maximum size of the attribute. - // --------------------------------------------------------- - elseif ( ! is_null($this->maximum) and $attributes[$attribute] > $this->maximum) - { - $this->error = 'number_too_big'; - } - // --------------------------------------------------------- - // Validate the minimum size of the attribute. - // --------------------------------------------------------- - elseif ( ! is_null($this->minimum) and $attributes[$attribute] < $this->minimum) - { - $this->error = 'number_too_small'; - } - - return is_null($this->error); - } - - /** - * Specify that the attribute must be an integer. - * - * @return Numericality_Of - */ - public function only_integer() - { - $this->only_integer = true; - return $this; - } - - /** - * Set the "not valid" error message. - * - * @param string $message - * @return Numericality_Of - */ - public function not_valid($message) - { - $this->not_valid = $message; - return $this; - } - - /** - * Set the "not integer" error message. - * - * @param string $message - * @return Numericality_Of - */ - public function not_integer($message) - { - $this->not_integer = $message; - return $this; - } - -} \ No newline at end of file diff --git a/system/validation/rules/presence_of.php b/system/validation/rules/presence_of.php deleted file mode 100644 index 8926e967..00000000 --- a/system/validation/rules/presence_of.php +++ /dev/null @@ -1,29 +0,0 @@ -column)) - { - $this->column = $attribute; - } - - return DB::table($this->table)->where($this->column, '=', $attributes[$attribute])->count() == 0; - } - - /** - * Set the database table and column. - * - * The attribute name will be used as the column name if no other - * column name is specified. - * - * @param string $table - * @param string $column - * @return Uniqueness_Of - */ - public function on($table, $column = null) - { - $this->table = $table; - $this->column = $column; - - return $this; - } - -} \ No newline at end of file diff --git a/system/validation/rules/upload_of.php b/system/validation/rules/upload_of.php deleted file mode 100644 index 4dfba8ca..00000000 --- a/system/validation/rules/upload_of.php +++ /dev/null @@ -1,160 +0,0 @@ -allow_null) - { - $this->error = 'presence_of'; - } - - return is_null($this->error); - } - - // ----------------------------------------------------- - // Uploaded files are stored in the $_FILES array, so - // we use that array instead of the $attributes. - // ----------------------------------------------------- - $file = Input::file($attribute); - - if ( ! is_null($this->maximum) and $file['size'] > $this->maximum * 1000) - { - $this->error = 'file_too_big'; - } - - // ----------------------------------------------------- - // The File::is method uses the Fileinfo PHP extension - // to determine the MIME type of the file. - // ----------------------------------------------------- - foreach ($this->types as $type) - { - if (File::is($type, $file['tmp_name'])) - { - break; - } - - $this->error = 'file_wrong_type'; - } - - return is_null($this->error); - } - - /** - * Set the acceptable file types. - * - * @return Upload_Of - */ - public function is() - { - $this->types = func_get_args(); - return $this; - } - - /** - * Require that the uploaded file is an image type. - * - * @return Upload_Of - */ - public function is_image() - { - $this->types = array_merge($this->types, array('jpg', 'gif', 'png', 'bmp')); - return $this; - } - - /** - * Set the maximum file size in kilobytes. - * - * @param int $maximum - * @return Upload_Of - */ - public function maximum($maximum) - { - $this->maximum = $maximum; - return $this; - } - - /** - * Set the validation error message. - * - * @param string $message - * @return Upload_Of - */ - public function message($message) - { - return $this->wrong_type($message)->too_big($message); - } - - /** - * Set the "wrong type" error message. - * - * @param string $message - * @return Upload_Of - */ - public function wrong_type($message) - { - $this->wrong_type = $message; - return $this; - } - - /** - * Set the "too big" error message. - * - * @param string $message - * @return Upload_Of - */ - public function too_big($message) - { - $this->too_big = $message; - return $this; - } - -} \ No newline at end of file diff --git a/system/validation/rules/with_callback.php b/system/validation/rules/with_callback.php deleted file mode 100644 index ed1d2ff3..00000000 --- a/system/validation/rules/with_callback.php +++ /dev/null @@ -1,48 +0,0 @@ -callback)) - { - throw new \Exception("The validation callback for the [$attribute] attribute is not callable."); - } - - if ( ! is_null($nullable = parent::check($attribute, $attributes))) - { - return $nullable; - } - - return call_user_func($this->callback, $attributes[$attribute]); - } - - /** - * Set the validation callback. - * - * @param function $callback - * @return With_Callback - */ - public function using($callback) - { - $this->callback = $callback; - return $this; - } - -} \ No newline at end of file diff --git a/system/validator.php b/system/validator.php index b4f87ccb..ff3e1209 100644 --- a/system/validator.php +++ b/system/validator.php @@ -3,86 +3,491 @@ class Validator { /** - * The attributes being validated. + * The array being validated. * * @var array */ public $attributes; - /** - * The validation error collector. - * - * @var Error_Collector - */ - public $errors; - /** * The validation rules. * * @var array */ - public $rules = array(); + public $rules; /** - * Create a new Validator instance. + * The validation messages. * - * @param mixed $target + * @var array + */ + public $messages; + + /** + * The post-validation error messages. + * + * @var array + */ + public $errors; + + /** + * The "size" related validation rules. + * + * @var array + */ + protected $size_rules = array('size', 'between', 'min', 'max'); + + /** + * Create a new validator instance. + * + * @param array $attributes + * @param array $rules + * @param array $messages * @return void */ - public function __construct($target = null) + public function __construct($attributes, $rules, $messages = array()) { - $this->errors = new Validation\Error_Collector; - - if (is_null($target)) - { - $target = Input::get(); - } - - // If the source is an Eloquent model, use the model's attributes as the validation attributes. - $this->attributes = ($target instanceof DB\Eloquent) ? $target->attributes : (array) $target; + $this->attributes = $attributes; + $this->rules = $rules; + $this->messages = $messages; } /** - * Create a new Validator instance. - * - * @param mixed $target - * @return Validator - */ - public static function make($target = null) - { - return new static($target); - } - - /** - * Determine if the attributes pass all of the validation rules. + * Validate the target array using the specified validation rules. * * @return bool */ - public function is_valid() + public function invalid() { - $this->errors->messages = array(); + return ! $this->valid(); + } - foreach ($this->rules as $rule) + /** + * Validate the target array using the specified validation rules. + * + * @return bool + */ + public function valid() + { + $this->errors = new Validation\Errors; + + foreach ($this->rules as $attribute => $rules) { - $rule->validate($this->attributes, $this->errors); + if (is_string($rules)) + { + $rules = explode('|', $rules); + } + + foreach ($rules as $rule) + { + $this->check($attribute, $rule); + } } return count($this->errors->messages) == 0; } /** - * Magic Method for dynamically creating validation rules. + * Evaluate an attribute against a validation rule. + * + * @param string $attribute + * @param string $rule + * @return void */ - public function __call($method, $parameters) + protected function check($attribute, $rule) { - if (file_exists(SYS_PATH.'validation/rules/'.$method.EXT)) - { - $rule = '\\System\\Validation\\Rules\\'.$method; + list($rule, $parameters) = $this->parse($rule); - return $this->rules[] = new $rule($parameters); + if ( ! method_exists($this, $validator = 'validate_'.$rule)) + { + throw new \Exception("Validation rule [$rule] doesn't exist."); } - throw new \Exception("Method [$method] does not exist on Validator class."); + // No validation will be run for attributes that do not exist unless the rule being validated + // is "required" or "accepted". No other rules have implicit "required" checks. + if ( ! array_key_exists($attribute, $this->attributes) and ! in_array($rule, array('required', 'accepted'))) + { + continue; + } + + if ( ! $this->$validator($attribute, $parameters)) + { + $this->errors->add($attribute, $this->format_message($this->get_message($attribute, $rule), $attribute, $rule, $parameters)); + } + } + + /** + * Validate that a required attribute exists in the attributes array. + * + * @param string $attribute + * @return bool + */ + protected function validate_required($attribute) + { + return array_key_exists($attribute, $this->attributes) and trim($this->attributes[$attribute]) !== ''; + } + + /** + * Validate that an attribute has a matching confirmation attribute. + * + * @param string $attribute + * @return bool + */ + protected function validate_confirmed($attribute) + { + return array_key_exists($attribute.'_confirmation', $this->attributes) and $this->attributes[$attribute] == $this->attributes[$attribute.'_confirmation']; + } + + /** + * Validate that an attribute was "accepted". + * + * This validation rule implies the attribute is "required". + * + * @param string $attribute + * @return bool + */ + protected function validate_accepted($attribute) + { + return static::validate_required($attribute) and ($this->attributes[$attribute] == 'yes' or $this->attributes[$attribute] == '1'); + } + + /** + * Validate that an attribute is numeric. + * + * @param string $attribute + * @return bool + */ + protected function validate_numeric($attribute) + { + return is_numeric($this->attributes[$attribute]); + } + + /** + * Validate that an attribute is an integer. + * + * @param string $attribute + * @return bool + */ + protected function validate_integer($attribute) + { + return filter_var($this->attributes[$attribute], FILTER_VALIDATE_INT) !== false; + } + + /** + * Validate the size of an attribute. + * + * @param string $attribute + * @param array $parameters + * @return bool + */ + protected function validate_size($attribute, $parameters) + { + return $this->get_size($attribute) == $parameters[0]; + } + + /** + * Validate the size of an attribute is between a set of values. + * + * @param string $attribute + * @param array $parameters + * @return bool + */ + protected function validate_between($attribute, $parameters) + { + return $this->get_size($attribute) >= $parameters[0] and $this->get_size($attribute) <= $parameters[1]; + } + + /** + * Validate the size of an attribute is greater than a minimum value. + * + * @param string $attribute + * @param array $parameters + * @return bool + */ + protected function validate_min($attribute, $parameters) + { + return $this->get_size($attribute) >= $parameters[0]; + } + + /** + * Validate the size of an attribute is less than a maximum value. + * + * @param string $attribute + * @param array $parameters + * @return bool + */ + protected function validate_max($attribute, $parameters) + { + return $this->get_size($attribute) <= $parameters[0]; + } + + /** + * Get the size of an attribute. + * + * @param string $attribute + * @return mixed + */ + protected function get_size($attribute) + { + if ($this->has_numeric_rule($attribute)) + { + return $this->attributes[$attribute]; + } + + return (array_key_exists($attribute, $_FILES)) ? $this->attributes[$attribute]['size'] / 1000 : Str::length(trim($this->attributes[$attribute])); + } + + /** + * Validate an attribute is contained within a list of values. + * + * @param string $attribute + * @param array $parameters + * @return bool + */ + protected function validate_in($attribute, $parameters) + { + return in_array($this->attributes[$attribute], $parameters); + } + + /** + * Validate an attribute is not contained within a list of values. + * + * @param string $attribute + * @param array $parameters + * @return bool + */ + protected function validate_not_in($attribute, $parameters) + { + return ! in_array($this->attributes[$attribute], $parameters); + } + + /** + * Validate the uniqueness of an attribute value on a given database table. + * + * @param string $attribute + * @param array $parameters + * @return bool + */ + protected function validate_unique($attribute, $parameters) + { + if ( ! isset($parameters[1])) + { + $parameters[1] = $attribute; + } + + return DB::table($parameters[0])->where($parameters[1], '=', $this->attributes[$attribute])->count() == 0; + } + + /** + * Validate than an attribute is a valid e-mail address. + * + * @param string $attribute + * @return bool + */ + protected function validate_email($attribute) + { + return filter_var($this->attributes[$attribute], FILTER_VALIDATE_EMAIL) !== false; + } + + /** + * Validate than an attribute is a valid URL. + * + * @param string $attribute + * @return bool + */ + protected function validate_url($attribute) + { + return filter_var($this->attributes[$attribute], FILTER_VALIDATE_URL) !== false; + } + + /** + * Validate that an attribute is an active URL. + * + * @param string $attribute + * @return bool + */ + protected function validate_active_url($attribute) + { + $url = str_replace(array('http://', 'https://', 'ftp://'), '', Str::lower($this->attributes[$attribute])); + + return checkdnsrr($url); + } + + /** + * Validate the MIME type of a file is an image MIME type. + * + * @param string $attribute + * @return bool + */ + protected function validate_image($attribute) + { + return static::validate_mime($attribute, array('jpg', 'png', 'gif', 'bmp')); + } + + /** + * Validate than an attribute contains only alphabetic characters. + * + * @param string $attribute + * @return bool + */ + protected function validate_alpha($attribute) + { + return preg_match('/^([a-z])+$/i', $this->attributes[$attribute]); + } + + /** + * Validate than an attribute contains only alpha-numeric characters. + * + * @param string $attribute + * @return bool + */ + protected function validate_alpha_num($attribute) + { + return preg_match('/^([a-z0-9])+$/i', $this->attributes[$attribute]); + } + + /** + * Validate than an attribute contains only alpha-numeric characters, dashes, and underscores. + * + * @param string $attribute + * @return bool + */ + protected function validate_alpha_dash($attribute) + { + return preg_match('/^([-a-z0-9_-])+$/i', $this->attributes[$attribute]); + } + + /** + * Validate the MIME type of a file upload attribute is in a set of MIME types. + * + * @param string $attribute + * @param array $parameters + * @return bool + */ + protected function validate_mimes($attribute, $parameters) + { + foreach ($parameters as $extension) + { + if (File::is($extension, $this->attributes[$attribute]['tmp_name'])) + { + return true; + } + } + + return false; + } + + /** + * Get the proper error message for an attribute and rule. + * + * Developer specified attribute specific rules take first priority. + * Developer specified error rules take second priority. + * + * If the message has not been specified by the developer, the default will be used + * from the validation language file. + * + * @param string $attribute + * @param string $rule + * @return string + */ + protected function get_message($attribute, $rule) + { + if (array_key_exists($attribute.'_'.$rule, $this->messages)) + { + return $this->messages[$attribute.'_'.$rule]; + } + elseif (array_key_exists($rule, $this->messages)) + { + return $this->messages[$rule]; + } + else + { + $message = Lang::line('validation.'.$rule)->get(); + + // For "size" rules that are validating strings or files, we need to adjust + // the default error message appropriately. + if (in_array($rule, $this->size_rules) and ! $this->has_numeric_rule($attribute)) + { + return (array_key_exists($attribute, $_FILES)) ? rtrim($message, '.').' kilobytes.' : rtrim($message, '.').' characters.'; + } + + return $message; + } + } + + /** + * Replace all error message place-holders with actual values. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function format_message($message, $attribute, $rule, $parameters) + { + $display = Lang::line('attributes.'.$attribute)->get(null, function() use ($attribute) { return str_replace('_', ' ', $attribute); }); + + $message = str_replace(':attribute', $display, $message); + + if (in_array($rule, $this->size_rules)) + { + $max = ($rule == 'between') ? $parameters[1] : $parameters[0]; + + $message = str_replace(':size', $parameters[0], str_replace(':min', $parameters[0], str_replace(':max', $max, $message))); + } + elseif (in_array($rule, array('in', 'not_in', 'mimes'))) + { + $message = str_replace(':values', implode(', ', $parameters), $message); + } + + return $message; + } + + /** + * Determine if an attribute has either a "numeric" or "integer" rule. + * + * @param string $attribute + * @return bool + */ + protected function has_numeric_rule($attribute) + { + return $this->has_rule($attribute, array('numeric', 'integer')); + } + + /** + * Determine if an attribute has a rule assigned to it. + * + * @param string $attribute + * @param array $rules + * @return bool + */ + protected function has_rule($attribute, $rules) + { + foreach ($this->rules[$attribute] as $rule) + { + list($rule, $parameters) = $this->parse($rule); + + if (in_array($rule, $rules)) + { + return true; + } + } + + return false; + } + + /** + * Extrac the rule name and parameters from a rule. + * + * @param string $rule + * @return array + */ + protected function parse($rule) + { + $parameters = (($colon = strpos($rule, ':')) !== false) ? explode(',', substr($rule, $colon + 1)) : array(); + + return array(is_numeric($colon) ? substr($rule, 0, $colon) : $rule, $parameters); } } \ No newline at end of file