customMessages = $messages; $this->data = $this->parseData($data); $this->rules = $this->explodeRules($rules); $this->customAttributes = $customAttributes; } private function __clone(){} /** * @param array $data * @param array $rules * @param array $messages * @param array $customAttributes * @return ValidatorService */ public static function make(array $data, array $rules, array $messages = array(), array $customAttributes = array()){ $instance = new ValidatorService( $data, $rules, $messages, $customAttributes); return $instance; } public function getCustomMessages() { return $this->customMessages; } /** * Set the custom messages for the validator * * @param array $messages * @return void */ public function setCustomMessages(array $messages) { $this->customMessages = array_merge($this->customMessages, $messages); } /** * Get the fallback messages for the validator. * * @return void */ public function getFallbackMessages() { return $this->fallbackMessages; } /** * Set the fallback messages for the validator. * * @param array $messages * @return void */ public function setFallbackMessages(array $messages) { $this->fallbackMessages = $messages; } /** * Get the failed validation rules. * * @return array */ public function failed() { return $this->failedRules; } /** * Explode the rules into an array of rules. * * @param string|array $rules * @return array */ protected function explodeRules($rules) { foreach ($rules as $key => &$rule) { $rule = (is_string($rule)) ? explode('|', $rule) : $rule; } return $rules; } protected function parseData(array $data) { $this->files = array(); foreach ($data as $key => $value) { // If this value is an instance of the HttpFoundation File class we will // remove it from the data array and add it to the files array, which // is used to conveniently separate out the files from other datas. if ($value instanceof File) { $this->files[$key] = $value; unset($data[$key]); } } return $data; } public function fails() { return ! $this->passes(); } public function passes() { $this->messages = array(); // We'll spin through each rule, validating the attributes attached to that // rule. Any error messages will be added to the containers with each of // the other error messages, returning true if we don't have messages. foreach ($this->rules as $attribute => $rules) { foreach ($rules as $rule) { $this->validate($attribute, $rule); } } return count($this->messages) === 0; } public function messages() { return $this->messages; } protected function validate($attribute, $rule) { if (trim($rule) == '') return; list($rule, $parameters) = $this->parseRule($rule); // We will get the value for the given attribute from the array of data and then // verify that the attribute is indeed validatable. Unless the rule implies // that the attribute is required, rules are not run for missing values. $value = $this->getValue($attribute); $validatable = $this->isValidatable($rule, $attribute, $value); $method = "validate{$rule}"; if ($validatable && ! $this->$method($attribute, $value, $parameters, $this)) { $this->addFailure($attribute, $rule, $parameters); } } protected function isImplicit($rule) { return in_array($rule, $this->implicitRules); } protected function addFailure($attribute, $rule, $parameters) { $this->addError($attribute, $rule, $parameters); $this->failedRules[$attribute][$rule] = $parameters; } protected function addError($attribute, $rule, $parameters) { $message = $this->getMessage($attribute, $rule); $message = $this->doReplacements($message, $attribute, $rule, $parameters); array_push($this->messages,array('attribute'=>$attribute,'message'=>$message)); } protected function getMessage($attribute, $rule) { $lowerRule = snake_case($rule); $inlineMessage = $this->getInlineMessage($attribute, $lowerRule); // First we will retrieve the custom message for the validation rule if one // exists. If a custom validation message is being used we'll return the // custom message, otherwise we'll keep searching for a valid message. if ( ! is_null($inlineMessage)) { return $inlineMessage; } // Finally, if no developer specified messages have been set, and no other // special messages apply for this rule, we will just pull the default // messages out of the translator service for this validation rule. $key = "validation.{$lowerRule}"; return $this->getInlineMessage( $attribute, $lowerRule, $this->fallbackMessages ) ?: $key; } protected function doReplacements($message, $attribute, $rule, $parameters) { $message = str_replace(':attribute', $this->getAttribute($attribute), $message); if (isset($this->replacers[snake_case($rule)])) { $message = $this->callReplacer($message, $attribute, snake_case($rule), $parameters); } elseif (method_exists($this, $replacer = "replace{$rule}")) { $message = $this->$replacer($message, $attribute, $rule, $parameters); } return $message; } /** * Call a custom validator message replacer. * * @param string $message * @param string $attribute * @param string $rule * @param array $parameters * @return string */ protected function callReplacer($message, $attribute, $rule, $parameters) { $callback = $this->replacers[$rule]; if ($callback instanceof Closure) { return call_user_func_array($callback, func_get_args()); } elseif (is_string($callback)) { return $this->callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters); } } /** * Call a class based validator message replacer. * * @param string $callback * @param string $message * @param string $attribute * @param string $rule * @param array $parameters * @return string */ protected function callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters) { list($class, $method) = explode('@', $callback); return call_user_func_array(array($this->container->make($class), $method), array_slice(func_get_args(), 1)); } /** * Get the displayable name of the attribute. * * @param string $attribute * @return string */ protected function getAttribute($attribute) { // The developer may dynamically specify the array of custom attributes // on this Validator instance. If the attribute exists in this array // it takes precedence over all other ways we can pull attributes. if (isset($this->customAttributes[$attribute])) { return $this->customAttributes[$attribute]; } return str_replace('_', ' ', $attribute); } protected function getInlineMessage($attribute, $lowerRule, $source = null) { $source = $source ?: $this->customMessages; $keys = array("{$attribute}.{$lowerRule}", $lowerRule); // First we will check for a custom message for an attribute specific rule // message for the fields, then we will check for a general custom line // that is not attribute specific. If we find either we'll return it. foreach ($keys as $key) { if (isset($source[$key])) return $source[$key]; } } protected function isValidatable($rule, $attribute, $value) { return $this->presentOrRuleIsImplicit($rule, $attribute, $value) && $this->passesOptionalCheck($attribute); } protected function presentOrRuleIsImplicit($rule, $attribute, $value) { return $this->validateRequired($attribute, $value) || $this->isImplicit($rule); } protected function passesOptionalCheck($attribute) { if ($this->hasRule($attribute, array('Sometimes'))) { return array_key_exists($attribute, $this->data) || array_key_exists($attribute, $this->files); } else { return true; } } protected function hasRule($attribute, $rules) { $rules = (array) $rules; // To determine if the attribute has a rule in the ruleset, we will spin // through each of the rules assigned to the attribute and parse them // all, then check to see if the parsed rules exists in the arrays. foreach ($this->rules[$attribute] as $rule) { list($rule, $parameters) = $this->parseRule($rule); if (in_array($rule, $rules)) return true; } return false; } protected function getValue($attribute) { if ( ! is_null($value = array_get($this->data, $attribute))) { return $value; } elseif ( ! is_null($value = array_get($this->files, $attribute))) { return $value; } } protected function parseRule($rule) { $parameters = array(); // The format for specifying validation rules and parameters follows an // easy {rule}:{parameters} formatting convention. For instance the // rule "Max:3" states that the value may only be three letters. if (strpos($rule, ':') !== false) { list($rule, $parameter) = explode(':', $rule, 2); $parameters = $this->parseParameters($rule, $parameter); } return array(studly_case($rule), $parameters); } protected function parseParameters($rule, $parameter) { if (strtolower($rule) == 'regex') return array($parameter); return str_getcsv($parameter); } protected function validateSometimes() { return true; } /** * Merge additional rules into a given attribute. * * @param string $attribute * @param string|array $rules * @return void */ public function mergeRules($attribute, $rules) { $current = array_get($this->rules, $attribute, array()); $merge = head($this->explodeRules(array($rules))); $this->rules[$attribute] = array_merge($current, $merge); } //validation methods /** * Validate that a required attribute exists. * * @param string $attribute * @param mixed $value * @return bool */ protected function validateRequired($attribute, $value) { if (is_null($value)) { return false; } elseif (is_string($value) && trim($value) === '') { return false; } elseif ($value instanceof File) { return (string) $value->getPath() != ''; } return true; } public function validateBoolean($attribute, $value, $parameters) { if(is_bool($value)) return true; if(is_int($value)) return true; return strtoupper(trim($value)) =='TRUE' || strtoupper(trim($value))=='FALSE' || strtoupper(trim($value))=='1' || strtoupper(trim($value))=='0' ; } public function validateText($attribute, $value, $parameters) { $value = trim($value); return preg_match("%[^<>]+$%i", $value) == 1; } public function validateHtmlText($attribute, $value, $parameters) { $value = trim($value); return true; } /** * Validate that an attribute is numeric. * * @param string $attribute * @param mixed $value * @return bool */ protected function validateNumeric($attribute, $value) { return is_numeric($value); } /** * Validate that an attribute is an integer. * * @param string $attribute * @param mixed $value * @return bool */ protected function validateInteger($attribute, $value) { return filter_var($value, FILTER_VALIDATE_INT) !== false; } /** * @param $attribute * @param $value * @return bool */ protected function validateFloat($attribute, $value){ return filter_var($value, FILTER_VALIDATE_FLOAT) !== false; } /** * Validate that an attribute is a valid date. * * @param string $attribute * @param mixed $value * @return bool */ protected function validateDate($attribute, $value) { if ($value instanceof DateTime) return true; if (strtotime($value) === false) return false; $date = date_parse($value); return checkdate($date['month'], $date['day'], $date['year']); } /** * Validate that an attribute matches a date format. * * @param string $attribute * @param mixed $value * @param array $parameters * @return bool */ protected function validateDateFormat($attribute, $value, $parameters) { $parsed = date_parse_from_format($parameters[0], $value); return $parsed['error_count'] === 0 && $parsed['warning_count'] === 0; } /** * Validate that an attribute is a valid URL. * * @param string $attribute * @param mixed $value * @return bool */ protected function validateUrl($attribute, $value) { $value = trim($value); $res = filter_var($value, FILTER_VALIDATE_URL); return $res!==FALSE; } public function validateHttpMethod($attribute, $value, $parameters){ $value = strtoupper(trim($value)); //'GET', 'HEAD','POST','PUT','DELETE','TRACE','CONNECT','OPTIONS' $allowed_http_verbs = array( 'GET'=>'GET', 'HEAD'=>'HEAD', 'POST'=>'POST', 'PUT'=>'PUT', 'DELETE'=>'DELETE', 'TRACE'=>'TRACE', 'CONNECT'=>'CONNECT', 'OPTIONS'=>'OPTIONS', ); return array_key_exists($value,$allowed_http_verbs); } public function validateRelativeUrl($attribute, $value, $parameters){ return true; } public function validateVersionStatus($attribute, $value, $parameters){ return true; } /** * Validate that an attribute is between a given number of digits. * * @param string $attribute * @param mixed $value * @param array $parameters * @return bool */ protected function validateDigitsBetween($attribute, $value, $parameters) { $length = strlen((string) $value); return $length >= $parameters[0] && $length <= $parameters[1]; } /** * Validate the size of an attribute. * * @param string $attribute * @param mixed $value * @param array $parameters * @return bool */ protected function validateSize($attribute, $value, $parameters) { return $this->getSize($attribute, $value) == $parameters[0]; } /** * Validate the size of an attribute is between a set of values. * * @param string $attribute * @param mixed $value * @param array $parameters * @return bool */ protected function validateBetween($attribute, $value, $parameters) { $size = $this->getSize($attribute, $value); return $size >= $parameters[0] && $size <= $parameters[1]; } /** * Validate the size of an attribute is greater than a minimum value. * * @param string $attribute * @param mixed $value * @param array $parameters * @return bool */ protected function validateMin($attribute, $value, $parameters) { return $this->getSize($attribute, $value) >= $parameters[0]; } /** * Validate the size of an attribute is less than a maximum value. * * @param string $attribute * @param mixed $value * @param array $parameters * @return bool */ protected function validateMax($attribute, $value, $parameters) { if ($value instanceof UploadedFile && ! $value->isValid()) return false; return $this->getSize($attribute, $value) <= $parameters[0]; } /** * Get the size of an attribute. * * @param string $attribute * @param mixed $value * @return mixed */ protected function getSize($attribute, $value) { $hasNumeric = $this->hasRule($attribute, $this->numericRules); // This method will determine if the attribute is a number, string, or file and // return the proper size accordingly. If it is a number, then number itself // is the size. If it is a file, we take kilobytes, and for a string the // entire length of the string will be considered the attribute size. if (is_numeric($value) && $hasNumeric) { return array_get($this->data, $attribute); } elseif (is_array($value)) { return count($value); } elseif ($value instanceof File) { return $value->getSize() / 1024; } else { return $this->getStringSize($value); } } /** * Get the size of a string. * * @param string $value * @return int */ protected function getStringSize($value) { if (function_exists('mb_strlen')) return mb_strlen($value); return strlen($value); } /** * Validate an attribute is contained within a list of values. * * @param string $attribute * @param mixed $value * @param array $parameters * @return bool */ protected function validateIn($attribute, $value, $parameters) { return in_array($value, $parameters); } /** * Validate an attribute is not contained within a list of values. * * @param string $attribute * @param mixed $value * @param array $parameters * @return bool */ protected function validateNotIn($attribute, $value, $parameters) { return ! in_array($value, $parameters); } /** * @param $attribute * @param $value * @param $parameters * @return bool */ protected function validateColor($attribute, $value, $parameters) { return preg_match('/^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/i',$value)==1 ; } /** * @param $attribute * @param $value * @param $parameters * @return bool */ protected function validateEmail($attribute, $value, $parameters) { return filter_var($value, FILTER_VALIDATE_EMAIL); } /** * Validate the date is after a given date. * * @param string $attribute * @param mixed $value * @param array $parameters * @return bool */ protected function validateAfter($attribute, $value, $parameters) { if (!($date = strtotime($parameters[0]))) { return strtotime($value) >= strtotime($this->getValue($parameters[0])); } else { return strtotime($value) >= $date; } } /** * Validate the date is before a given date. * * @param string $attribute * @param mixed $value * @param array $parameters * @return bool */ protected function validateBefore($attribute, $value, $parameters) { if ( ! ($date = strtotime($parameters[0]))) { return strtotime($value) <= strtotime($this->getValue($parameters[0])); } else { return strtotime($value) <= $date; } } }