Validator.php 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777
  1. <?php
  2. namespace Illuminate\Validation;
  3. use BadMethodCallException;
  4. use Illuminate\Contracts\Container\Container;
  5. use Illuminate\Contracts\Translation\Translator;
  6. use Illuminate\Contracts\Validation\DataAwareRule;
  7. use Illuminate\Contracts\Validation\ImplicitRule;
  8. use Illuminate\Contracts\Validation\Rule as RuleContract;
  9. use Illuminate\Contracts\Validation\Validator as ValidatorContract;
  10. use Illuminate\Contracts\Validation\ValidatorAwareRule;
  11. use Illuminate\Support\Arr;
  12. use Illuminate\Support\Collection;
  13. use Illuminate\Support\Fluent;
  14. use Illuminate\Support\MessageBag;
  15. use Illuminate\Support\Str;
  16. use Illuminate\Support\ValidatedInput;
  17. use InvalidArgumentException;
  18. use RuntimeException;
  19. use stdClass;
  20. use Symfony\Component\HttpFoundation\File\UploadedFile;
  21. class Validator implements ValidatorContract
  22. {
  23. use Concerns\FormatsMessages,
  24. Concerns\ValidatesAttributes;
  25. /**
  26. * The Translator implementation.
  27. *
  28. * @var \Illuminate\Contracts\Translation\Translator
  29. */
  30. protected $translator;
  31. /**
  32. * The container instance.
  33. *
  34. * @var \Illuminate\Contracts\Container\Container
  35. */
  36. protected $container;
  37. /**
  38. * The Presence Verifier implementation.
  39. *
  40. * @var \Illuminate\Validation\PresenceVerifierInterface
  41. */
  42. protected $presenceVerifier;
  43. /**
  44. * The failed validation rules.
  45. *
  46. * @var array
  47. */
  48. protected $failedRules = [];
  49. /**
  50. * Attributes that should be excluded from the validated data.
  51. *
  52. * @var array
  53. */
  54. protected $excludeAttributes = [];
  55. /**
  56. * The message bag instance.
  57. *
  58. * @var \Illuminate\Support\MessageBag
  59. */
  60. protected $messages;
  61. /**
  62. * The data under validation.
  63. *
  64. * @var array
  65. */
  66. protected $data;
  67. /**
  68. * The initial rules provided.
  69. *
  70. * @var array
  71. */
  72. protected $initialRules;
  73. /**
  74. * The rules to be applied to the data.
  75. *
  76. * @var array
  77. */
  78. protected $rules;
  79. /**
  80. * The current rule that is validating.
  81. *
  82. * @var string
  83. */
  84. protected $currentRule;
  85. /**
  86. * The array of wildcard attributes with their asterisks expanded.
  87. *
  88. * @var array
  89. */
  90. protected $implicitAttributes = [];
  91. /**
  92. * The callback that should be used to format the attribute.
  93. *
  94. * @var callable|null
  95. */
  96. protected $implicitAttributesFormatter;
  97. /**
  98. * The cached data for the "distinct" rule.
  99. *
  100. * @var array
  101. */
  102. protected $distinctValues = [];
  103. /**
  104. * All of the registered "after" callbacks.
  105. *
  106. * @var array
  107. */
  108. protected $after = [];
  109. /**
  110. * The array of custom error messages.
  111. *
  112. * @var array
  113. */
  114. public $customMessages = [];
  115. /**
  116. * The array of fallback error messages.
  117. *
  118. * @var array
  119. */
  120. public $fallbackMessages = [];
  121. /**
  122. * The array of custom attribute names.
  123. *
  124. * @var array
  125. */
  126. public $customAttributes = [];
  127. /**
  128. * The array of custom displayable values.
  129. *
  130. * @var array
  131. */
  132. public $customValues = [];
  133. /**
  134. * Indicates if the validator should stop on the first rule failure.
  135. *
  136. * @var bool
  137. */
  138. protected $stopOnFirstFailure = false;
  139. /**
  140. * Indicates that unvalidated array keys should be excluded, even if the parent array was validated.
  141. *
  142. * @var bool
  143. */
  144. public $excludeUnvalidatedArrayKeys = false;
  145. /**
  146. * All of the custom validator extensions.
  147. *
  148. * @var array
  149. */
  150. public $extensions = [];
  151. /**
  152. * All of the custom replacer extensions.
  153. *
  154. * @var array
  155. */
  156. public $replacers = [];
  157. /**
  158. * The validation rules that may be applied to files.
  159. *
  160. * @var string[]
  161. */
  162. protected $fileRules = [
  163. 'Between',
  164. 'Dimensions',
  165. 'Encoding',
  166. 'Extensions',
  167. 'File',
  168. 'Image',
  169. 'Max',
  170. 'Mimes',
  171. 'Mimetypes',
  172. 'Min',
  173. 'Size',
  174. ];
  175. /**
  176. * The validation rules that imply the field is required.
  177. *
  178. * @var string[]
  179. */
  180. protected $implicitRules = [
  181. 'Accepted',
  182. 'AcceptedIf',
  183. 'Declined',
  184. 'DeclinedIf',
  185. 'Filled',
  186. 'Missing',
  187. 'MissingIf',
  188. 'MissingUnless',
  189. 'MissingWith',
  190. 'MissingWithAll',
  191. 'Present',
  192. 'PresentIf',
  193. 'PresentUnless',
  194. 'PresentWith',
  195. 'PresentWithAll',
  196. 'Required',
  197. 'RequiredIf',
  198. 'RequiredIfAccepted',
  199. 'RequiredIfDeclined',
  200. 'RequiredUnless',
  201. 'RequiredWith',
  202. 'RequiredWithAll',
  203. 'RequiredWithout',
  204. 'RequiredWithoutAll',
  205. ];
  206. /**
  207. * The validation rules which depend on other fields as parameters.
  208. *
  209. * @var string[]
  210. */
  211. protected $dependentRules = [
  212. 'After',
  213. 'AfterOrEqual',
  214. 'Before',
  215. 'BeforeOrEqual',
  216. 'Confirmed',
  217. 'Different',
  218. 'ExcludeIf',
  219. 'ExcludeUnless',
  220. 'ExcludeWith',
  221. 'ExcludeWithout',
  222. 'Gt',
  223. 'Gte',
  224. 'Lt',
  225. 'Lte',
  226. 'AcceptedIf',
  227. 'DeclinedIf',
  228. 'RequiredIf',
  229. 'RequiredIfAccepted',
  230. 'RequiredIfDeclined',
  231. 'RequiredUnless',
  232. 'RequiredWith',
  233. 'RequiredWithAll',
  234. 'RequiredWithout',
  235. 'RequiredWithoutAll',
  236. 'PresentIf',
  237. 'PresentUnless',
  238. 'PresentWith',
  239. 'PresentWithAll',
  240. 'Prohibited',
  241. 'ProhibitedIf',
  242. 'ProhibitedIfAccepted',
  243. 'ProhibitedIfDeclined',
  244. 'ProhibitedUnless',
  245. 'Prohibits',
  246. 'MissingIf',
  247. 'MissingUnless',
  248. 'MissingWith',
  249. 'MissingWithAll',
  250. 'Same',
  251. 'Unique',
  252. ];
  253. /**
  254. * The validation rules that can exclude an attribute.
  255. *
  256. * @var string[]
  257. */
  258. protected $excludeRules = ['Exclude', 'ExcludeIf', 'ExcludeUnless', 'ExcludeWith', 'ExcludeWithout'];
  259. /**
  260. * The size related validation rules.
  261. *
  262. * @var string[]
  263. */
  264. protected $sizeRules = ['Size', 'Between', 'Min', 'Max', 'Gt', 'Lt', 'Gte', 'Lte'];
  265. /**
  266. * The numeric related validation rules.
  267. *
  268. * @var string[]
  269. */
  270. protected $numericRules = ['Numeric', 'Integer', 'Decimal'];
  271. /**
  272. * The default numeric related validation rules.
  273. *
  274. * @var string[]
  275. */
  276. protected $defaultNumericRules = ['Numeric', 'Integer', 'Decimal'];
  277. /**
  278. * The current random hash for the validator.
  279. *
  280. * @var string|null
  281. */
  282. protected static $placeholderHash;
  283. /**
  284. * The exception to throw upon failure.
  285. *
  286. * @var class-string<\Illuminate\Validation\ValidationException>
  287. */
  288. protected $exception = ValidationException::class;
  289. /**
  290. * The custom callback to determine if an exponent is within allowed range.
  291. *
  292. * @var callable|null
  293. */
  294. protected $ensureExponentWithinAllowedRangeUsing;
  295. /**
  296. * Create a new Validator instance.
  297. *
  298. * @param \Illuminate\Contracts\Translation\Translator $translator
  299. * @param array $data
  300. * @param array $rules
  301. * @param array $messages
  302. * @param array $attributes
  303. */
  304. public function __construct(
  305. Translator $translator,
  306. array $data,
  307. array $rules,
  308. array $messages = [],
  309. array $attributes = [],
  310. ) {
  311. if (! isset(static::$placeholderHash)) {
  312. static::$placeholderHash = Str::random();
  313. }
  314. $this->initialRules = $rules;
  315. $this->translator = $translator;
  316. $this->customMessages = $messages;
  317. $this->data = $this->parseData($data);
  318. $this->customAttributes = $attributes;
  319. $this->setRules($rules);
  320. }
  321. /**
  322. * Parse the data array, converting dots and asterisks.
  323. *
  324. * @param array $data
  325. * @return array
  326. */
  327. public function parseData(array $data)
  328. {
  329. $newData = [];
  330. foreach ($data as $key => $value) {
  331. if (is_array($value)) {
  332. $value = $this->parseData($value);
  333. }
  334. $key = str_replace(
  335. ['.', '*'],
  336. ['__dot__'.static::$placeholderHash, '__asterisk__'.static::$placeholderHash],
  337. $key
  338. );
  339. $newData[$key] = $value;
  340. }
  341. return $newData;
  342. }
  343. /**
  344. * Replace the placeholders used in data keys.
  345. *
  346. * @param array $data
  347. * @return array
  348. */
  349. protected function replacePlaceholders($data)
  350. {
  351. $originalData = [];
  352. foreach ($data as $key => $value) {
  353. $originalData[$this->replacePlaceholderInString($key)] = is_array($value)
  354. ? $this->replacePlaceholders($value)
  355. : $value;
  356. }
  357. return $originalData;
  358. }
  359. /**
  360. * Replace the placeholders in the given string.
  361. *
  362. * @param string $value
  363. * @return string
  364. */
  365. protected function replacePlaceholderInString(string $value)
  366. {
  367. return str_replace(
  368. ['__dot__'.static::$placeholderHash, '__asterisk__'.static::$placeholderHash],
  369. ['.', '*'],
  370. $value
  371. );
  372. }
  373. /**
  374. * Replace each field parameter dot placeholder with dot.
  375. *
  376. * @param array $parameters
  377. * @return array
  378. */
  379. protected function replaceDotPlaceholderInParameters(array $parameters)
  380. {
  381. return array_map(function ($field) {
  382. return str_replace('__dot__'.static::$placeholderHash, '.', $field);
  383. }, $parameters);
  384. }
  385. /**
  386. * Add an after validation callback.
  387. *
  388. * @param callable|array|string $callback
  389. * @return $this
  390. */
  391. public function after($callback)
  392. {
  393. if (is_array($callback) && ! is_callable($callback)) {
  394. foreach ($callback as $rule) {
  395. $this->after(method_exists($rule, 'after') ? $rule->after(...) : $rule);
  396. }
  397. return $this;
  398. }
  399. $this->after[] = fn () => $callback($this);
  400. return $this;
  401. }
  402. /**
  403. * Determine if the data passes the validation rules.
  404. *
  405. * @return bool
  406. */
  407. public function passes()
  408. {
  409. $this->messages = new MessageBag;
  410. [$this->distinctValues, $this->failedRules] = [[], []];
  411. // We'll spin through each rule, validating the attributes attached to that
  412. // rule. Any error messages will be added to the containers with each of
  413. // the other error messages, returning true if we don't have messages.
  414. foreach ($this->rules as $attribute => $rules) {
  415. if ($this->shouldBeExcluded($attribute)) {
  416. $this->removeAttribute($attribute);
  417. continue;
  418. }
  419. if ($this->stopOnFirstFailure && $this->messages->isNotEmpty()) {
  420. break;
  421. }
  422. foreach ($rules as $rule) {
  423. $this->validateAttribute($attribute, $rule);
  424. if ($this->shouldBeExcluded($attribute)) {
  425. break;
  426. }
  427. if ($this->shouldStopValidating($attribute)) {
  428. break;
  429. }
  430. }
  431. }
  432. foreach ($this->rules as $attribute => $rules) {
  433. if ($this->shouldBeExcluded($attribute)) {
  434. $this->removeAttribute($attribute);
  435. }
  436. }
  437. // Here we will spin through all of the "after" hooks on this validator and
  438. // fire them off. This gives the callbacks a chance to perform all kinds
  439. // of other validation that needs to get wrapped up in this operation.
  440. foreach ($this->after as $after) {
  441. $after();
  442. }
  443. return $this->messages->isEmpty();
  444. }
  445. /**
  446. * Determine if the data fails the validation rules.
  447. *
  448. * @return bool
  449. */
  450. public function fails()
  451. {
  452. return ! $this->passes();
  453. }
  454. /**
  455. * Execute the callback if the data passes the validation rules.
  456. *
  457. * @template TWhenReturnType
  458. *
  459. * @param (callable($this): TWhenReturnType) $callback
  460. * @param (callable($this): TWhenReturnType)|null $default
  461. * @return $this|TWhenReturnType
  462. */
  463. public function whenPasses(callable $callback, ?callable $default = null)
  464. {
  465. if ($this->passes()) {
  466. return $callback($this) ?? $this;
  467. } elseif ($default) {
  468. return $default($this) ?? $this;
  469. }
  470. return $this;
  471. }
  472. /**
  473. * Execute the callback if the data fails the validation rules.
  474. *
  475. * @template TWhenReturnType
  476. *
  477. * @param (callable($this): TWhenReturnType) $callback
  478. * @param (callable($this): TWhenReturnType)|null $default
  479. * @return $this|TWhenReturnType
  480. */
  481. public function whenFails(callable $callback, ?callable $default = null)
  482. {
  483. if ($this->fails()) {
  484. return $callback($this) ?? $this;
  485. } elseif ($default) {
  486. return $default($this) ?? $this;
  487. }
  488. return $this;
  489. }
  490. /**
  491. * Determine if the attribute should be excluded.
  492. *
  493. * @param string $attribute
  494. * @return bool
  495. */
  496. protected function shouldBeExcluded($attribute)
  497. {
  498. foreach ($this->excludeAttributes as $excludeAttribute) {
  499. if ($attribute === $excludeAttribute ||
  500. Str::startsWith($attribute, $excludeAttribute.'.')) {
  501. return true;
  502. }
  503. }
  504. return false;
  505. }
  506. /**
  507. * Remove the given attribute.
  508. *
  509. * @param string $attribute
  510. * @return void
  511. */
  512. protected function removeAttribute($attribute)
  513. {
  514. Arr::forget($this->data, $attribute);
  515. Arr::forget($this->rules, $attribute);
  516. }
  517. /**
  518. * Run the validator's rules against its data.
  519. *
  520. * @return array
  521. *
  522. * @throws \Illuminate\Validation\ValidationException
  523. */
  524. public function validate()
  525. {
  526. throw_if($this->fails(), $this->exception, $this);
  527. return $this->validated();
  528. }
  529. /**
  530. * Run the validator's rules against its data.
  531. *
  532. * @param string $errorBag
  533. * @return array
  534. *
  535. * @throws \Illuminate\Validation\ValidationException
  536. */
  537. public function validateWithBag(string $errorBag)
  538. {
  539. try {
  540. return $this->validate();
  541. } catch (ValidationException $e) {
  542. $e->errorBag = $errorBag;
  543. throw $e;
  544. }
  545. }
  546. /**
  547. * Get a validated input container for the validated input.
  548. *
  549. * @param array|null $keys
  550. * @return \Illuminate\Support\ValidatedInput|array
  551. */
  552. public function safe(?array $keys = null)
  553. {
  554. return is_array($keys)
  555. ? (new ValidatedInput($this->validated()))->only($keys)
  556. : new ValidatedInput($this->validated());
  557. }
  558. /**
  559. * Get the attributes and values that were validated.
  560. *
  561. * @return array
  562. *
  563. * @throws \Illuminate\Validation\ValidationException
  564. */
  565. public function validated()
  566. {
  567. if (! $this->messages) {
  568. $this->passes();
  569. }
  570. throw_if($this->messages->isNotEmpty(), $this->exception, $this);
  571. $results = [];
  572. $missingValue = new stdClass;
  573. foreach ($this->getRules() as $key => $rules) {
  574. $value = data_get($this->getData(), $key, $missingValue);
  575. if ($this->excludeUnvalidatedArrayKeys &&
  576. (in_array('array', $rules) || in_array('list', $rules)) &&
  577. $value !== null &&
  578. ! empty(preg_grep('/^'.preg_quote($key, '/').'\.+/', array_keys($this->getRules())))) {
  579. continue;
  580. }
  581. if ($value !== $missingValue) {
  582. Arr::set($results, $key, $value);
  583. }
  584. }
  585. return $this->replacePlaceholders($results);
  586. }
  587. /**
  588. * Validate a given attribute against a rule.
  589. *
  590. * @param string $attribute
  591. * @param string $rule
  592. * @return void
  593. */
  594. protected function validateAttribute($attribute, $rule)
  595. {
  596. $this->currentRule = $rule;
  597. [$rule, $parameters] = ValidationRuleParser::parse($rule);
  598. if ($rule === '') {
  599. return;
  600. }
  601. // First we will get the correct keys for the given attribute in case the field is nested in
  602. // an array. Then we determine if the given rule accepts other field names as parameters.
  603. // If so, we will replace any asterisks found in the parameters with the correct keys.
  604. if ($this->dependsOnOtherFields($rule)) {
  605. $parameters = $this->replaceDotInParameters($parameters);
  606. if ($keys = $this->getExplicitKeys($attribute)) {
  607. $parameters = $this->replaceAsterisksInParameters($parameters, $keys);
  608. }
  609. }
  610. $value = $this->getValue($attribute);
  611. // If the attribute is a file, we will verify that the file upload was actually successful
  612. // and if it wasn't we will add a failure for the attribute. Files may not successfully
  613. // upload if they are too large based on PHP's settings so we will bail in this case.
  614. if ($value instanceof UploadedFile && ! $value->isValid() &&
  615. $this->hasRule($attribute, array_merge($this->fileRules, $this->implicitRules))
  616. ) {
  617. return $this->addFailure($attribute, 'uploaded', []);
  618. }
  619. // If we have made it this far we will make sure the attribute is validatable and if it is
  620. // we will call the validation method with the attribute. If a method returns false the
  621. // attribute is invalid and we will add a failure message for this failing attribute.
  622. $validatable = $this->isValidatable($rule, $attribute, $value);
  623. if ($rule instanceof RuleContract) {
  624. return $validatable
  625. ? $this->validateUsingCustomRule($attribute, $value, $rule)
  626. : null;
  627. }
  628. $method = "validate{$rule}";
  629. $this->numericRules = $this->defaultNumericRules;
  630. if ($validatable && ! $this->$method($attribute, $value, $parameters, $this)) {
  631. $this->addFailure($attribute, $rule, $parameters);
  632. }
  633. }
  634. /**
  635. * Determine if the given rule depends on other fields.
  636. *
  637. * @param string $rule
  638. * @return bool
  639. */
  640. protected function dependsOnOtherFields($rule)
  641. {
  642. return in_array($rule, $this->dependentRules);
  643. }
  644. /**
  645. * Get the explicit keys from an attribute flattened with dot notation.
  646. *
  647. * E.g. 'foo.1.bar.spark.baz' -> [1, 'spark'] for 'foo.*.bar.*.baz'
  648. *
  649. * @param string $attribute
  650. * @return array
  651. */
  652. protected function getExplicitKeys($attribute)
  653. {
  654. $pattern = str_replace('\*', '([^\.]+)', preg_quote($this->getPrimaryAttribute($attribute), '/'));
  655. if (preg_match('/^'.$pattern.'/', $attribute, $keys)) {
  656. array_shift($keys);
  657. return $keys;
  658. }
  659. return [];
  660. }
  661. /**
  662. * Get the primary attribute name.
  663. *
  664. * For example, if "name.0" is given, "name.*" will be returned.
  665. *
  666. * @param string $attribute
  667. * @return string
  668. */
  669. protected function getPrimaryAttribute($attribute)
  670. {
  671. foreach ($this->implicitAttributes as $unparsed => $parsed) {
  672. if (in_array($attribute, $parsed, true)) {
  673. return $unparsed;
  674. }
  675. }
  676. return $attribute;
  677. }
  678. /**
  679. * Replace each field parameter which has an escaped dot with the dot placeholder.
  680. *
  681. * @param array $parameters
  682. * @return array
  683. */
  684. protected function replaceDotInParameters(array $parameters)
  685. {
  686. return array_map(function ($field) {
  687. return static::encodeAttributeWithPlaceholder((string) ($field ?? ''));
  688. }, $parameters);
  689. }
  690. /**
  691. * Replace each field parameter which has asterisks with the given keys.
  692. *
  693. * @param array $parameters
  694. * @param array $keys
  695. * @return array
  696. */
  697. protected function replaceAsterisksInParameters(array $parameters, array $keys)
  698. {
  699. return array_map(function ($field) use ($keys) {
  700. return vsprintf(str_replace('*', '%s', $field), $keys);
  701. }, $parameters);
  702. }
  703. /**
  704. * Determine if the attribute is validatable.
  705. *
  706. * @param object|string $rule
  707. * @param string $attribute
  708. * @param mixed $value
  709. * @return bool
  710. */
  711. protected function isValidatable($rule, $attribute, $value)
  712. {
  713. if (in_array($rule, $this->excludeRules)) {
  714. return true;
  715. }
  716. return $this->presentOrRuleIsImplicit($rule, $attribute, $value) &&
  717. $this->passesOptionalCheck($attribute) &&
  718. $this->isNotNullIfMarkedAsNullable($rule, $attribute) &&
  719. $this->hasNotFailedPreviousRuleIfPresenceRule($rule, $attribute);
  720. }
  721. /**
  722. * Determine if the field is present, or the rule implies required.
  723. *
  724. * @param object|string $rule
  725. * @param string $attribute
  726. * @param mixed $value
  727. * @return bool
  728. */
  729. protected function presentOrRuleIsImplicit($rule, $attribute, $value)
  730. {
  731. if (is_string($value) && trim($value) === '') {
  732. return $this->isImplicit($rule);
  733. }
  734. return $this->validatePresent($attribute, $value) ||
  735. $this->isImplicit($rule);
  736. }
  737. /**
  738. * Determine if a given rule implies the attribute is required.
  739. *
  740. * @param object|string $rule
  741. * @return bool
  742. */
  743. protected function isImplicit($rule)
  744. {
  745. return $rule instanceof ImplicitRule ||
  746. in_array($rule, $this->implicitRules);
  747. }
  748. /**
  749. * Determine if the attribute passes any optional check.
  750. *
  751. * @param string $attribute
  752. * @return bool
  753. */
  754. protected function passesOptionalCheck($attribute)
  755. {
  756. if (! $this->hasRule($attribute, ['Sometimes'])) {
  757. return true;
  758. }
  759. $data = ValidationData::initializeAndGatherData($attribute, $this->data);
  760. return array_key_exists($attribute, $data)
  761. || array_key_exists($attribute, $this->data);
  762. }
  763. /**
  764. * Determine if the attribute fails the nullable check.
  765. *
  766. * @param string $rule
  767. * @param string $attribute
  768. * @return bool
  769. */
  770. protected function isNotNullIfMarkedAsNullable($rule, $attribute)
  771. {
  772. if ($this->isImplicit($rule) || ! $this->hasRule($attribute, ['Nullable'])) {
  773. return true;
  774. }
  775. return ! is_null(Arr::get($this->data, $attribute, 0));
  776. }
  777. /**
  778. * Determine if it's a necessary presence validation.
  779. *
  780. * This is to avoid possible database type comparison errors.
  781. *
  782. * @param string $rule
  783. * @param string $attribute
  784. * @return bool
  785. */
  786. protected function hasNotFailedPreviousRuleIfPresenceRule($rule, $attribute)
  787. {
  788. return in_array($rule, ['Unique', 'Exists']) ? ! $this->messages->has($attribute) : true;
  789. }
  790. /**
  791. * Validate an attribute using a custom rule object.
  792. *
  793. * @param string $attribute
  794. * @param mixed $value
  795. * @param \Illuminate\Contracts\Validation\Rule $rule
  796. * @return void
  797. */
  798. protected function validateUsingCustomRule($attribute, $value, $rule)
  799. {
  800. $originalAttribute = $this->replacePlaceholderInString($attribute);
  801. $attribute = match (true) {
  802. $rule instanceof Rules\Email => $attribute,
  803. $rule instanceof Rules\File => $attribute,
  804. $rule instanceof Rules\Password => $attribute,
  805. default => $originalAttribute,
  806. };
  807. $value = is_array($value) ? $this->replacePlaceholders($value) : $value;
  808. if ($rule instanceof ValidatorAwareRule) {
  809. if ($attribute !== $originalAttribute) {
  810. $this->addCustomAttributes([
  811. $attribute => $this->customAttributes[$originalAttribute] ?? $originalAttribute,
  812. ]);
  813. }
  814. $rule->setValidator($this);
  815. }
  816. if ($rule instanceof DataAwareRule) {
  817. $rule->setData($this->data);
  818. }
  819. if (! $rule->passes($attribute, $value)) {
  820. $ruleClass = $rule instanceof InvokableValidationRule ?
  821. get_class($rule->invokable()) :
  822. get_class($rule);
  823. $this->failedRules[$originalAttribute][$ruleClass] = [];
  824. $messages = $this->getFromLocalArray($originalAttribute, $ruleClass) ?? $rule->message();
  825. $messages = $messages ? (array) $messages : [$ruleClass];
  826. foreach ($messages as $key => $message) {
  827. $key = is_string($key) ? $key : $originalAttribute;
  828. $this->messages->add($key, $this->makeReplacements(
  829. $message, $key, $ruleClass, []
  830. ));
  831. }
  832. }
  833. }
  834. /**
  835. * Check if we should stop further validations on a given attribute.
  836. *
  837. * @param string $attribute
  838. * @return bool
  839. */
  840. protected function shouldStopValidating($attribute)
  841. {
  842. $cleanedAttribute = $this->replacePlaceholderInString($attribute);
  843. if ($this->hasRule($attribute, ['Bail'])) {
  844. return $this->messages->has($cleanedAttribute);
  845. }
  846. if (isset($this->failedRules[$cleanedAttribute]) &&
  847. array_key_exists('uploaded', $this->failedRules[$cleanedAttribute])) {
  848. return true;
  849. }
  850. // In case the attribute has any rule that indicates that the field is required
  851. // and that rule already failed then we should stop validation at this point
  852. // as now there is no point in calling other rules with this field empty.
  853. return $this->hasRule($attribute, $this->implicitRules) &&
  854. isset($this->failedRules[$cleanedAttribute]) &&
  855. array_intersect(array_keys($this->failedRules[$cleanedAttribute]), $this->implicitRules);
  856. }
  857. /**
  858. * Add a failed rule and error message to the collection.
  859. *
  860. * @param string $attribute
  861. * @param string $rule
  862. * @param array $parameters
  863. * @return void
  864. */
  865. public function addFailure($attribute, $rule, $parameters = [])
  866. {
  867. if (! $this->messages) {
  868. $this->passes();
  869. }
  870. $attributeWithPlaceholders = $attribute;
  871. $attribute = $this->replacePlaceholderInString($attribute);
  872. if (in_array($rule, $this->excludeRules)) {
  873. return $this->excludeAttribute($attribute);
  874. }
  875. if ($this->dependsOnOtherFields($rule)) {
  876. $parameters = $this->replaceDotPlaceholderInParameters($parameters);
  877. }
  878. $this->messages->add($attribute, $this->makeReplacements(
  879. $this->getMessage($attributeWithPlaceholders, $rule), $attribute, $rule, $parameters
  880. ));
  881. $this->failedRules[$attribute][$rule] = $parameters;
  882. }
  883. /**
  884. * Add the given attribute to the list of excluded attributes.
  885. *
  886. * @param string $attribute
  887. * @return void
  888. */
  889. protected function excludeAttribute(string $attribute)
  890. {
  891. $this->excludeAttributes[] = $attribute;
  892. $this->excludeAttributes = array_unique($this->excludeAttributes);
  893. }
  894. /**
  895. * Returns the data which was valid.
  896. *
  897. * @return array
  898. */
  899. public function valid()
  900. {
  901. if (! $this->messages) {
  902. $this->passes();
  903. }
  904. return array_diff_key(
  905. $this->data, $this->attributesThatHaveMessages()
  906. );
  907. }
  908. /**
  909. * Returns the data which was invalid.
  910. *
  911. * @return array
  912. */
  913. public function invalid()
  914. {
  915. if (! $this->messages) {
  916. $this->passes();
  917. }
  918. $invalid = array_intersect_key(
  919. $this->data, $this->attributesThatHaveMessages()
  920. );
  921. $result = [];
  922. $failed = Arr::only(Arr::dot($invalid), array_keys($this->failed()));
  923. foreach ($failed as $key => $failure) {
  924. Arr::set($result, $key, $failure);
  925. }
  926. return $result;
  927. }
  928. /**
  929. * Generate an array of all attributes that have messages.
  930. *
  931. * @return array
  932. */
  933. protected function attributesThatHaveMessages()
  934. {
  935. return (new Collection($this->messages()->toArray()))
  936. ->map(fn ($message, $key) => explode('.', $key)[0])
  937. ->unique()
  938. ->flip()
  939. ->all();
  940. }
  941. /**
  942. * Get the failed validation rules.
  943. *
  944. * @return array
  945. */
  946. public function failed()
  947. {
  948. return $this->failedRules;
  949. }
  950. /**
  951. * Get the message container for the validator.
  952. *
  953. * @return \Illuminate\Support\MessageBag
  954. */
  955. public function messages()
  956. {
  957. if (! $this->messages) {
  958. $this->passes();
  959. }
  960. return $this->messages;
  961. }
  962. /**
  963. * An alternative more semantic shortcut to the message container.
  964. *
  965. * @return \Illuminate\Support\MessageBag
  966. */
  967. public function errors()
  968. {
  969. return $this->messages();
  970. }
  971. /**
  972. * Get the messages for the instance.
  973. *
  974. * @return \Illuminate\Support\MessageBag
  975. */
  976. public function getMessageBag()
  977. {
  978. return $this->messages();
  979. }
  980. /**
  981. * Determine if the given attribute has a rule in the given set.
  982. *
  983. * @param string $attribute
  984. * @param string|array $rules
  985. * @return bool
  986. */
  987. public function hasRule($attribute, $rules)
  988. {
  989. return ! is_null($this->getRule($attribute, $rules));
  990. }
  991. /**
  992. * Get a rule and its parameters for a given attribute.
  993. *
  994. * @param string $attribute
  995. * @param string|array $rules
  996. * @return array|null
  997. */
  998. protected function getRule($attribute, $rules)
  999. {
  1000. if (! array_key_exists($attribute, $this->rules)) {
  1001. return;
  1002. }
  1003. $rules = (array) $rules;
  1004. foreach ($this->rules[$attribute] as $rule) {
  1005. [$rule, $parameters] = ValidationRuleParser::parse($rule);
  1006. if (in_array($rule, $rules)) {
  1007. return [$rule, $parameters];
  1008. }
  1009. }
  1010. }
  1011. /**
  1012. * Get the data under validation.
  1013. *
  1014. * @return array
  1015. */
  1016. public function attributes()
  1017. {
  1018. return $this->getData();
  1019. }
  1020. /**
  1021. * Get the data under validation.
  1022. *
  1023. * @return array
  1024. */
  1025. public function getData()
  1026. {
  1027. return $this->data;
  1028. }
  1029. /**
  1030. * Set the data under validation.
  1031. *
  1032. * @param array $data
  1033. * @return $this
  1034. */
  1035. public function setData(array $data)
  1036. {
  1037. $this->data = $this->parseData($data);
  1038. $this->setRules($this->initialRules);
  1039. return $this;
  1040. }
  1041. /**
  1042. * Get the value of a given attribute.
  1043. *
  1044. * @param string $attribute
  1045. * @return mixed
  1046. */
  1047. public function getValue($attribute)
  1048. {
  1049. return Arr::get($this->data, $attribute);
  1050. }
  1051. /**
  1052. * Set the value of a given attribute.
  1053. *
  1054. * @param string $attribute
  1055. * @param mixed $value
  1056. * @return void
  1057. */
  1058. public function setValue($attribute, $value)
  1059. {
  1060. Arr::set($this->data, $attribute, $value);
  1061. }
  1062. /**
  1063. * Get the validation rules.
  1064. *
  1065. * @return array
  1066. */
  1067. public function getRules()
  1068. {
  1069. return $this->rules;
  1070. }
  1071. /**
  1072. * Get the validation rules with key placeholders removed.
  1073. *
  1074. * @return array
  1075. */
  1076. public function getRulesWithoutPlaceholders()
  1077. {
  1078. return (new Collection($this->rules))
  1079. ->mapWithKeys(fn ($value, $key) => [
  1080. static::decodeAttributeWithPlaceholder($key) => $value,
  1081. ])
  1082. ->all();
  1083. }
  1084. /**
  1085. * Set the validation rules.
  1086. *
  1087. * @param array $rules
  1088. * @return $this
  1089. */
  1090. public function setRules(array $rules)
  1091. {
  1092. $rules = (new Collection($rules))
  1093. ->mapWithKeys(function ($value, $key) {
  1094. return [static::encodeAttributeWithPlaceholder($key) => $value];
  1095. })
  1096. ->toArray();
  1097. $this->initialRules = $rules;
  1098. $this->rules = [];
  1099. $this->addRules($rules);
  1100. return $this;
  1101. }
  1102. /**
  1103. * Append new validation rules to the validator.
  1104. *
  1105. * @param array $rules
  1106. * @return $this
  1107. */
  1108. public function appendRules(array $rules)
  1109. {
  1110. $rules = (new Collection($rules))
  1111. ->map(function ($value) {
  1112. return is_string($value) ? explode('|', $value) : $value;
  1113. })
  1114. ->all();
  1115. return $this->setRules(array_merge_recursive($this->getRulesWithoutPlaceholders(), $rules));
  1116. }
  1117. /**
  1118. * Parse the given rules and merge them into current rules.
  1119. *
  1120. * @internal
  1121. *
  1122. * @param array $rules
  1123. * @return void
  1124. */
  1125. public function addRules($rules)
  1126. {
  1127. // The primary purpose of this parser is to expand any "*" rules to the all
  1128. // of the explicit rules needed for the given data. For example the rule
  1129. // names.* would get expanded to names.0, names.1, etc. for this data.
  1130. $response = (new ValidationRuleParser($this->data))
  1131. ->explode(ValidationRuleParser::filterConditionalRules($rules, $this->data));
  1132. foreach ($response->rules as $key => $rule) {
  1133. $this->rules[$key] = array_merge($this->rules[$key] ?? [], $rule);
  1134. }
  1135. $this->implicitAttributes = array_merge(
  1136. $this->implicitAttributes, $response->implicitAttributes
  1137. );
  1138. }
  1139. /**
  1140. * Add conditions to a given field based on a Closure.
  1141. *
  1142. * @param string|array $attribute
  1143. * @param string|array $rules
  1144. * @param callable $callback
  1145. * @return $this
  1146. */
  1147. public function sometimes($attribute, $rules, callable $callback)
  1148. {
  1149. $payload = new Fluent($this->data);
  1150. foreach ((array) $attribute as $key) {
  1151. $response = (new ValidationRuleParser($this->data))->explode([$key => $rules]);
  1152. $this->implicitAttributes = array_merge($response->implicitAttributes, $this->implicitAttributes);
  1153. foreach ($response->rules as $ruleKey => $ruleValue) {
  1154. if ($callback($payload, $this->dataForSometimesIteration($ruleKey, ! str_ends_with($key, '.*')))) {
  1155. $this->addRules([static::encodeAttributeWithPlaceholder($ruleKey) => $ruleValue]);
  1156. }
  1157. }
  1158. }
  1159. return $this;
  1160. }
  1161. /**
  1162. * Get the data that should be injected into the iteration of a wildcard "sometimes" callback.
  1163. *
  1164. * @param string $attribute
  1165. * @param bool $removeLastSegmentOfAttribute
  1166. * @return \Illuminate\Support\Fluent|mixed
  1167. */
  1168. private function dataForSometimesIteration(string $attribute, bool $removeLastSegmentOfAttribute)
  1169. {
  1170. $lastSegmentOfAttribute = strrchr($attribute, '.');
  1171. $attribute = $lastSegmentOfAttribute && $removeLastSegmentOfAttribute
  1172. ? Str::replaceLast($lastSegmentOfAttribute, '', $attribute)
  1173. : $attribute;
  1174. return is_array($data = data_get($this->data, $attribute))
  1175. ? new Fluent($data)
  1176. : $data;
  1177. }
  1178. /**
  1179. * Instruct the validator to stop validating after the first rule failure.
  1180. *
  1181. * @param bool $stopOnFirstFailure
  1182. * @return $this
  1183. */
  1184. public function stopOnFirstFailure($stopOnFirstFailure = true)
  1185. {
  1186. $this->stopOnFirstFailure = $stopOnFirstFailure;
  1187. return $this;
  1188. }
  1189. /**
  1190. * Register an array of custom validator extensions.
  1191. *
  1192. * @param array $extensions
  1193. * @return void
  1194. */
  1195. public function addExtensions(array $extensions)
  1196. {
  1197. if ($extensions) {
  1198. $keys = array_map(Str::snake(...), array_keys($extensions));
  1199. $extensions = array_combine($keys, array_values($extensions));
  1200. }
  1201. $this->extensions = array_merge($this->extensions, $extensions);
  1202. }
  1203. /**
  1204. * Register an array of custom implicit validator extensions.
  1205. *
  1206. * @param array $extensions
  1207. * @return void
  1208. */
  1209. public function addImplicitExtensions(array $extensions)
  1210. {
  1211. $this->addExtensions($extensions);
  1212. foreach ($extensions as $rule => $extension) {
  1213. $this->implicitRules[] = Str::studly($rule);
  1214. }
  1215. }
  1216. /**
  1217. * Register an array of custom dependent validator extensions.
  1218. *
  1219. * @param array $extensions
  1220. * @return void
  1221. */
  1222. public function addDependentExtensions(array $extensions)
  1223. {
  1224. $this->addExtensions($extensions);
  1225. foreach ($extensions as $rule => $extension) {
  1226. $this->dependentRules[] = Str::studly($rule);
  1227. }
  1228. }
  1229. /**
  1230. * Register a custom validator extension.
  1231. *
  1232. * @param string $rule
  1233. * @param \Closure|string $extension
  1234. * @return void
  1235. */
  1236. public function addExtension($rule, $extension)
  1237. {
  1238. $this->extensions[Str::snake($rule)] = $extension;
  1239. }
  1240. /**
  1241. * Register a custom implicit validator extension.
  1242. *
  1243. * @param string $rule
  1244. * @param \Closure|string $extension
  1245. * @return void
  1246. */
  1247. public function addImplicitExtension($rule, $extension)
  1248. {
  1249. $this->addExtension($rule, $extension);
  1250. $this->implicitRules[] = Str::studly($rule);
  1251. }
  1252. /**
  1253. * Register a custom dependent validator extension.
  1254. *
  1255. * @param string $rule
  1256. * @param \Closure|string $extension
  1257. * @return void
  1258. */
  1259. public function addDependentExtension($rule, $extension)
  1260. {
  1261. $this->addExtension($rule, $extension);
  1262. $this->dependentRules[] = Str::studly($rule);
  1263. }
  1264. /**
  1265. * Register an array of custom validator message replacers.
  1266. *
  1267. * @param array $replacers
  1268. * @return void
  1269. */
  1270. public function addReplacers(array $replacers)
  1271. {
  1272. if ($replacers) {
  1273. $keys = array_map(Str::snake(...), array_keys($replacers));
  1274. $replacers = array_combine($keys, array_values($replacers));
  1275. }
  1276. $this->replacers = array_merge($this->replacers, $replacers);
  1277. }
  1278. /**
  1279. * Register a custom validator message replacer.
  1280. *
  1281. * @param string $rule
  1282. * @param \Closure|string $replacer
  1283. * @return void
  1284. */
  1285. public function addReplacer($rule, $replacer)
  1286. {
  1287. $this->replacers[Str::snake($rule)] = $replacer;
  1288. }
  1289. /**
  1290. * Set the custom messages for the validator.
  1291. *
  1292. * @param array $messages
  1293. * @return $this
  1294. */
  1295. public function setCustomMessages(array $messages)
  1296. {
  1297. $this->customMessages = array_merge($this->customMessages, $messages);
  1298. return $this;
  1299. }
  1300. /**
  1301. * Set the custom attributes on the validator.
  1302. *
  1303. * @param array $attributes
  1304. * @return $this
  1305. */
  1306. public function setAttributeNames(array $attributes)
  1307. {
  1308. $this->customAttributes = $attributes;
  1309. return $this;
  1310. }
  1311. /**
  1312. * Add custom attributes to the validator.
  1313. *
  1314. * @param array $attributes
  1315. * @return $this
  1316. */
  1317. public function addCustomAttributes(array $attributes)
  1318. {
  1319. $this->customAttributes = array_merge($this->customAttributes, $attributes);
  1320. return $this;
  1321. }
  1322. /**
  1323. * Set the callback that used to format an implicit attribute.
  1324. *
  1325. * @param callable|null $formatter
  1326. * @return $this
  1327. */
  1328. public function setImplicitAttributesFormatter(?callable $formatter = null)
  1329. {
  1330. $this->implicitAttributesFormatter = $formatter;
  1331. return $this;
  1332. }
  1333. /**
  1334. * Set the custom values on the validator.
  1335. *
  1336. * @param array $values
  1337. * @return $this
  1338. */
  1339. public function setValueNames(array $values)
  1340. {
  1341. $this->customValues = $values;
  1342. return $this;
  1343. }
  1344. /**
  1345. * Add the custom values for the validator.
  1346. *
  1347. * @param array $customValues
  1348. * @return $this
  1349. */
  1350. public function addCustomValues(array $customValues)
  1351. {
  1352. $this->customValues = array_merge($this->customValues, $customValues);
  1353. return $this;
  1354. }
  1355. /**
  1356. * Set the fallback messages for the validator.
  1357. *
  1358. * @param array $messages
  1359. * @return void
  1360. */
  1361. public function setFallbackMessages(array $messages)
  1362. {
  1363. $this->fallbackMessages = $messages;
  1364. }
  1365. /**
  1366. * Get the Presence Verifier implementation.
  1367. *
  1368. * @param string|null $connection
  1369. * @return \Illuminate\Validation\PresenceVerifierInterface
  1370. *
  1371. * @throws \RuntimeException
  1372. */
  1373. public function getPresenceVerifier($connection = null)
  1374. {
  1375. if (! isset($this->presenceVerifier)) {
  1376. throw new RuntimeException('Presence verifier has not been set.');
  1377. }
  1378. if ($this->presenceVerifier instanceof DatabasePresenceVerifierInterface) {
  1379. $this->presenceVerifier->setConnection($connection);
  1380. }
  1381. return $this->presenceVerifier;
  1382. }
  1383. /**
  1384. * Set the Presence Verifier implementation.
  1385. *
  1386. * @param \Illuminate\Validation\PresenceVerifierInterface $presenceVerifier
  1387. * @return void
  1388. */
  1389. public function setPresenceVerifier(PresenceVerifierInterface $presenceVerifier)
  1390. {
  1391. $this->presenceVerifier = $presenceVerifier;
  1392. }
  1393. /**
  1394. * Get the exception to throw upon failed validation.
  1395. *
  1396. * @return class-string<\Illuminate\Validation\ValidationException>
  1397. */
  1398. public function getException()
  1399. {
  1400. return $this->exception;
  1401. }
  1402. /**
  1403. * Set the exception to throw upon failed validation.
  1404. *
  1405. * @param class-string<\Illuminate\Validation\ValidationException> $exception
  1406. * @return $this
  1407. *
  1408. * @throws \InvalidArgumentException
  1409. */
  1410. public function setException($exception)
  1411. {
  1412. if (! is_a($exception, ValidationException::class, true)) {
  1413. throw new InvalidArgumentException(
  1414. sprintf('Exception [%s] is invalid. It must extend [%s].', $exception, ValidationException::class)
  1415. );
  1416. }
  1417. $this->exception = $exception;
  1418. return $this;
  1419. }
  1420. /**
  1421. * Ensure exponents are within range using the given callback.
  1422. *
  1423. * @param callable(int $scale, string $attribute, mixed $value) $callback
  1424. * @return $this
  1425. */
  1426. public function ensureExponentWithinAllowedRangeUsing($callback)
  1427. {
  1428. $this->ensureExponentWithinAllowedRangeUsing = $callback;
  1429. return $this;
  1430. }
  1431. /**
  1432. * Get the Translator implementation.
  1433. *
  1434. * @return \Illuminate\Contracts\Translation\Translator
  1435. */
  1436. public function getTranslator()
  1437. {
  1438. return $this->translator;
  1439. }
  1440. /**
  1441. * Set the Translator implementation.
  1442. *
  1443. * @param \Illuminate\Contracts\Translation\Translator $translator
  1444. * @return void
  1445. */
  1446. public function setTranslator(Translator $translator)
  1447. {
  1448. $this->translator = $translator;
  1449. }
  1450. /**
  1451. * Set the IoC container instance.
  1452. *
  1453. * @param \Illuminate\Contracts\Container\Container $container
  1454. * @return void
  1455. */
  1456. public function setContainer(Container $container)
  1457. {
  1458. $this->container = $container;
  1459. }
  1460. /**
  1461. * Call a custom validator extension.
  1462. *
  1463. * @param string $rule
  1464. * @param array $parameters
  1465. * @return bool|null
  1466. */
  1467. protected function callExtension($rule, $parameters)
  1468. {
  1469. $callback = $this->extensions[$rule];
  1470. if (is_callable($callback)) {
  1471. return $callback(...array_values($parameters));
  1472. } elseif (is_string($callback)) {
  1473. return $this->callClassBasedExtension($callback, $parameters);
  1474. }
  1475. }
  1476. /**
  1477. * Call a class based validator extension.
  1478. *
  1479. * @param string $callback
  1480. * @param array $parameters
  1481. * @return bool
  1482. */
  1483. protected function callClassBasedExtension($callback, $parameters)
  1484. {
  1485. [$class, $method] = Str::parseCallback($callback, 'validate');
  1486. return $this->container->make($class)->{$method}(...array_values($parameters));
  1487. }
  1488. /**
  1489. * Encode the attribute with the placeholder hash.
  1490. *
  1491. * @param string $attribute
  1492. * @return string
  1493. */
  1494. protected static function encodeAttributeWithPlaceholder(string $attribute)
  1495. {
  1496. return str_replace('\.', '__dot__'.static::$placeholderHash, $attribute);
  1497. }
  1498. /**
  1499. * Decode an attribute with a placeholder hash.
  1500. *
  1501. * @param string $attribute
  1502. * @return string
  1503. */
  1504. protected static function decodeAttributeWithPlaceholder(string $attribute)
  1505. {
  1506. return str_replace('__dot__'.static::$placeholderHash, '\\.', $attribute);
  1507. }
  1508. /**
  1509. * Flush the validator's global state.
  1510. *
  1511. * @return void
  1512. */
  1513. public static function flushState()
  1514. {
  1515. static::$placeholderHash = null;
  1516. }
  1517. /**
  1518. * Handle dynamic calls to class methods.
  1519. *
  1520. * @param string $method
  1521. * @param array $parameters
  1522. * @return mixed
  1523. *
  1524. * @throws \BadMethodCallException
  1525. */
  1526. public function __call($method, $parameters)
  1527. {
  1528. $rule = Str::snake(substr($method, 8));
  1529. if (isset($this->extensions[$rule])) {
  1530. return $this->callExtension($rule, $parameters);
  1531. }
  1532. throw new BadMethodCallException(sprintf(
  1533. 'Method %s::%s does not exist.', static::class, $method
  1534. ));
  1535. }
  1536. }