initialRules = $rules; $this->translator = $translator; $this->customMessages = $messages; $this->data = $this->parseData($data); $this->customAttributes = $customAttributes; $this->setRules($rules); } /** * Parse the data array, converting dots to ->. * * @param array $data * @return array */ public function parseData(array $data) { $newData = []; foreach ($data as $key => $value) { if (is_array($value)) { $value = $this->parseData($value); } // If the data key contains a dot, we will replace it with another character // sequence so it doesn't interfere with dot processing when working with // array based validation rules and array_dot later in the validations. if (Str::contains($key, '.')) { $newData[str_replace('.', '->', $key)] = $value; } else { $newData[$key] = $value; } } return $newData; } /** * Add an after validation callback. * * @param callable|string $callback * @return $this */ public function after($callback) { $this->after[] = function () use ($callback) { return call_user_func_array($callback, [$this]); }; return $this; } /** * Determine if the data passes the validation rules. * * @return bool */ public function passes() { $this->messages = new MessageBag; // 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) { $attribute = str_replace('\.', '->', $attribute); foreach ($rules as $rule) { $this->validateAttribute($attribute, $rule); if ($this->shouldStopValidating($attribute)) { break; } } } // Here we will spin through all of the "after" hooks on this validator and // fire them off. This gives the callbacks a chance to perform all kinds // of other validation that needs to get wrapped up in this operation. foreach ($this->after as $after) { call_user_func($after); } return $this->messages->isEmpty(); } /** * Determine if the data fails the validation rules. * * @return bool */ public function fails() { return ! $this->passes(); } /** * Run the validator's rules against its data. * * @return array * * @throws \Illuminate\Validation\ValidationException */ public function validate() { if ($this->fails()) { throw new ValidationException($this); } $data = collect($this->getData()); return $data->only(collect($this->getRules())->keys()->map(function ($rule) { return explode('.', $rule)[0]; })->unique())->toArray(); } /** * Validate a given attribute against a rule. * * @param string $attribute * @param string $rule * @return void */ protected function validateAttribute($attribute, $rule) { $this->currentRule = $rule; list($rule, $parameters) = ValidationRuleParser::parse($rule); if ($rule == '') { return; } // First we will get the correct keys for the given attribute in case the field is nested in // an array. Then we determine if the given rule accepts other field names as parameters. // If so, we will replace any asterisks found in the parameters with the correct keys. if (($keys = $this->getExplicitKeys($attribute)) && $this->dependsOnOtherFields($rule)) { $parameters = $this->replaceAsterisksInParameters($parameters, $keys); } $value = $this->getValue($attribute); // If the attribute is a file, we will verify that the file upload was actually successful // and if it wasn't we will add a failure for the attribute. Files may not successfully // upload if they are too large based on PHP's settings so we will bail in this case. if ($value instanceof UploadedFile && ! $value->isValid() && $this->hasRule($attribute, array_merge($this->fileRules, $this->implicitRules)) ) { return $this->addFailure($attribute, 'uploaded', []); } // If we have made it this far we will make sure the attribute is validatable and if it is // we will call the validation method with the attribute. If a method returns false the // attribute is invalid and we will add a failure message for this failing attribute. $validatable = $this->isValidatable($rule, $attribute, $value); if ($rule instanceof RuleContract) { return $validatable ? $this->validateUsingCustomRule($attribute, $value, $rule) : null; } $method = "validate{$rule}"; if ($validatable && ! $this->$method($attribute, $value, $parameters, $this)) { $this->addFailure($attribute, $rule, $parameters); } } /** * Determine if the given rule depends on other fields. * * @param string $rule * @return bool */ protected function dependsOnOtherFields($rule) { return in_array($rule, $this->dependentRules); } /** * Get the explicit keys from an attribute flattened with dot notation. * * E.g. 'foo.1.bar.spark.baz' -> [1, 'spark'] for 'foo.*.bar.*.baz' * * @param string $attribute * @return array */ protected function getExplicitKeys($attribute) { $pattern = str_replace('\*', '([^\.]+)', preg_quote($this->getPrimaryAttribute($attribute), '/')); if (preg_match('/^'.$pattern.'/', $attribute, $keys)) { array_shift($keys); return $keys; } return []; } /** * Get the primary attribute name. * * For example, if "name.0" is given, "name.*" will be returned. * * @param string $attribute * @return string */ protected function getPrimaryAttribute($attribute) { foreach ($this->implicitAttributes as $unparsed => $parsed) { if (in_array($attribute, $parsed)) { return $unparsed; } } return $attribute; } /** * Replace each field parameter which has asterisks with the given keys. * * @param array $parameters * @param array $keys * @return array */ protected function replaceAsterisksInParameters(array $parameters, array $keys) { return array_map(function ($field) use ($keys) { return vsprintf(str_replace('*', '%s', $field), $keys); }, $parameters); } /** * Determine if the attribute is validatable. * * @param object|string $rule * @param string $attribute * @param mixed $value * @return bool */ protected function isValidatable($rule, $attribute, $value) { return $this->presentOrRuleIsImplicit($rule, $attribute, $value) && $this->passesOptionalCheck($attribute) && $this->isNotNullIfMarkedAsNullable($rule, $attribute) && $this->hasNotFailedPreviousRuleIfPresenceRule($rule, $attribute); } /** * Determine if the field is present, or the rule implies required. * * @param object|string $rule * @param string $attribute * @param mixed $value * @return bool */ protected function presentOrRuleIsImplicit($rule, $attribute, $value) { if (is_string($value) && trim($value) === '') { return $this->isImplicit($rule); } return $this->validatePresent($attribute, $value) || $this->isImplicit($rule); } /** * Determine if a given rule implies the attribute is required. * * @param object|string $rule * @return bool */ protected function isImplicit($rule) { return $rule instanceof ImplicitRule || in_array($rule, $this->implicitRules); } /** * Determine if the attribute passes any optional check. * * @param string $attribute * @return bool */ protected function passesOptionalCheck($attribute) { if (! $this->hasRule($attribute, ['Sometimes'])) { return true; } $data = ValidationData::initializeAndGatherData($attribute, $this->data); return array_key_exists($attribute, $data) || array_key_exists($attribute, $this->data); } /** * Determine if the attribute fails the nullable check. * * @param string $rule * @param string $attribute * @return bool */ protected function isNotNullIfMarkedAsNullable($rule, $attribute) { if ($this->isImplicit($rule) || ! $this->hasRule($attribute, ['Nullable'])) { return true; } return ! is_null(Arr::get($this->data, $attribute, 0)); } /** * Determine if it's a necessary presence validation. * * This is to avoid possible database type comparison errors. * * @param string $rule * @param string $attribute * @return bool */ protected function hasNotFailedPreviousRuleIfPresenceRule($rule, $attribute) { return in_array($rule, ['Unique', 'Exists']) ? ! $this->messages->has($attribute) : true; } /** * Validate an attribute using a custom rule object. * * @param string $attribute * @param mixed $value * @param \Illuminate\Contracts\Validation\Rule $rule * @return void */ protected function validateUsingCustomRule($attribute, $value, $rule) { if (! $rule->passes($attribute, $value)) { $this->failedRules[$attribute][get_class($rule)] = []; $this->messages->add($attribute, $this->makeReplacements( $rule->message(), $attribute, get_class($rule), [] )); } } /** * Check if we should stop further validations on a given attribute. * * @param string $attribute * @return bool */ protected function shouldStopValidating($attribute) { if ($this->hasRule($attribute, ['Bail'])) { return $this->messages->has($attribute); } if (isset($this->failedRules[$attribute]) && array_key_exists('uploaded', $this->failedRules[$attribute])) { return true; } // In case the attribute has any rule that indicates that the field is required // and that rule already failed then we should stop validation at this point // as now there is no point in calling other rules with this field empty. return $this->hasRule($attribute, $this->implicitRules) && isset($this->failedRules[$attribute]) && array_intersect(array_keys($this->failedRules[$attribute]), $this->implicitRules); } /** * Add a failed rule and error message to the collection. * * @param string $attribute * @param string $rule * @param array $parameters * @return void */ protected function addFailure($attribute, $rule, $parameters) { $this->messages->add($attribute, $this->makeReplacements( $this->getMessage($attribute, $rule), $attribute, $rule, $parameters )); $this->failedRules[$attribute][$rule] = $parameters; } /** * Returns the data which was valid. * * @return array */ public function valid() { if (! $this->messages) { $this->passes(); } return array_diff_key( $this->data, $this->attributesThatHaveMessages() ); } /** * Returns the data which was invalid. * * @return array */ public function invalid() { if (! $this->messages) { $this->passes(); } return array_intersect_key( $this->data, $this->attributesThatHaveMessages() ); } /** * Generate an array of all attributes that have messages. * * @return array */ protected function attributesThatHaveMessages() { return collect($this->messages()->toArray())->map(function ($message, $key) { return explode('.', $key)[0]; })->unique()->flip()->all(); } /** * Get the failed validation rules. * * @return array */ public function failed() { return $this->failedRules; } /** * Get the message container for the validator. * * @return \Illuminate\Support\MessageBag */ public function messages() { if (! $this->messages) { $this->passes(); } return $this->messages; } /** * An alternative more semantic shortcut to the message container. * * @return \Illuminate\Support\MessageBag */ public function errors() { return $this->messages(); } /** * Get the messages for the instance. * * @return \Illuminate\Support\MessageBag */ public function getMessageBag() { return $this->messages(); } /** * Determine if the given attribute has a rule in the given set. * * @param string $attribute * @param string|array $rules * @return bool */ public function hasRule($attribute, $rules) { return ! is_null($this->getRule($attribute, $rules)); } /** * Get a rule and its parameters for a given attribute. * * @param string $attribute * @param string|array $rules * @return array|null */ protected function getRule($attribute, $rules) { if (! array_key_exists($attribute, $this->rules)) { return; } $rules = (array) $rules; foreach ($this->rules[$attribute] as $rule) { list($rule, $parameters) = ValidationRuleParser::parse($rule); if (in_array($rule, $rules)) { return [$rule, $parameters]; } } } /** * Get the data under validation. * * @return array */ public function attributes() { return $this->getData(); } /** * Get the data under validation. * * @return array */ public function getData() { return $this->data; } /** * Set the data under validation. * * @param array $data * @return $this */ public function setData(array $data) { $this->data = $this->parseData($data); $this->setRules($this->initialRules); return $this; } /** * Get the value of a given attribute. * * @param string $attribute * @return mixed */ protected function getValue($attribute) { return Arr::get($this->data, $attribute); } /** * Get the validation rules. * * @return array */ public function getRules() { return $this->rules; } /** * Set the validation rules. * * @param array $rules * @return $this */ public function setRules(array $rules) { $this->initialRules = $rules; $this->rules = []; $this->addRules($rules); return $this; } /** * Parse the given rules and merge them into current rules. * * @param array $rules * @return void */ public function addRules($rules) { // The primary purpose of this parser is to expand any "*" rules to the all // of the explicit rules needed for the given data. For example the rule // names.* would get expanded to names.0, names.1, etc. for this data. $response = (new ValidationRuleParser($this->data)) ->explode($rules); $this->rules = array_merge_recursive( $this->rules, $response->rules ); $this->implicitAttributes = array_merge( $this->implicitAttributes, $response->implicitAttributes ); } /** * Add conditions to a given field based on a Closure. * * @param string|array $attribute * @param string|array $rules * @param callable $callback * @return $this */ public function sometimes($attribute, $rules, callable $callback) { $payload = new Fluent($this->getData()); if (call_user_func($callback, $payload)) { foreach ((array) $attribute as $key) { $this->addRules([$key => $rules]); } } return $this; } /** * Register an array of custom validator extensions. * * @param array $extensions * @return void */ public function addExtensions(array $extensions) { if ($extensions) { $keys = array_map('\Illuminate\Support\Str::snake', array_keys($extensions)); $extensions = array_combine($keys, array_values($extensions)); } $this->extensions = array_merge($this->extensions, $extensions); } /** * Register an array of custom implicit validator extensions. * * @param array $extensions * @return void */ public function addImplicitExtensions(array $extensions) { $this->addExtensions($extensions); foreach ($extensions as $rule => $extension) { $this->implicitRules[] = Str::studly($rule); } } /** * Register an array of custom implicit validator extensions. * * @param array $extensions * @return void */ public function addDependentExtensions(array $extensions) { $this->addExtensions($extensions); foreach ($extensions as $rule => $extension) { $this->dependentRules[] = Str::studly($rule); } } /** * Register a custom validator extension. * * @param string $rule * @param \Closure|string $extension * @return void */ public function addExtension($rule, $extension) { $this->extensions[Str::snake($rule)] = $extension; } /** * Register a custom implicit validator extension. * * @param string $rule * @param \Closure|string $extension * @return void */ public function addImplicitExtension($rule, $extension) { $this->addExtension($rule, $extension); $this->implicitRules[] = Str::studly($rule); } /** * Register a custom dependent validator extension. * * @param string $rule * @param \Closure|string $extension * @return void */ public function addDependentExtension($rule, $extension) { $this->addExtension($rule, $extension); $this->dependentRules[] = Str::studly($rule); } /** * Register an array of custom validator message replacers. * * @param array $replacers * @return void */ public function addReplacers(array $replacers) { if ($replacers) { $keys = array_map('\Illuminate\Support\Str::snake', array_keys($replacers)); $replacers = array_combine($keys, array_values($replacers)); } $this->replacers = array_merge($this->replacers, $replacers); } /** * Register a custom validator message replacer. * * @param string $rule * @param \Closure|string $replacer * @return void */ public function addReplacer($rule, $replacer) { $this->replacers[Str::snake($rule)] = $replacer; } /** * Set the custom messages for the validator. * * @param array $messages * @return $this */ public function setCustomMessages(array $messages) { $this->customMessages = array_merge($this->customMessages, $messages); return $this; } /** * Set the custom attributes on the validator. * * @param array $attributes * @return $this */ public function setAttributeNames(array $attributes) { $this->customAttributes = $attributes; return $this; } /** * Add custom attributes to the validator. * * @param array $customAttributes * @return $this */ public function addCustomAttributes(array $customAttributes) { $this->customAttributes = array_merge($this->customAttributes, $customAttributes); return $this; } /** * Set the custom values on the validator. * * @param array $values * @return $this */ public function setValueNames(array $values) { $this->customValues = $values; return $this; } /** * Add the custom values for the validator. * * @param array $customValues * @return $this */ public function addCustomValues(array $customValues) { $this->customValues = array_merge($this->customValues, $customValues); return $this; } /** * Set the fallback messages for the validator. * * @param array $messages * @return void */ public function setFallbackMessages(array $messages) { $this->fallbackMessages = $messages; } /** * Get the Presence Verifier implementation. * * @return \Illuminate\Validation\PresenceVerifierInterface * * @throws \RuntimeException */ public function getPresenceVerifier() { if (! isset($this->presenceVerifier)) { throw new RuntimeException('Presence verifier has not been set.'); } return $this->presenceVerifier; } /** * Get the Presence Verifier implementation. * * @param string $connection * @return \Illuminate\Validation\PresenceVerifierInterface * * @throws \RuntimeException */ protected function getPresenceVerifierFor($connection) { return tap($this->getPresenceVerifier(), function ($verifier) use ($connection) { $verifier->setConnection($connection); }); } /** * Set the Presence Verifier implementation. * * @param \Illuminate\Validation\PresenceVerifierInterface $presenceVerifier * @return void */ public function setPresenceVerifier(PresenceVerifierInterface $presenceVerifier) { $this->presenceVerifier = $presenceVerifier; } /** * Get the Translator implementation. * * @return \Illuminate\Contracts\Translation\Translator */ public function getTranslator() { return $this->translator; } /** * Set the Translator implementation. * * @param \Illuminate\Contracts\Translation\Translator $translator * @return void */ public function setTranslator(Translator $translator) { $this->translator = $translator; } /** * Set the IoC container instance. * * @param \Illuminate\Contracts\Container\Container $container * @return void */ public function setContainer(Container $container) { $this->container = $container; } /** * Call a custom validator extension. * * @param string $rule * @param array $parameters * @return bool|null */ protected function callExtension($rule, $parameters) { $callback = $this->extensions[$rule]; if (is_callable($callback)) { return call_user_func_array($callback, $parameters); } elseif (is_string($callback)) { return $this->callClassBasedExtension($callback, $parameters); } } /** * Call a class based validator extension. * * @param string $callback * @param array $parameters * @return bool */ protected function callClassBasedExtension($callback, $parameters) { list($class, $method) = Str::parseCallback($callback, 'validate'); return call_user_func_array([$this->container->make($class), $method], $parameters); } /** * Handle dynamic calls to class methods. * * @param string $method * @param array $parameters * @return mixed * * @throws \BadMethodCallException */ public function __call($method, $parameters) { $rule = Str::snake(substr($method, 8)); if (isset($this->extensions[$rule])) { return $this->callExtension($rule, $parameters); } throw new BadMethodCallException(sprintf( 'Method %s::%s does not exist.', static::class, $method )); } }