Revision 148 | Blame | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed
<?phpnamespace Illuminate\Validation\Concerns;use Closure;use Illuminate\Support\Arr;use Illuminate\Support\Str;use Symfony\Component\HttpFoundation\File\UploadedFile;trait FormatsMessages{use ReplacesAttributes;/*** Get the validation message for an attribute and rule.** @param string $attribute* @param string $rule* @return string*/protected function getMessage($attribute, $rule){$attributeWithPlaceholders = $attribute;$attribute = $this->replacePlaceholderInString($attribute);$inlineMessage = $this->getInlineMessage($attribute, $rule);// 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;}$lowerRule = Str::snake($rule);$customKey = "validation.custom.{$attribute}.{$lowerRule}";$customMessage = $this->getCustomMessageFromTranslator(in_array($rule, $this->sizeRules)? [$customKey.".{$this->getAttributeType($attribute)}", $customKey]: $customKey);// First we check for a custom defined validation message for the attribute// and rule. This allows the developer to specify specific messages for// only some attributes and rules that need to get specially formed.if ($customMessage !== $customKey) {return $customMessage;}// If the rule being validated is a "size" rule, we will need to gather the// specific error message for the type of attribute being validated such// as a number, file or string which all have different message types.elseif (in_array($rule, $this->sizeRules)) {return $this->getSizeMessage($attributeWithPlaceholders, $rule);}// 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}";if ($key !== ($value = $this->translator->get($key))) {return $value;}return $this->getFromLocalArray($attribute, $lowerRule, $this->fallbackMessages) ?: $key;}/*** Get the proper inline error message for standard and size rules.** @param string $attribute* @param string $rule* @return string|null*/protected function getInlineMessage($attribute, $rule){$inlineEntry = $this->getFromLocalArray($attribute, Str::snake($rule));return is_array($inlineEntry) && in_array($rule, $this->sizeRules)? $inlineEntry[$this->getAttributeType($attribute)]: $inlineEntry;}/*** Get the inline message for a rule if it exists.** @param string $attribute* @param string $lowerRule* @param array|null $source* @return string|null*/protected function getFromLocalArray($attribute, $lowerRule, $source = null){$source = $source ?: $this->customMessages;$keys = ["{$attribute}.{$lowerRule}", $lowerRule, $attribute];// 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) {foreach (array_keys($source) as $sourceKey) {if (str_contains($sourceKey, '*')) {$pattern = str_replace('\*', '([^.]*)', preg_quote($sourceKey, '#'));if (preg_match('#^'.$pattern.'\z#u', $key) === 1) {$message = $source[$sourceKey];if (is_array($message) && isset($message[$lowerRule])) {return $message[$lowerRule];}return $message;}continue;}if (Str::is($sourceKey, $key)) {$message = $source[$sourceKey];if ($sourceKey === $attribute && is_array($message) && isset($message[$lowerRule])) {return $message[$lowerRule];}return $message;}}}}/*** Get the custom error message from the translator.** @param array|string $keys* @return string*/protected function getCustomMessageFromTranslator($keys){foreach (Arr::wrap($keys) as $key) {if (($message = $this->translator->get($key)) !== $key) {return $message;}// If an exact match was not found for the key, we will collapse all of these// messages and loop through them and try to find a wildcard match for the// given key. Otherwise, we will simply return the key's value back out.$shortKey = preg_replace('/^validation\.custom\./', '', $key);$message = $this->getWildcardCustomMessages(Arr::dot((array) $this->translator->get('validation.custom')), $shortKey, $key);if ($message !== $key) {return $message;}}return Arr::last(Arr::wrap($keys));}/*** Check the given messages for a wildcard key.** @param array $messages* @param string $search* @param string $default* @return string*/protected function getWildcardCustomMessages($messages, $search, $default){foreach ($messages as $key => $message) {if ($search === $key || (Str::contains($key, ['*']) && Str::is($key, $search))) {return $message;}}return $default;}/*** Get the proper error message for an attribute and size rule.** @param string $attribute* @param string $rule* @return string*/protected function getSizeMessage($attribute, $rule){$lowerRule = Str::snake($rule);// There are three different types of size validations. The attribute may be// either a number, file, or string so we will check a few things to know// which type of value it is and return the correct line for that type.$type = $this->getAttributeType($attribute);$key = "validation.{$lowerRule}.{$type}";return $this->translator->get($key);}/*** Get the data type of the given attribute.** @param string $attribute* @return string*/protected function getAttributeType($attribute){// We assume that the attributes present in the file array are files so that// means that if the attribute does not have a numeric rule and the files// list doesn't have it we'll just consider it a string by elimination.if ($this->hasRule($attribute, $this->numericRules)) {return 'numeric';} elseif ($this->hasRule($attribute, ['Array'])) {return 'array';} elseif ($this->getValue($attribute) instanceof UploadedFile) {return 'file';}return 'string';}/*** Replace all error message place-holders with actual values.** @param string $message* @param string $attribute* @param string $rule* @param array $parameters* @return string*/public function makeReplacements($message, $attribute, $rule, $parameters){$message = $this->replaceAttributePlaceholder($message, $this->getDisplayableAttribute($attribute));$message = $this->replaceInputPlaceholder($message, $attribute);$message = $this->replaceIndexPlaceholder($message, $attribute);$message = $this->replacePositionPlaceholder($message, $attribute);if (isset($this->replacers[Str::snake($rule)])) {return $this->callReplacer($message, $attribute, Str::snake($rule), $parameters, $this);} elseif (method_exists($this, $replacer = "replace{$rule}")) {return $this->$replacer($message, $attribute, $rule, $parameters);}return $message;}/*** Get the displayable name of the attribute.** @param string $attribute* @return string*/public function getDisplayableAttribute($attribute){$primaryAttribute = $this->getPrimaryAttribute($attribute);$expectedAttributes = $attribute != $primaryAttribute? [$attribute, $primaryAttribute] : [$attribute];foreach ($expectedAttributes as $name) {// The developer may dynamically specify the array of custom attributes on this// validator instance. If the attribute exists in this array it is used over// the other ways of pulling the attribute name for this given attributes.if (isset($this->customAttributes[$name])) {return $this->customAttributes[$name];}// We allow for a developer to specify language lines for any attribute in this// application, which allows flexibility for displaying a unique displayable// version of the attribute name instead of the name used in an HTTP POST.if ($line = $this->getAttributeFromTranslations($name)) {return $line;}}// When no language line has been specified for the attribute and it is also// an implicit attribute we will display the raw attribute's name and not// modify it with any of these replacements before we display the name.if (isset($this->implicitAttributes[$primaryAttribute])) {return ($formatter = $this->implicitAttributesFormatter)? $formatter($attribute): $attribute;}return str_replace('_', ' ', Str::snake($attribute));}/*** Get the given attribute from the attribute translations.** @param string $name* @return string*/protected function getAttributeFromTranslations($name){return Arr::get($this->translator->get('validation.attributes'), $name);}/*** Replace the :attribute placeholder in the given message.** @param string $message* @param string $value* @return string*/protected function replaceAttributePlaceholder($message, $value){return str_replace([':attribute', ':ATTRIBUTE', ':Attribute'],[$value, Str::upper($value), Str::ucfirst($value)],$message);}/*** Replace the :index placeholder in the given message.** @param string $message* @param string $attribute* @return string*/protected function replaceIndexPlaceholder($message, $attribute){return $this->replaceIndexOrPositionPlaceholder($message, $attribute, 'index');}/*** Replace the :position placeholder in the given message.** @param string $message* @param string $attribute* @return string*/protected function replacePositionPlaceholder($message, $attribute){return $this->replaceIndexOrPositionPlaceholder($message, $attribute, 'position', fn ($segment) => $segment + 1);}/*** Replace the :index or :position placeholder in the given message.** @param string $message* @param string $attribute* @param string $placeholder* @param \Closure|null $modifier* @return string*/protected function replaceIndexOrPositionPlaceholder($message, $attribute, $placeholder, Closure $modifier = null){$segments = explode('.', $attribute);$modifier ??= fn ($value) => $value;$numericIndex = 1;foreach ($segments as $segment) {if (is_numeric($segment)) {if ($numericIndex === 1) {$message = str_ireplace(':'.$placeholder, $modifier((int) $segment), $message);}$message = str_ireplace(':'.$this->numberToIndexOrPositionWord($numericIndex).'-'.$placeholder,$modifier((int) $segment),$message);$numericIndex++;}}return $message;}/*** Get the word for a index or position segment.** @param int $value* @return string*/protected function numberToIndexOrPositionWord(int $value){return [1 => 'first',2 => 'second',3 => 'third',4 => 'fourth',5 => 'fifth',6 => 'sixth',7 => 'seventh',8 => 'eighth',9 => 'ninth',10 => 'tenth',][(int) $value] ?? 'other';}/*** Replace the :input placeholder in the given message.** @param string $message* @param string $attribute* @return string*/protected function replaceInputPlaceholder($message, $attribute){$actualValue = $this->getValue($attribute);if (is_scalar($actualValue) || is_null($actualValue)) {$message = str_replace(':input', $this->getDisplayableValue($attribute, $actualValue), $message);}return $message;}/*** Get the displayable name of the value.** @param string $attribute* @param mixed $value* @return string*/public function getDisplayableValue($attribute, $value){if (isset($this->customValues[$attribute][$value])) {return $this->customValues[$attribute][$value];}$key = "validation.values.{$attribute}.{$value}";if (($line = $this->translator->get($key)) !== $key) {return $line;}if (is_bool($value)) {return $value ? 'true' : 'false';}if (is_null($value)) {return 'empty';}return (string) $value;}/*** Transform an array of attributes to their displayable form.** @param array $values* @return array*/protected function getAttributeList(array $values){$attributes = [];// For each attribute in the list we will simply get its displayable form as// this is convenient when replacing lists of parameters like some of the// replacement functions do when formatting out the validation message.foreach ($values as $key => $value) {$attributes[$key] = $this->getDisplayableAttribute($value);}return $attributes;}/*** Call a custom validator message replacer.** @param string $message* @param string $attribute* @param string $rule* @param array $parameters* @param \Illuminate\Validation\Validator $validator* @return string|null*/protected function callReplacer($message, $attribute, $rule, $parameters, $validator){$callback = $this->replacers[$rule];if ($callback instanceof Closure) {return $callback(...func_get_args());} elseif (is_string($callback)) {return $this->callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters, $validator);}}/*** Call a class based validator message replacer.** @param string $callback* @param string $message* @param string $attribute* @param string $rule* @param array $parameters* @param \Illuminate\Validation\Validator $validator* @return string*/protected function callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters, $validator){[$class, $method] = Str::parseCallback($callback, 'replace');return $this->container->make($class)->{$method}(...array_slice(func_get_args(), 1));}}