| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777 |
- <?php
- namespace Illuminate\Validation;
- use BadMethodCallException;
- use Illuminate\Contracts\Container\Container;
- use Illuminate\Contracts\Translation\Translator;
- use Illuminate\Contracts\Validation\DataAwareRule;
- use Illuminate\Contracts\Validation\ImplicitRule;
- use Illuminate\Contracts\Validation\Rule as RuleContract;
- use Illuminate\Contracts\Validation\Validator as ValidatorContract;
- use Illuminate\Contracts\Validation\ValidatorAwareRule;
- use Illuminate\Support\Arr;
- use Illuminate\Support\Collection;
- use Illuminate\Support\Fluent;
- use Illuminate\Support\MessageBag;
- use Illuminate\Support\Str;
- use Illuminate\Support\ValidatedInput;
- use InvalidArgumentException;
- use RuntimeException;
- use stdClass;
- use Symfony\Component\HttpFoundation\File\UploadedFile;
- class Validator implements ValidatorContract
- {
- use Concerns\FormatsMessages,
- Concerns\ValidatesAttributes;
- /**
- * The Translator implementation.
- *
- * @var \Illuminate\Contracts\Translation\Translator
- */
- protected $translator;
- /**
- * The container instance.
- *
- * @var \Illuminate\Contracts\Container\Container
- */
- protected $container;
- /**
- * The Presence Verifier implementation.
- *
- * @var \Illuminate\Validation\PresenceVerifierInterface
- */
- protected $presenceVerifier;
- /**
- * The failed validation rules.
- *
- * @var array
- */
- protected $failedRules = [];
- /**
- * Attributes that should be excluded from the validated data.
- *
- * @var array
- */
- protected $excludeAttributes = [];
- /**
- * The message bag instance.
- *
- * @var \Illuminate\Support\MessageBag
- */
- protected $messages;
- /**
- * The data under validation.
- *
- * @var array
- */
- protected $data;
- /**
- * The initial rules provided.
- *
- * @var array
- */
- protected $initialRules;
- /**
- * The rules to be applied to the data.
- *
- * @var array
- */
- protected $rules;
- /**
- * The current rule that is validating.
- *
- * @var string
- */
- protected $currentRule;
- /**
- * The array of wildcard attributes with their asterisks expanded.
- *
- * @var array
- */
- protected $implicitAttributes = [];
- /**
- * The callback that should be used to format the attribute.
- *
- * @var callable|null
- */
- protected $implicitAttributesFormatter;
- /**
- * The cached data for the "distinct" rule.
- *
- * @var array
- */
- protected $distinctValues = [];
- /**
- * All of the registered "after" callbacks.
- *
- * @var array
- */
- protected $after = [];
- /**
- * The array of custom error messages.
- *
- * @var array
- */
- public $customMessages = [];
- /**
- * The array of fallback error messages.
- *
- * @var array
- */
- public $fallbackMessages = [];
- /**
- * The array of custom attribute names.
- *
- * @var array
- */
- public $customAttributes = [];
- /**
- * The array of custom displayable values.
- *
- * @var array
- */
- public $customValues = [];
- /**
- * Indicates if the validator should stop on the first rule failure.
- *
- * @var bool
- */
- protected $stopOnFirstFailure = false;
- /**
- * Indicates that unvalidated array keys should be excluded, even if the parent array was validated.
- *
- * @var bool
- */
- public $excludeUnvalidatedArrayKeys = false;
- /**
- * All of the custom validator extensions.
- *
- * @var array
- */
- public $extensions = [];
- /**
- * All of the custom replacer extensions.
- *
- * @var array
- */
- public $replacers = [];
- /**
- * The validation rules that may be applied to files.
- *
- * @var string[]
- */
- protected $fileRules = [
- 'Between',
- 'Dimensions',
- 'Encoding',
- 'Extensions',
- 'File',
- 'Image',
- 'Max',
- 'Mimes',
- 'Mimetypes',
- 'Min',
- 'Size',
- ];
- /**
- * The validation rules that imply the field is required.
- *
- * @var string[]
- */
- protected $implicitRules = [
- 'Accepted',
- 'AcceptedIf',
- 'Declined',
- 'DeclinedIf',
- 'Filled',
- 'Missing',
- 'MissingIf',
- 'MissingUnless',
- 'MissingWith',
- 'MissingWithAll',
- 'Present',
- 'PresentIf',
- 'PresentUnless',
- 'PresentWith',
- 'PresentWithAll',
- 'Required',
- 'RequiredIf',
- 'RequiredIfAccepted',
- 'RequiredIfDeclined',
- 'RequiredUnless',
- 'RequiredWith',
- 'RequiredWithAll',
- 'RequiredWithout',
- 'RequiredWithoutAll',
- ];
- /**
- * The validation rules which depend on other fields as parameters.
- *
- * @var string[]
- */
- protected $dependentRules = [
- 'After',
- 'AfterOrEqual',
- 'Before',
- 'BeforeOrEqual',
- 'Confirmed',
- 'Different',
- 'ExcludeIf',
- 'ExcludeUnless',
- 'ExcludeWith',
- 'ExcludeWithout',
- 'Gt',
- 'Gte',
- 'Lt',
- 'Lte',
- 'AcceptedIf',
- 'DeclinedIf',
- 'RequiredIf',
- 'RequiredIfAccepted',
- 'RequiredIfDeclined',
- 'RequiredUnless',
- 'RequiredWith',
- 'RequiredWithAll',
- 'RequiredWithout',
- 'RequiredWithoutAll',
- 'PresentIf',
- 'PresentUnless',
- 'PresentWith',
- 'PresentWithAll',
- 'Prohibited',
- 'ProhibitedIf',
- 'ProhibitedIfAccepted',
- 'ProhibitedIfDeclined',
- 'ProhibitedUnless',
- 'Prohibits',
- 'MissingIf',
- 'MissingUnless',
- 'MissingWith',
- 'MissingWithAll',
- 'Same',
- 'Unique',
- ];
- /**
- * The validation rules that can exclude an attribute.
- *
- * @var string[]
- */
- protected $excludeRules = ['Exclude', 'ExcludeIf', 'ExcludeUnless', 'ExcludeWith', 'ExcludeWithout'];
- /**
- * The size related validation rules.
- *
- * @var string[]
- */
- protected $sizeRules = ['Size', 'Between', 'Min', 'Max', 'Gt', 'Lt', 'Gte', 'Lte'];
- /**
- * The numeric related validation rules.
- *
- * @var string[]
- */
- protected $numericRules = ['Numeric', 'Integer', 'Decimal'];
- /**
- * The default numeric related validation rules.
- *
- * @var string[]
- */
- protected $defaultNumericRules = ['Numeric', 'Integer', 'Decimal'];
- /**
- * The current random hash for the validator.
- *
- * @var string|null
- */
- protected static $placeholderHash;
- /**
- * The exception to throw upon failure.
- *
- * @var class-string<\Illuminate\Validation\ValidationException>
- */
- protected $exception = ValidationException::class;
- /**
- * The custom callback to determine if an exponent is within allowed range.
- *
- * @var callable|null
- */
- protected $ensureExponentWithinAllowedRangeUsing;
- /**
- * Create a new Validator instance.
- *
- * @param \Illuminate\Contracts\Translation\Translator $translator
- * @param array $data
- * @param array $rules
- * @param array $messages
- * @param array $attributes
- */
- public function __construct(
- Translator $translator,
- array $data,
- array $rules,
- array $messages = [],
- array $attributes = [],
- ) {
- if (! isset(static::$placeholderHash)) {
- static::$placeholderHash = Str::random();
- }
- $this->initialRules = $rules;
- $this->translator = $translator;
- $this->customMessages = $messages;
- $this->data = $this->parseData($data);
- $this->customAttributes = $attributes;
- $this->setRules($rules);
- }
- /**
- * Parse the data array, converting dots and asterisks.
- *
- * @param array $data
- * @return array
- */
- public function parseData(array $data)
- {
- $newData = [];
- foreach ($data as $key => $value) {
- if (is_array($value)) {
- $value = $this->parseData($value);
- }
- $key = str_replace(
- ['.', '*'],
- ['__dot__'.static::$placeholderHash, '__asterisk__'.static::$placeholderHash],
- $key
- );
- $newData[$key] = $value;
- }
- return $newData;
- }
- /**
- * Replace the placeholders used in data keys.
- *
- * @param array $data
- * @return array
- */
- protected function replacePlaceholders($data)
- {
- $originalData = [];
- foreach ($data as $key => $value) {
- $originalData[$this->replacePlaceholderInString($key)] = is_array($value)
- ? $this->replacePlaceholders($value)
- : $value;
- }
- return $originalData;
- }
- /**
- * Replace the placeholders in the given string.
- *
- * @param string $value
- * @return string
- */
- protected function replacePlaceholderInString(string $value)
- {
- return str_replace(
- ['__dot__'.static::$placeholderHash, '__asterisk__'.static::$placeholderHash],
- ['.', '*'],
- $value
- );
- }
- /**
- * Replace each field parameter dot placeholder with dot.
- *
- * @param array $parameters
- * @return array
- */
- protected function replaceDotPlaceholderInParameters(array $parameters)
- {
- return array_map(function ($field) {
- return str_replace('__dot__'.static::$placeholderHash, '.', $field);
- }, $parameters);
- }
- /**
- * Add an after validation callback.
- *
- * @param callable|array|string $callback
- * @return $this
- */
- public function after($callback)
- {
- if (is_array($callback) && ! is_callable($callback)) {
- foreach ($callback as $rule) {
- $this->after(method_exists($rule, 'after') ? $rule->after(...) : $rule);
- }
- return $this;
- }
- $this->after[] = fn () => $callback($this);
- return $this;
- }
- /**
- * Determine if the data passes the validation rules.
- *
- * @return bool
- */
- public function passes()
- {
- $this->messages = new MessageBag;
- [$this->distinctValues, $this->failedRules] = [[], []];
- // 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) {
- if ($this->shouldBeExcluded($attribute)) {
- $this->removeAttribute($attribute);
- continue;
- }
- if ($this->stopOnFirstFailure && $this->messages->isNotEmpty()) {
- break;
- }
- foreach ($rules as $rule) {
- $this->validateAttribute($attribute, $rule);
- if ($this->shouldBeExcluded($attribute)) {
- break;
- }
- if ($this->shouldStopValidating($attribute)) {
- break;
- }
- }
- }
- foreach ($this->rules as $attribute => $rules) {
- if ($this->shouldBeExcluded($attribute)) {
- $this->removeAttribute($attribute);
- }
- }
- // 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) {
- $after();
- }
- return $this->messages->isEmpty();
- }
- /**
- * Determine if the data fails the validation rules.
- *
- * @return bool
- */
- public function fails()
- {
- return ! $this->passes();
- }
- /**
- * Execute the callback if the data passes the validation rules.
- *
- * @template TWhenReturnType
- *
- * @param (callable($this): TWhenReturnType) $callback
- * @param (callable($this): TWhenReturnType)|null $default
- * @return $this|TWhenReturnType
- */
- public function whenPasses(callable $callback, ?callable $default = null)
- {
- if ($this->passes()) {
- return $callback($this) ?? $this;
- } elseif ($default) {
- return $default($this) ?? $this;
- }
- return $this;
- }
- /**
- * Execute the callback if the data fails the validation rules.
- *
- * @template TWhenReturnType
- *
- * @param (callable($this): TWhenReturnType) $callback
- * @param (callable($this): TWhenReturnType)|null $default
- * @return $this|TWhenReturnType
- */
- public function whenFails(callable $callback, ?callable $default = null)
- {
- if ($this->fails()) {
- return $callback($this) ?? $this;
- } elseif ($default) {
- return $default($this) ?? $this;
- }
- return $this;
- }
- /**
- * Determine if the attribute should be excluded.
- *
- * @param string $attribute
- * @return bool
- */
- protected function shouldBeExcluded($attribute)
- {
- foreach ($this->excludeAttributes as $excludeAttribute) {
- if ($attribute === $excludeAttribute ||
- Str::startsWith($attribute, $excludeAttribute.'.')) {
- return true;
- }
- }
- return false;
- }
- /**
- * Remove the given attribute.
- *
- * @param string $attribute
- * @return void
- */
- protected function removeAttribute($attribute)
- {
- Arr::forget($this->data, $attribute);
- Arr::forget($this->rules, $attribute);
- }
- /**
- * Run the validator's rules against its data.
- *
- * @return array
- *
- * @throws \Illuminate\Validation\ValidationException
- */
- public function validate()
- {
- throw_if($this->fails(), $this->exception, $this);
- return $this->validated();
- }
- /**
- * Run the validator's rules against its data.
- *
- * @param string $errorBag
- * @return array
- *
- * @throws \Illuminate\Validation\ValidationException
- */
- public function validateWithBag(string $errorBag)
- {
- try {
- return $this->validate();
- } catch (ValidationException $e) {
- $e->errorBag = $errorBag;
- throw $e;
- }
- }
- /**
- * Get a validated input container for the validated input.
- *
- * @param array|null $keys
- * @return \Illuminate\Support\ValidatedInput|array
- */
- public function safe(?array $keys = null)
- {
- return is_array($keys)
- ? (new ValidatedInput($this->validated()))->only($keys)
- : new ValidatedInput($this->validated());
- }
- /**
- * Get the attributes and values that were validated.
- *
- * @return array
- *
- * @throws \Illuminate\Validation\ValidationException
- */
- public function validated()
- {
- if (! $this->messages) {
- $this->passes();
- }
- throw_if($this->messages->isNotEmpty(), $this->exception, $this);
- $results = [];
- $missingValue = new stdClass;
- foreach ($this->getRules() as $key => $rules) {
- $value = data_get($this->getData(), $key, $missingValue);
- if ($this->excludeUnvalidatedArrayKeys &&
- (in_array('array', $rules) || in_array('list', $rules)) &&
- $value !== null &&
- ! empty(preg_grep('/^'.preg_quote($key, '/').'\.+/', array_keys($this->getRules())))) {
- continue;
- }
- if ($value !== $missingValue) {
- Arr::set($results, $key, $value);
- }
- }
- return $this->replacePlaceholders($results);
- }
- /**
- * Validate a given attribute against a rule.
- *
- * @param string $attribute
- * @param string $rule
- * @return void
- */
- protected function validateAttribute($attribute, $rule)
- {
- $this->currentRule = $rule;
- [$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 ($this->dependsOnOtherFields($rule)) {
- $parameters = $this->replaceDotInParameters($parameters);
- if ($keys = $this->getExplicitKeys($attribute)) {
- $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}";
- $this->numericRules = $this->defaultNumericRules;
- 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, true)) {
- return $unparsed;
- }
- }
- return $attribute;
- }
- /**
- * Replace each field parameter which has an escaped dot with the dot placeholder.
- *
- * @param array $parameters
- * @return array
- */
- protected function replaceDotInParameters(array $parameters)
- {
- return array_map(function ($field) {
- return static::encodeAttributeWithPlaceholder((string) ($field ?? ''));
- }, $parameters);
- }
- /**
- * 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)
- {
- if (in_array($rule, $this->excludeRules)) {
- return true;
- }
- 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)
- {
- $originalAttribute = $this->replacePlaceholderInString($attribute);
- $attribute = match (true) {
- $rule instanceof Rules\Email => $attribute,
- $rule instanceof Rules\File => $attribute,
- $rule instanceof Rules\Password => $attribute,
- default => $originalAttribute,
- };
- $value = is_array($value) ? $this->replacePlaceholders($value) : $value;
- if ($rule instanceof ValidatorAwareRule) {
- if ($attribute !== $originalAttribute) {
- $this->addCustomAttributes([
- $attribute => $this->customAttributes[$originalAttribute] ?? $originalAttribute,
- ]);
- }
- $rule->setValidator($this);
- }
- if ($rule instanceof DataAwareRule) {
- $rule->setData($this->data);
- }
- if (! $rule->passes($attribute, $value)) {
- $ruleClass = $rule instanceof InvokableValidationRule ?
- get_class($rule->invokable()) :
- get_class($rule);
- $this->failedRules[$originalAttribute][$ruleClass] = [];
- $messages = $this->getFromLocalArray($originalAttribute, $ruleClass) ?? $rule->message();
- $messages = $messages ? (array) $messages : [$ruleClass];
- foreach ($messages as $key => $message) {
- $key = is_string($key) ? $key : $originalAttribute;
- $this->messages->add($key, $this->makeReplacements(
- $message, $key, $ruleClass, []
- ));
- }
- }
- }
- /**
- * Check if we should stop further validations on a given attribute.
- *
- * @param string $attribute
- * @return bool
- */
- protected function shouldStopValidating($attribute)
- {
- $cleanedAttribute = $this->replacePlaceholderInString($attribute);
- if ($this->hasRule($attribute, ['Bail'])) {
- return $this->messages->has($cleanedAttribute);
- }
- if (isset($this->failedRules[$cleanedAttribute]) &&
- array_key_exists('uploaded', $this->failedRules[$cleanedAttribute])) {
- 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[$cleanedAttribute]) &&
- array_intersect(array_keys($this->failedRules[$cleanedAttribute]), $this->implicitRules);
- }
- /**
- * Add a failed rule and error message to the collection.
- *
- * @param string $attribute
- * @param string $rule
- * @param array $parameters
- * @return void
- */
- public function addFailure($attribute, $rule, $parameters = [])
- {
- if (! $this->messages) {
- $this->passes();
- }
- $attributeWithPlaceholders = $attribute;
- $attribute = $this->replacePlaceholderInString($attribute);
- if (in_array($rule, $this->excludeRules)) {
- return $this->excludeAttribute($attribute);
- }
- if ($this->dependsOnOtherFields($rule)) {
- $parameters = $this->replaceDotPlaceholderInParameters($parameters);
- }
- $this->messages->add($attribute, $this->makeReplacements(
- $this->getMessage($attributeWithPlaceholders, $rule), $attribute, $rule, $parameters
- ));
- $this->failedRules[$attribute][$rule] = $parameters;
- }
- /**
- * Add the given attribute to the list of excluded attributes.
- *
- * @param string $attribute
- * @return void
- */
- protected function excludeAttribute(string $attribute)
- {
- $this->excludeAttributes[] = $attribute;
- $this->excludeAttributes = array_unique($this->excludeAttributes);
- }
- /**
- * 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();
- }
- $invalid = array_intersect_key(
- $this->data, $this->attributesThatHaveMessages()
- );
- $result = [];
- $failed = Arr::only(Arr::dot($invalid), array_keys($this->failed()));
- foreach ($failed as $key => $failure) {
- Arr::set($result, $key, $failure);
- }
- return $result;
- }
- /**
- * Generate an array of all attributes that have messages.
- *
- * @return array
- */
- protected function attributesThatHaveMessages()
- {
- return (new Collection($this->messages()->toArray()))
- ->map(fn ($message, $key) => 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) {
- [$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
- */
- public function getValue($attribute)
- {
- return Arr::get($this->data, $attribute);
- }
- /**
- * Set the value of a given attribute.
- *
- * @param string $attribute
- * @param mixed $value
- * @return void
- */
- public function setValue($attribute, $value)
- {
- Arr::set($this->data, $attribute, $value);
- }
- /**
- * Get the validation rules.
- *
- * @return array
- */
- public function getRules()
- {
- return $this->rules;
- }
- /**
- * Get the validation rules with key placeholders removed.
- *
- * @return array
- */
- public function getRulesWithoutPlaceholders()
- {
- return (new Collection($this->rules))
- ->mapWithKeys(fn ($value, $key) => [
- static::decodeAttributeWithPlaceholder($key) => $value,
- ])
- ->all();
- }
- /**
- * Set the validation rules.
- *
- * @param array $rules
- * @return $this
- */
- public function setRules(array $rules)
- {
- $rules = (new Collection($rules))
- ->mapWithKeys(function ($value, $key) {
- return [static::encodeAttributeWithPlaceholder($key) => $value];
- })
- ->toArray();
- $this->initialRules = $rules;
- $this->rules = [];
- $this->addRules($rules);
- return $this;
- }
- /**
- * Append new validation rules to the validator.
- *
- * @param array $rules
- * @return $this
- */
- public function appendRules(array $rules)
- {
- $rules = (new Collection($rules))
- ->map(function ($value) {
- return is_string($value) ? explode('|', $value) : $value;
- })
- ->all();
- return $this->setRules(array_merge_recursive($this->getRulesWithoutPlaceholders(), $rules));
- }
- /**
- * Parse the given rules and merge them into current rules.
- *
- * @internal
- *
- * @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(ValidationRuleParser::filterConditionalRules($rules, $this->data));
- foreach ($response->rules as $key => $rule) {
- $this->rules[$key] = array_merge($this->rules[$key] ?? [], $rule);
- }
- $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->data);
- foreach ((array) $attribute as $key) {
- $response = (new ValidationRuleParser($this->data))->explode([$key => $rules]);
- $this->implicitAttributes = array_merge($response->implicitAttributes, $this->implicitAttributes);
- foreach ($response->rules as $ruleKey => $ruleValue) {
- if ($callback($payload, $this->dataForSometimesIteration($ruleKey, ! str_ends_with($key, '.*')))) {
- $this->addRules([static::encodeAttributeWithPlaceholder($ruleKey) => $ruleValue]);
- }
- }
- }
- return $this;
- }
- /**
- * Get the data that should be injected into the iteration of a wildcard "sometimes" callback.
- *
- * @param string $attribute
- * @param bool $removeLastSegmentOfAttribute
- * @return \Illuminate\Support\Fluent|mixed
- */
- private function dataForSometimesIteration(string $attribute, bool $removeLastSegmentOfAttribute)
- {
- $lastSegmentOfAttribute = strrchr($attribute, '.');
- $attribute = $lastSegmentOfAttribute && $removeLastSegmentOfAttribute
- ? Str::replaceLast($lastSegmentOfAttribute, '', $attribute)
- : $attribute;
- return is_array($data = data_get($this->data, $attribute))
- ? new Fluent($data)
- : $data;
- }
- /**
- * Instruct the validator to stop validating after the first rule failure.
- *
- * @param bool $stopOnFirstFailure
- * @return $this
- */
- public function stopOnFirstFailure($stopOnFirstFailure = true)
- {
- $this->stopOnFirstFailure = $stopOnFirstFailure;
- return $this;
- }
- /**
- * Register an array of custom validator extensions.
- *
- * @param array $extensions
- * @return void
- */
- public function addExtensions(array $extensions)
- {
- if ($extensions) {
- $keys = array_map(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 dependent 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(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 $attributes
- * @return $this
- */
- public function addCustomAttributes(array $attributes)
- {
- $this->customAttributes = array_merge($this->customAttributes, $attributes);
- return $this;
- }
- /**
- * Set the callback that used to format an implicit attribute.
- *
- * @param callable|null $formatter
- * @return $this
- */
- public function setImplicitAttributesFormatter(?callable $formatter = null)
- {
- $this->implicitAttributesFormatter = $formatter;
- 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.
- *
- * @param string|null $connection
- * @return \Illuminate\Validation\PresenceVerifierInterface
- *
- * @throws \RuntimeException
- */
- public function getPresenceVerifier($connection = null)
- {
- if (! isset($this->presenceVerifier)) {
- throw new RuntimeException('Presence verifier has not been set.');
- }
- if ($this->presenceVerifier instanceof DatabasePresenceVerifierInterface) {
- $this->presenceVerifier->setConnection($connection);
- }
- return $this->presenceVerifier;
- }
- /**
- * Set the Presence Verifier implementation.
- *
- * @param \Illuminate\Validation\PresenceVerifierInterface $presenceVerifier
- * @return void
- */
- public function setPresenceVerifier(PresenceVerifierInterface $presenceVerifier)
- {
- $this->presenceVerifier = $presenceVerifier;
- }
- /**
- * Get the exception to throw upon failed validation.
- *
- * @return class-string<\Illuminate\Validation\ValidationException>
- */
- public function getException()
- {
- return $this->exception;
- }
- /**
- * Set the exception to throw upon failed validation.
- *
- * @param class-string<\Illuminate\Validation\ValidationException> $exception
- * @return $this
- *
- * @throws \InvalidArgumentException
- */
- public function setException($exception)
- {
- if (! is_a($exception, ValidationException::class, true)) {
- throw new InvalidArgumentException(
- sprintf('Exception [%s] is invalid. It must extend [%s].', $exception, ValidationException::class)
- );
- }
- $this->exception = $exception;
- return $this;
- }
- /**
- * Ensure exponents are within range using the given callback.
- *
- * @param callable(int $scale, string $attribute, mixed $value) $callback
- * @return $this
- */
- public function ensureExponentWithinAllowedRangeUsing($callback)
- {
- $this->ensureExponentWithinAllowedRangeUsing = $callback;
- return $this;
- }
- /**
- * 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 $callback(...array_values($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)
- {
- [$class, $method] = Str::parseCallback($callback, 'validate');
- return $this->container->make($class)->{$method}(...array_values($parameters));
- }
- /**
- * Encode the attribute with the placeholder hash.
- *
- * @param string $attribute
- * @return string
- */
- protected static function encodeAttributeWithPlaceholder(string $attribute)
- {
- return str_replace('\.', '__dot__'.static::$placeholderHash, $attribute);
- }
- /**
- * Decode an attribute with a placeholder hash.
- *
- * @param string $attribute
- * @return string
- */
- protected static function decodeAttributeWithPlaceholder(string $attribute)
- {
- return str_replace('__dot__'.static::$placeholderHash, '\\.', $attribute);
- }
- /**
- * Flush the validator's global state.
- *
- * @return void
- */
- public static function flushState()
- {
- static::$placeholderHash = null;
- }
- /**
- * 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
- ));
- }
- }
|