%PDF- %PDF-
Direktori : /www/loslex/demo/vendor/livewire/livewire/src/Features/SupportValidation/ |
Current File : /www/loslex/demo/vendor/livewire/livewire/src/Features/SupportValidation/HandlesValidation.php |
<?php namespace Livewire\Features\SupportValidation; use function Livewire\invade; use function Livewire\store; use Illuminate\Contracts\Support\Arrayable; use Livewire\Wireable; use Livewire\Exceptions\MissingRulesException; use Livewire\Drawer\Utils; use Illuminate\Validation\ValidationException; use Illuminate\Support\MessageBag; use Illuminate\Support\Facades\Validator; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\ViewErrorBag; use Livewire\Form; trait HandlesValidation { protected $withValidatorCallback; protected $rulesFromOutside = []; protected $messagesFromOutside = []; protected $validationAttributesFromOutside = []; public function addRulesFromOutside($rules) { $this->rulesFromOutside[] = $rules; } public function addMessagesFromOutside($messages) { $this->messagesFromOutside[] = $messages; } public function addValidationAttributesFromOutside($validationAttributes) { $this->validationAttributesFromOutside[] = $validationAttributes; } public function getErrorBag() { if (! store($this)->has('errorBag')) { $previouslySharedErrors = app('view')->getShared()['errors'] ?? new ViewErrorBag; $this->setErrorBag($previouslySharedErrors->getMessages()); } return store($this)->get('errorBag'); } public function addError($name, $message) { return $this->getErrorBag()->add($name, $message); } public function setErrorBag($bag) { return store($this)->set('errorBag', $bag instanceof MessageBag ? $bag : new MessageBag($bag) ); } public function resetErrorBag($field = null) { $fields = (array) $field; if (empty($fields)) { $errorBag = new MessageBag; $this->setErrorBag($errorBag); return $errorBag; } $this->setErrorBag( $this->errorBagExcept($fields) ); } public function clearValidation($field = null) { $this->resetErrorBag($field); } public function resetValidation($field = null) { $this->resetErrorBag($field); } public function errorBagExcept($field) { $fields = (array) $field; return new MessageBag( collect($this->getErrorBag()) ->reject(function ($messages, $messageKey) use ($fields) { return collect($fields)->some(function ($field) use ($messageKey) { return str($messageKey)->is($field); }); }) ->toArray() ); } public function getRules() { $rulesFromComponent = []; if (method_exists($this, 'rules')) $rulesFromComponent = $this->rules(); else if (property_exists($this, 'rules')) $rulesFromComponent = $this->rules; $rulesFromOutside = array_merge_recursive( ...array_map( fn($i) => value($i), $this->rulesFromOutside ) ); return array_merge($rulesFromComponent, $rulesFromOutside); } protected function getMessages() { $messages = []; if (method_exists($this, 'messages')) $messages = $this->messages(); elseif (property_exists($this, 'messages')) $messages = $this->messages; $messagesFromOutside = array_merge( ...array_map( fn($i) => value($i), $this->messagesFromOutside ) ); return array_merge($messages, $messagesFromOutside); } protected function getValidationAttributes() { $validationAttributes = []; if (method_exists($this, 'validationAttributes')) $validationAttributes = $this->validationAttributes(); elseif (property_exists($this, 'validationAttributes')) $validationAttributes = $this->validationAttributes; $validationAttributesFromOutside = array_merge( ...array_map( fn($i) => value($i), $this->validationAttributesFromOutside ) ); return array_merge($validationAttributes, $validationAttributesFromOutside); } protected function getValidationCustomValues() { if (method_exists($this, 'validationCustomValues')) return $this->validationCustomValues(); if (property_exists($this, 'validationCustomValues')) return $this->validationCustomValues; return []; } public function rulesForModel($name) { if (empty($this->getRules())) return collect(); return collect($this->getRules()) ->filter(function ($value, $key) use ($name) { return Utils::beforeFirstDot($key) === $name; }); } public function hasRuleFor($dotNotatedProperty) { $propertyWithStarsInsteadOfNumbers = $this->ruleWithNumbersReplacedByStars($dotNotatedProperty); // If property has numeric indexes in it, if ($dotNotatedProperty !== $propertyWithStarsInsteadOfNumbers) { return collect($this->getRules())->keys()->contains($propertyWithStarsInsteadOfNumbers); } return collect($this->getRules()) ->keys() ->map(function ($key) { return (string) str($key)->before('.*'); })->contains($dotNotatedProperty); } public function ruleWithNumbersReplacedByStars($dotNotatedProperty) { // Convert foo.0.bar.1 -> foo.*.bar.* return (string) str($dotNotatedProperty) // Replace all numeric indexes with an array wildcard: (.0., .10., .007.) => .*. // In order to match overlapping numerical indexes (foo.1.2.3.4.name), // We need to use a positive look-behind, that's technically all the magic here. // For better understanding, see: https://regexr.com/5d1n3 ->replaceMatches('/(?<=(\.))\d+\./', '*.') // Replace all numeric indexes at the end of the name with an array wildcard // (Same as the previous regex, but ran only at the end of the string) // For better undestanding, see: https://regexr.com/5d1n6 ->replaceMatches('/\.\d+$/', '.*'); } public function missingRuleFor($dotNotatedProperty) { return ! $this->hasRuleFor($dotNotatedProperty); } public function withValidator($callback) { $this->withValidatorCallback = $callback; return $this; } protected function checkRuleMatchesProperty($rules, $data) { collect($rules) ->keys() ->each(function($ruleKey) use ($data) { throw_unless( array_key_exists(Utils::beforeFirstDot($ruleKey), $data), new \Exception('No property found for validation: ['.$ruleKey.']') ); }); } public function validate($rules = null, $messages = [], $attributes = []) { $isUsingGlobalRules = is_null($rules); [$rules, $messages, $attributes] = $this->providedOrGlobalRulesMessagesAndAttributes($rules, $messages, $attributes); $data = $this->prepareForValidation( $this->getDataForValidation($rules) ); $this->checkRuleMatchesProperty($rules, $data); $ruleKeysToShorten = $this->getModelAttributeRuleKeysToShorten($data, $rules); $data = $this->unwrapDataForValidation($data); $validator = Validator::make($data, $rules, $messages, $attributes); if ($this->withValidatorCallback) { call_user_func($this->withValidatorCallback, $validator); $this->withValidatorCallback = null; } $this->shortenModelAttributesInsideValidator($ruleKeysToShorten, $validator); $customValues = $this->getValidationCustomValues(); if (! empty($customValues)) { $validator->addCustomValues($customValues); } if ($this->isRootComponent() && $isUsingGlobalRules) { $validatedData = $this->withFormObjectValidators($validator, fn () => $validator->validate(), fn ($form) => $form->validate()); } else { $validatedData = $validator->validate(); } $this->resetErrorBag(); return $validatedData; } protected function isRootComponent() { // Because this trait is used for form objects as well... return $this instanceof \Livewire\Component; } protected function withFormObjectValidators($validator, $validateSelf, $validateForm) { $cumulativeErrors = new MessageBag; $cumulativeData = []; $formExceptions = []; // First, run sub-validators... foreach ($this->getFormObjects() as $form) { try { // Only run sub-validator if the sub-validator has rules... if (filled($form->getRules())) { $cumulativeData = array_merge($cumulativeData, $validateForm($form)); } } catch (ValidationException $e) { $cumulativeErrors->merge($e->validator->errors()); $formExceptions[] = $e; } } // Now run main validator... try { $cumulativeData = array_merge($cumulativeData, $validateSelf()); } catch (ValidationException $e) { // If the main validator has errors, merge them with subs and rethrow... $e->validator->errors()->merge($cumulativeErrors); throw $e; } // If main validation passed, go through other sub-validation exceptions // and throw the first one with the cumulative messages... foreach ($formExceptions as $e) { $e->validator->errors()->merge($cumulativeErrors->unique()); throw $e; } // All validation has passed, we can return the data... return $cumulativeData; } public function validateOnly($field, $rules = null, $messages = [], $attributes = [], $dataOverrides = []) { $property = (string) str($field)->before('.'); // If validating a field in a form object, defer validation to that form object... if ( $this->isRootComponent() && ($form = $this->all()[$property] ?? false) instanceof Form ) { $stripPrefix = (string) str($field)->after('.'); return $form->validateOnly($stripPrefix, $rules, $messages, $attributes, $dataOverrides); } $isUsingGlobalRules = is_null($rules); [$rules, $messages, $attributes] = $this->providedOrGlobalRulesMessagesAndAttributes($rules, $messages, $attributes); // Loop through rules and swap any wildcard '*' with keys from field, then filter down to only // rules that match the field, but return the rules without wildcard characters replaced, // so that custom attributes and messages still work as they need wildcards to work. $rulesForField = collect($rules) ->filter(function($value, $rule) use ($field) { if(! str($field)->is($rule)) { return false; } $fieldArray = str($field)->explode('.'); $ruleArray = str($rule)->explode('.'); for($i = 0; $i < count($fieldArray); $i++) { if(isset($ruleArray[$i]) && $ruleArray[$i] === '*') { $ruleArray[$i] = $fieldArray[$i]; } } $rule = $ruleArray->join('.'); return $field === $rule; }); $ruleForField = $rulesForField->keys()->first(); $rulesForField = $rulesForField->toArray(); $ruleKeysForField = array_keys($rulesForField); $data = array_merge($this->getDataForValidation($rules), $dataOverrides); $data = $this->prepareForValidation($data); $this->checkRuleMatchesProperty($rules, $data); $ruleKeysToShorten = $this->getModelAttributeRuleKeysToShorten($data, $rules); $data = $this->unwrapDataForValidation($data); // If a matching rule is found, then filter collections down to keys specified in the field, // while leaving all other data intact. If a key isn't specified and instead there is a // wildcard '*' then leave that whole collection intact. This ensures that any rules // that depend on other fields/ properties still work. if ($ruleForField) { $ruleArray = str($ruleForField)->explode('.'); $fieldArray = str($field)->explode('.'); $data = $this->filterCollectionDataDownToSpecificKeys($data, $ruleArray, $fieldArray); } $validator = Validator::make($data, $rulesForField, $messages, $attributes); if ($this->withValidatorCallback) { call_user_func($this->withValidatorCallback, $validator); $this->withValidatorCallback = null; } $this->shortenModelAttributesInsideValidator($ruleKeysToShorten, $validator); $customValues = $this->getValidationCustomValues(); if (!empty($customValues)) { $validator->addCustomValues($customValues); } try { $result = $validator->validate(); } catch (ValidationException $e) { $messages = $e->validator->getMessageBag(); invade($e->validator)->messages = $messages->merge( $this->errorBagExcept($ruleKeysForField) ); throw $e; } $this->resetErrorBag($ruleKeysForField); return $result; } protected function filterCollectionDataDownToSpecificKeys($data, $ruleKeys, $fieldKeys) { // Filter data down to specified keys in collections, but leave all other data intact if (count($ruleKeys)) { $ruleKey = $ruleKeys->shift(); $fieldKey = $fieldKeys->shift(); if ($fieldKey == '*') { // If the specified field has a '*', then loop through the collection and keep the whole collection intact. foreach ($data as $key => $value) { $data[$key] = $this->filterCollectionDataDownToSpecificKeys($value, $ruleKeys, $fieldKeys); } } else { // Otherwise filter collection down to a specific key $keyData = data_get($data, $fieldKey, null); if ($ruleKey == '*') { $data = []; } data_set($data, $fieldKey, $this->filterCollectionDataDownToSpecificKeys($keyData, $ruleKeys, $fieldKeys)); } } return $data; } protected function getModelAttributeRuleKeysToShorten($data, $rules) { // If a model ($foo) is a property, and the validation rule is // "foo.bar", then set the attribute to just "bar", so that // the validation message is shortened and more readable. $toShorten = []; foreach ($rules as $key => $value) { $propertyName = Utils::beforeFirstDot($key); if ($data[$propertyName] instanceof Model) { $toShorten[] = $key; } } return $toShorten; } protected function shortenModelAttributesInsideValidator($ruleKeys, $validator) { foreach ($ruleKeys as $key) { if (str($key)->snake()->replace('_', ' ')->is($validator->getDisplayableAttribute($key))) { $validator->addCustomAttributes([$key => $validator->getDisplayableAttribute(Utils::afterFirstDot($key))]); } } } protected function providedOrGlobalRulesMessagesAndAttributes($rules, $messages, $attributes) { $rules = is_null($rules) ? $this->getRules() : $rules; // Before we warn the user about not providing validation rules, // Let's make sure there are no form objects that contain them... $allRules = $rules; if ($this->isRootComponent()) { foreach ($this->getFormObjects() as $form) { $allRules = array_merge($allRules, $form->getRules()); } } throw_if(empty($allRules), new MissingRulesException($this)); $messages = empty($messages) ? $this->getMessages() : $messages; $attributes = empty($attributes) ? $this->getValidationAttributes() : $attributes; return [$rules, $messages, $attributes]; } protected function getDataForValidation($rules) { return Utils::getPublicPropertiesDefinedOnSubclass($this); } protected function unwrapDataForValidation($data) { return collect($data)->map(function ($value) { // @todo: this logic should be contained within "SupportWireables"... if ($value instanceof Wireable) return $value->toLivewire(); else if ($value instanceof Arrayable) return $value->toArray(); return $value; })->all(); } protected function prepareForValidation($attributes) { return $attributes; } }