Created
November 24, 2020 17:27
-
-
Save kmuenkel/aff413e7e14051bcdb49c682cedf8eb8 to your computer and use it in GitHub Desktop.
Laravel Validator Fix
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Providers; | |
use ReflectionException; | |
use Illuminate\Validation\Validator; | |
use App\Validators\Rules\CustomRule; | |
use Illuminate\Support\ServiceProvider; | |
use Illuminate\Contracts\Validation\Rule; | |
use Lti\Overrides\Validation\Validator as ValidatorOverride; | |
use Illuminate\Support\Facades\Validator as ValidatorFacade; | |
use Illuminate\Contracts\Validation\Factory as ValidationFactory; | |
/** | |
* Class LtiServiceProvider | |
* @package Lti\Providers | |
*/ | |
class LtiServiceProvider extends ServiceProvider | |
{ | |
/** | |
* @void | |
* @throws ReflectionException | |
*/ | |
public function boot() | |
{ | |
$this->loadCustomValidationRules(['ruleName' => CustomRule::class]); | |
$this->overrideValidator(); | |
} | |
/** | |
* @void | |
*/ | |
protected function overrideValidator() | |
{ | |
$validatorFactory = app(ValidationFactory::class); | |
$validatorFactory->resolver(function (...$args) { | |
return new ValidatorOverride(...$args); | |
}); | |
} | |
/** | |
* @param string[] $rules | |
*/ | |
protected function loadCustomValidationRules(array $rules) | |
{ | |
foreach ($rules as $name => $ruleClass) { | |
//Logically, a string by-reference variable should work for this, but it's disallowed in this context | |
$message = new class { | |
/** | |
* @var Validator|null | |
*/ | |
public static $validator; | |
/** | |
* @var Rule|null | |
*/ | |
public static $rule; | |
/** | |
* @var string[] | |
*/ | |
public static $parameters = []; | |
/** | |
* @var mixed | |
*/ | |
public static $attribute = null; | |
/** | |
* @var string | |
*/ | |
public static $ruleName = ''; | |
/** | |
* @return string | |
*/ | |
public function __toString() | |
{ | |
return static::$validator->makeReplacements( | |
static::$rule->message(), | |
static::$attribute, | |
static::$ruleName, | |
static::$parameters | |
); | |
} | |
}; | |
$message::$rule = $custom = app($ruleClass); | |
$instance = null; | |
//Intercept the arguments passed to the Rule::passes() method | |
$extension = function ($attribute, $value, $parameters, Validator $validator) use ($custom, $message) { | |
$message::$validator = $validator; | |
$message::$attribute = $attribute; | |
$message::$parameters = $parameters; | |
return $custom->passes($attribute, $value, $parameters, $validator); | |
}; | |
//ValidatorFacade::replacer() would result in infinite recursion when using Validator::makeReplacements() | |
ValidatorFacade::extend($message::$ruleName = $name, $extension, $message); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Overrides\Validation; | |
use Illuminate\Support\Arr; | |
use Illuminate\Validation\ValidationData as BaseValidationData; | |
/** | |
* Class ValidationData | |
* @package Lti\Overrides\Validation | |
*/ | |
class ValidationData extends BaseValidationData | |
{ | |
/** | |
* Correct the absence of the preg_quote() $delimiter | |
* @inheritDoc | |
*/ | |
protected static function extractValuesForWildcards($masterData, $data, $attribute) | |
{ | |
$keys = []; | |
$pattern = str_replace('\*', '[^\.]+', preg_quote($attribute, '/')); | |
foreach ($data as $key => $value) { | |
try { | |
if ((bool)preg_match('/^' . $pattern . '/', $key, $matches)) { | |
$keys[] = $matches[0]; | |
} | |
} catch (\Exception $e) { | |
dd($pattern); | |
} | |
} | |
$keys = array_unique($keys); | |
$data = []; | |
foreach ($keys as $key) { | |
$data[$key] = Arr::get($masterData, $key); | |
} | |
return $data; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Overrides\Validation; | |
use Illuminate\Support\Str; | |
use Illuminate\Validation\ValidationRuleParser as BaseValidationRuleParser; | |
/** | |
* Class ValidationRuleParser | |
* @package Lti\Overrides\Validation | |
*/ | |
class ValidationRuleParser extends BaseValidationRuleParser | |
{ | |
/** | |
* Correct the absence of the preg_quote() $delimiter | |
* @inheritDoc | |
*/ | |
protected function explodeWildcardRules($results, $attribute, $rules) | |
{ | |
$pattern = str_replace('\*', '[^\.]*', preg_quote($attribute, '/')); | |
$data = ValidationData::initializeAndGatherData($attribute, $this->data); | |
foreach ($data as $key => $value) { | |
if (Str::startsWith($key, $attribute) || (bool) preg_match('/^'.$pattern.'\z/', $key)) { | |
foreach ((array) $rules as $rule) { | |
$this->implicitAttributes[$attribute][] = $key; | |
$results = $this->mergeRules($results, $key, $rule); | |
} | |
} | |
} | |
return $results; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Overrides\Validation; | |
use DateTime; | |
use Illuminate\Validation\Validator as BaseValidator; | |
use Symfony\Component\HttpFoundation\File\UploadedFile; | |
use Illuminate\Contracts\Validation\Rule as RuleContract; | |
/** | |
* Class Validator | |
* @package Lti\Overrides\Validation | |
*/ | |
class Validator extends BaseValidator | |
{ | |
/** | |
* Leverage the ValidationData override | |
* @inheritDoc | |
*/ | |
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); | |
} | |
/** | |
* Leverage the ValidationRuleParser override | |
* @inheritDoc | |
*/ | |
public function addRules($rules) | |
{ | |
$response = (new ValidationRuleParser($this->data))->explode($rules); | |
$this->rules = array_merge_recursive($this->rules, $response->rules); | |
$this->implicitAttributes = array_merge($this->implicitAttributes, $response->implicitAttributes); | |
} | |
/** | |
* Move the replacement of [$this->dotPlaceholder, '__asterisk__'] with ['.', '*'], as is the case in the | |
* parent::addFailure(). The asterisk won't be present anyway, due to $this->addRules(). And the dots need to | |
* remain as $this->dotPlaceholder so $this->shouldBeExcluded() can locate the corresponding value properly by | |
* $this->excludeAttribute(). But the conversion does still need to be done before the error message is produced. | |
* @inheritDoc | |
*/ | |
public function addFailure($attribute, $rule, $parameters = []) | |
{ | |
!$this->messages && $this->passes(); | |
if (in_array($rule, $this->excludeRules)) { | |
$this->excludeAttribute($attribute); | |
return; | |
} | |
$attribute = str_replace($this->dotPlaceholder, '\.', $attribute); | |
$message = $this->getMessage($attribute, $rule); | |
$message = $this->makeReplacements($message, $attribute, $rule, $parameters); | |
$this->messages->add($attribute, $message); | |
$this->failedRules[$attribute][$rule] = $parameters; | |
} | |
/** | |
* If the rule is one that references another field, the first parameter/field must go through the same dot | |
* placeholder conversion as the field names in order for a match to be possible | |
* @inheritDoc | |
*/ | |
protected function validateAttribute($attribute, $rule) | |
{ | |
$this->currentRule = $rule; | |
[$rule, $parameters] = ValidationRuleParser::parse($rule); | |
if (in_array($rule, $this->excludeRules)) { | |
$parameters[0] = str_replace('\.', $this->dotPlaceholder, $parameters[0]); | |
} | |
if (!$rule) { | |
return null; | |
} | |
if (($keys = $this->getExplicitKeys($attribute)) && $this->dependsOnOtherFields($rule)) { | |
$parameters = $this->replaceAsterisksInParameters($parameters, $keys); | |
} | |
$value = $this->getValue($attribute); | |
$rules = array_merge($this->fileRules, $this->implicitRules); | |
if ($value instanceof UploadedFile && ! $value->isValid() && $this->hasRule($attribute, $rules)) { | |
$this->addFailure($attribute, 'uploaded', []); | |
return null; | |
} | |
$validatable = $this->isValidatable($rule, $attribute, $value); | |
if ($rule instanceof RuleContract) { | |
$validatable && $this->validateUsingCustomRule($attribute, $value, $rule); | |
return null; | |
} | |
$method = "validate{$rule}"; | |
$isValid = $this->$method($attribute, $value, $parameters, $this); | |
if ($validatable && !$isValid) { | |
$this->addFailure($attribute, $rule, $parameters); | |
} | |
} | |
/** | |
* Ignore 'required' rules on elements within arrays that are missing in their entirety. The parent arrays can be | |
* set to 'required' if their existence is necessary, rather than expect it as a side effect of nested data. | |
* @inheritDoc | |
*/ | |
public function validateRequired($attribute, $value) | |
{ | |
if (!($valid = parent::validateRequired($attribute, $value))) { | |
$parent = explode('.', $attribute); | |
$attributeIsNested = count($parent) >= 2; | |
array_pop($parent); | |
$parent = implode('.', $parent); | |
$parentExists = $this->getValue($parent); | |
$valid |= ($attributeIsNested && !$parentExists); | |
} | |
return $valid; | |
} | |
/** | |
* ISO8601 can be represented with a colon in the timezone ("c"), without one (DateTime::ISO8601), or Zulu | |
* @inheritDoc | |
*/ | |
public function validateDateFormat($attribute, $value, $parameters) | |
{ | |
$this->requireParameterCount(1, $parameters, 'date_format'); | |
if (!is_string($value) && !is_numeric($value)) { | |
return false; | |
} | |
$format = $parameters[0]; | |
$date = DateTime::createFromFormat("!$format", $value); | |
$acceptable = [$date->format($format)]; | |
if (in_array($format, [DateTime::ISO8601, 'c', 'Y-m-d\TH:i:s\Z'])) { | |
$acceptable += [ | |
$date->format(DateTime::ISO8601), | |
$date->format('c'), | |
gmdate('Y-m-d\TH:i:s\Z', $date->getTimestamp()) | |
]; | |
} | |
return $date && in_array($value, $acceptable); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment