FormatsMessages.php 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. <?php
  2. namespace Illuminate\Validation\Concerns;
  3. use Closure;
  4. use Illuminate\Support\Arr;
  5. use Illuminate\Support\Str;
  6. use Symfony\Component\HttpFoundation\File\UploadedFile;
  7. trait FormatsMessages
  8. {
  9. use ReplacesAttributes;
  10. /**
  11. * Get the validation message for an attribute and rule.
  12. *
  13. * @param string $attribute
  14. * @param string $rule
  15. * @return string
  16. */
  17. protected function getMessage($attribute, $rule)
  18. {
  19. $inlineMessage = $this->getInlineMessage($attribute, $rule);
  20. // First we will retrieve the custom message for the validation rule if one
  21. // exists. If a custom validation message is being used we'll return the
  22. // custom message, otherwise we'll keep searching for a valid message.
  23. if (! is_null($inlineMessage)) {
  24. return $inlineMessage;
  25. }
  26. $lowerRule = Str::snake($rule);
  27. $customMessage = $this->getCustomMessageFromTranslator(
  28. $customKey = "validation.custom.{$attribute}.{$lowerRule}"
  29. );
  30. // First we check for a custom defined validation message for the attribute
  31. // and rule. This allows the developer to specify specific messages for
  32. // only some attributes and rules that need to get specially formed.
  33. if ($customMessage !== $customKey) {
  34. return $customMessage;
  35. }
  36. // If the rule being validated is a "size" rule, we will need to gather the
  37. // specific error message for the type of attribute being validated such
  38. // as a number, file or string which all have different message types.
  39. elseif (in_array($rule, $this->sizeRules)) {
  40. return $this->getSizeMessage($attribute, $rule);
  41. }
  42. // Finally, if no developer specified messages have been set, and no other
  43. // special messages apply for this rule, we will just pull the default
  44. // messages out of the translator service for this validation rule.
  45. $key = "validation.{$lowerRule}";
  46. if ($key != ($value = $this->translator->trans($key))) {
  47. return $value;
  48. }
  49. return $this->getFromLocalArray(
  50. $attribute, $lowerRule, $this->fallbackMessages
  51. ) ?: $key;
  52. }
  53. /**
  54. * Get the proper inline error message for standard and size rules.
  55. *
  56. * @param string $attribute
  57. * @param string $rule
  58. * @return string|null
  59. */
  60. protected function getInlineMessage($attribute, $rule)
  61. {
  62. $inlineEntry = $this->getFromLocalArray($attribute, Str::snake($rule));
  63. return is_array($inlineEntry) && in_array($rule, $this->sizeRules)
  64. ? $inlineEntry[$this->getAttributeType($attribute)]
  65. : $inlineEntry;
  66. }
  67. /**
  68. * Get the inline message for a rule if it exists.
  69. *
  70. * @param string $attribute
  71. * @param string $lowerRule
  72. * @param array|null $source
  73. * @return string|null
  74. */
  75. protected function getFromLocalArray($attribute, $lowerRule, $source = null)
  76. {
  77. $source = $source ?: $this->customMessages;
  78. $keys = ["{$attribute}.{$lowerRule}", $lowerRule];
  79. // First we will check for a custom message for an attribute specific rule
  80. // message for the fields, then we will check for a general custom line
  81. // that is not attribute specific. If we find either we'll return it.
  82. foreach ($keys as $key) {
  83. foreach (array_keys($source) as $sourceKey) {
  84. if (Str::is($sourceKey, $key)) {
  85. return $source[$sourceKey];
  86. }
  87. }
  88. }
  89. }
  90. /**
  91. * Get the custom error message from translator.
  92. *
  93. * @param string $key
  94. * @return string
  95. */
  96. protected function getCustomMessageFromTranslator($key)
  97. {
  98. if (($message = $this->translator->trans($key)) !== $key) {
  99. return $message;
  100. }
  101. // If an exact match was not found for the key, we will collapse all of these
  102. // messages and loop through them and try to find a wildcard match for the
  103. // given key. Otherwise, we will simply return the key's value back out.
  104. $shortKey = preg_replace(
  105. '/^validation\.custom\./', '', $key
  106. );
  107. return $this->getWildcardCustomMessages(Arr::dot(
  108. (array) $this->translator->trans('validation.custom')
  109. ), $shortKey, $key);
  110. }
  111. /**
  112. * Check the given messages for a wildcard key.
  113. *
  114. * @param array $messages
  115. * @param string $search
  116. * @param string $default
  117. * @return string
  118. */
  119. protected function getWildcardCustomMessages($messages, $search, $default)
  120. {
  121. foreach ($messages as $key => $message) {
  122. if ($search === $key || (Str::contains($key, ['*']) && Str::is($key, $search))) {
  123. return $message;
  124. }
  125. }
  126. return $default;
  127. }
  128. /**
  129. * Get the proper error message for an attribute and size rule.
  130. *
  131. * @param string $attribute
  132. * @param string $rule
  133. * @return string
  134. */
  135. protected function getSizeMessage($attribute, $rule)
  136. {
  137. $lowerRule = Str::snake($rule);
  138. // There are three different types of size validations. The attribute may be
  139. // either a number, file, or string so we will check a few things to know
  140. // which type of value it is and return the correct line for that type.
  141. $type = $this->getAttributeType($attribute);
  142. $key = "validation.{$lowerRule}.{$type}";
  143. return $this->translator->trans($key);
  144. }
  145. /**
  146. * Get the data type of the given attribute.
  147. *
  148. * @param string $attribute
  149. * @return string
  150. */
  151. protected function getAttributeType($attribute)
  152. {
  153. // We assume that the attributes present in the file array are files so that
  154. // means that if the attribute does not have a numeric rule and the files
  155. // list doesn't have it we'll just consider it a string by elimination.
  156. if ($this->hasRule($attribute, $this->numericRules)) {
  157. return 'numeric';
  158. } elseif ($this->hasRule($attribute, ['Array'])) {
  159. return 'array';
  160. } elseif ($this->getValue($attribute) instanceof UploadedFile) {
  161. return 'file';
  162. }
  163. return 'string';
  164. }
  165. /**
  166. * Replace all error message place-holders with actual values.
  167. *
  168. * @param string $message
  169. * @param string $attribute
  170. * @param string $rule
  171. * @param array $parameters
  172. * @return string
  173. */
  174. public function makeReplacements($message, $attribute, $rule, $parameters)
  175. {
  176. $message = $this->replaceAttributePlaceholder(
  177. $message, $this->getDisplayableAttribute($attribute)
  178. );
  179. $message = $this->replaceInputPlaceholder($message, $attribute);
  180. if (isset($this->replacers[Str::snake($rule)])) {
  181. return $this->callReplacer($message, $attribute, Str::snake($rule), $parameters, $this);
  182. } elseif (method_exists($this, $replacer = "replace{$rule}")) {
  183. return $this->$replacer($message, $attribute, $rule, $parameters);
  184. }
  185. return $message;
  186. }
  187. /**
  188. * Get the displayable name of the attribute.
  189. *
  190. * @param string $attribute
  191. * @return string
  192. */
  193. public function getDisplayableAttribute($attribute)
  194. {
  195. $primaryAttribute = $this->getPrimaryAttribute($attribute);
  196. $expectedAttributes = $attribute != $primaryAttribute
  197. ? [$attribute, $primaryAttribute] : [$attribute];
  198. foreach ($expectedAttributes as $name) {
  199. // The developer may dynamically specify the array of custom attributes on this
  200. // validator instance. If the attribute exists in this array it is used over
  201. // the other ways of pulling the attribute name for this given attributes.
  202. if (isset($this->customAttributes[$name])) {
  203. return $this->customAttributes[$name];
  204. }
  205. // We allow for a developer to specify language lines for any attribute in this
  206. // application, which allows flexibility for displaying a unique displayable
  207. // version of the attribute name instead of the name used in an HTTP POST.
  208. if ($line = $this->getAttributeFromTranslations($name)) {
  209. return $line;
  210. }
  211. }
  212. // When no language line has been specified for the attribute and it is also
  213. // an implicit attribute we will display the raw attribute's name and not
  214. // modify it with any of these replacements before we display the name.
  215. if (isset($this->implicitAttributes[$primaryAttribute])) {
  216. return $attribute;
  217. }
  218. return str_replace('_', ' ', Str::snake($attribute));
  219. }
  220. /**
  221. * Get the given attribute from the attribute translations.
  222. *
  223. * @param string $name
  224. * @return string
  225. */
  226. protected function getAttributeFromTranslations($name)
  227. {
  228. return Arr::get($this->translator->trans('validation.attributes'), $name);
  229. }
  230. /**
  231. * Replace the :attribute placeholder in the given message.
  232. *
  233. * @param string $message
  234. * @param string $value
  235. * @return string
  236. */
  237. protected function replaceAttributePlaceholder($message, $value)
  238. {
  239. return str_replace(
  240. [':attribute', ':ATTRIBUTE', ':Attribute'],
  241. [$value, Str::upper($value), Str::ucfirst($value)],
  242. $message
  243. );
  244. }
  245. /**
  246. * Replace the :input placeholder in the given message.
  247. *
  248. * @param string $message
  249. * @param string $attribute
  250. * @return string
  251. */
  252. protected function replaceInputPlaceholder($message, $attribute)
  253. {
  254. $actualValue = $this->getValue($attribute);
  255. if (is_scalar($actualValue) || is_null($actualValue)) {
  256. $message = str_replace(':input', $actualValue, $message);
  257. }
  258. return $message;
  259. }
  260. /**
  261. * Get the displayable name of the value.
  262. *
  263. * @param string $attribute
  264. * @param mixed $value
  265. * @return string
  266. */
  267. public function getDisplayableValue($attribute, $value)
  268. {
  269. if (isset($this->customValues[$attribute][$value])) {
  270. return $this->customValues[$attribute][$value];
  271. }
  272. $key = "validation.values.{$attribute}.{$value}";
  273. if (($line = $this->translator->trans($key)) !== $key) {
  274. return $line;
  275. }
  276. return $value;
  277. }
  278. /**
  279. * Transform an array of attributes to their displayable form.
  280. *
  281. * @param array $values
  282. * @return array
  283. */
  284. protected function getAttributeList(array $values)
  285. {
  286. $attributes = [];
  287. // For each attribute in the list we will simply get its displayable form as
  288. // this is convenient when replacing lists of parameters like some of the
  289. // replacement functions do when formatting out the validation message.
  290. foreach ($values as $key => $value) {
  291. $attributes[$key] = $this->getDisplayableAttribute($value);
  292. }
  293. return $attributes;
  294. }
  295. /**
  296. * Call a custom validator message replacer.
  297. *
  298. * @param string $message
  299. * @param string $attribute
  300. * @param string $rule
  301. * @param array $parameters
  302. * @param \Illuminate\Validation\Validator $validator
  303. * @return string|null
  304. */
  305. protected function callReplacer($message, $attribute, $rule, $parameters, $validator)
  306. {
  307. $callback = $this->replacers[$rule];
  308. if ($callback instanceof Closure) {
  309. return call_user_func_array($callback, func_get_args());
  310. } elseif (is_string($callback)) {
  311. return $this->callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters, $validator);
  312. }
  313. }
  314. /**
  315. * Call a class based validator message replacer.
  316. *
  317. * @param string $callback
  318. * @param string $message
  319. * @param string $attribute
  320. * @param string $rule
  321. * @param array $parameters
  322. * @param \Illuminate\Validation\Validator $validator
  323. * @return string
  324. */
  325. protected function callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters, $validator)
  326. {
  327. list($class, $method) = Str::parseCallback($callback, 'replace');
  328. return call_user_func_array([$this->container->make($class), $method], array_slice(func_get_args(), 1));
  329. }
  330. }