Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
JasonTheAdams committed Mar 28, 2023
2 parents cc5213d + c5e7fcc commit 03aa1f4
Show file tree
Hide file tree
Showing 2 changed files with 287 additions and 79 deletions.
254 changes: 175 additions & 79 deletions src/ValidationRuleSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,126 +45,96 @@ public function __construct(ValidationRulesRegistrar $register)
public function rules(...$rules): self
{
foreach ($rules as $rule) {
if ($rule instanceof Closure) {
$this->validateClosureRule($rule);
$this->rules[] = $rule;
} elseif ($rule instanceof ValidationRule) {
$this->rules[] = $rule;
} elseif (is_string($rule)) {
$this->rules[] = $this->getRuleFromString($rule);
} else {
Config::throwInvalidArgumentException(
sprintf(
'Validation rule must be a string, instance of %s, or a closure',
ValidationRule::class
)
);
}
$this->rules[] = $this->sanitizeRule($rule);
}

return $this;
}

/**
* Validates that a closure rule has the proper parameters to be used as a validation rule.
* Prepends a given rule to the start of the rules array.
*
* @since 1.0.0
* @since 1.3.0
*
* @return void
* @param string|ValidationRule|Closure $rule
*/
private function validateClosureRule(Closure $closure)
public function prependRule($rule): self
{
try {
$reflection = new ReflectionFunction($closure);
} catch (ReflectionException $e) {
Config::throwInvalidArgumentException(
'Unable to validate closure parameters. Please ensure that the closure is valid.'
);
}
array_unshift($this->rules, $this->sanitizeRule($rule));

$parameters = $reflection->getParameters();
$parameterCount = count($parameters);
return $this;
}

if ($parameterCount < 2 || $parameterCount > 4) {
Config::throwInvalidArgumentException(
"Validation rule closure must accept between 2 and 4 parameters, $parameterCount given."
);
}
/**
* Replaces the given rule at the same index position or appends it if it doesn't exist.
*
* @since 1.3.0
*
* @param string|ValidationRule|Closure $rule
*
* @return bool True if the rule was replaced, false if it was appended.
*/
public function replaceOrAppendRule(string $ruleId, $rule): bool
{
$replaced = $this->replaceRule($ruleId, $rule);

$parameterType = $this->getParameterTypeName($parameters[1]);
if ($parameterType !== null && $parameterType !== 'Closure') {
Config::throwInvalidArgumentException(
"Validation rule closure must accept a Closure as the second parameter, {$parameterType} given."
);
}
if (!$replaced) {
$this->rules($rule);

$parameterType = $parameterCount > 2 ? $this->getParameterTypeName($parameters[2]) : null;
if ($parameterType !== null && $parameterType !== 'string') {
Config::throwInvalidArgumentException(
"Validation rule closure must accept a string as the third parameter, {$parameterType} given."
);
return false;
}

$parameterType = $parameterCount > 3 ? $this->getParameterTypeName($parameters[3]) : null;
if ($parameterType !== null && $parameterType !== 'array') {
Config::throwInvalidArgumentException(
"Validation rule closure must accept a array as the fourth parameter, {$parameterType} given."
);
}
return true;
}

/**
* Retrieves the parameter type with PHP 7.0 compatibility.
* Replaces the given rule at the same index position or prepends it if it doesn't exist.
*
* @since 1.0.0
* @since 1.3.0
*
* @return string|null
* @param string|ValidationRule|Closure $rule
*
* @return bool True if the rule was replaced, false if it was prepended.
*/
private function getParameterTypeName(ReflectionParameter $parameter)
public function replaceOrPrependRule(string $ruleId, $rule): bool
{
$type = $parameter->getType();
$replaced = $this->replaceRule($ruleId, $rule);

if ($type === null) {
return null;
}
if (!$replaced) {
$this->prependRule($rule);

// Check if the method exists for PHP 7.0 compatibility (it exits as of PHP 7.1)
if (method_exists($type, 'getName')) {
return $type->getName();
return false;
}

return (string)$type;
return true;
}

/**
* Takes a validation rule string and returns the corresponding rule instance.
* Replace a rule with the given id with the given rule at the same index position. Returns true if the rule was
* replaced, false otherwise.
*
* @since 1.0.0
* @since 1.3.0
*
* @param string|ValidationRule|Closure $rule
*/
private function getRuleFromString(string $rule): ValidationRule
public function replaceRule(string $ruleId, $rule): bool
{
list($ruleId, $ruleOptions) = array_pad(explode(':', $rule, 2), 2, null);

/**
* @var ValidationRule $ruleClass
*/
$ruleClass = $this->register->getRule($ruleId);
foreach ($this->rules as $index => $validationRule) {
if ($validationRule instanceof ValidationRule && $validationRule::id() === $ruleId) {
$this->rules[$index] = $this->sanitizeRule($rule);

if (!$ruleClass) {
Config::throwInvalidArgumentException(
sprintf(
'Validation rule with id %s has not been registered.',
$ruleId
)
);
return true;
}
}

return $ruleClass::fromString($ruleOptions);
return false;
}

/**
* Finds and returns the validation rule by id. Does not work for Closure rules.
*
* @since 1.0.0
*
* @return ValidationRule|null
*/
public function getRule(string $rule)
Expand Down Expand Up @@ -268,4 +238,130 @@ public function jsonSerialize()

return $rules;
}

/**
* Sanitizes a given rule by validating the rule and making sure it's safe to use.
*
* @since 1.3.0
*
* @param mixed $rule
*
* @return Closure|ValidationRule
*/
private function sanitizeRule($rule)
{
if ($rule instanceof Closure) {
$this->validateClosureRule($rule);

return $rule;
} elseif ($rule instanceof ValidationRule) {
return $rule;
} elseif (is_string($rule)) {
return $this->getRuleFromString($rule);
} else {
Config::throwInvalidArgumentException(
sprintf(
'Validation rule must be a string, instance of %s, or a closure',
ValidationRule::class
)
);
}
}

/**
* Validates that a closure rule has the proper parameters to be used as a validation rule.
*
* @since 1.0.0
*
* @return void
*/
private function validateClosureRule(Closure $closure)
{
try {
$reflection = new ReflectionFunction($closure);
} catch (ReflectionException $e) {
Config::throwInvalidArgumentException(
'Unable to validate closure parameters. Please ensure that the closure is valid.'
);
}

$parameters = $reflection->getParameters();
$parameterCount = count($parameters);

if ($parameterCount < 2 || $parameterCount > 4) {
Config::throwInvalidArgumentException(
"Validation rule closure must accept between 2 and 4 parameters, $parameterCount given."
);
}

$parameterType = $this->getParameterTypeName($parameters[1]);
if ($parameterType !== null && $parameterType !== 'Closure') {
Config::throwInvalidArgumentException(
"Validation rule closure must accept a Closure as the second parameter, {$parameterType} given."
);
}

$parameterType = $parameterCount > 2 ? $this->getParameterTypeName($parameters[2]) : null;
if ($parameterType !== null && $parameterType !== 'string') {
Config::throwInvalidArgumentException(
"Validation rule closure must accept a string as the third parameter, {$parameterType} given."
);
}

$parameterType = $parameterCount > 3 ? $this->getParameterTypeName($parameters[3]) : null;
if ($parameterType !== null && $parameterType !== 'array') {
Config::throwInvalidArgumentException(
"Validation rule closure must accept a array as the fourth parameter, {$parameterType} given."
);
}
}

/**
* Retrieves the parameter type with PHP 7.0 compatibility.
*
* @since 1.0.0
*
* @return string|null
*/
private function getParameterTypeName(ReflectionParameter $parameter)
{
$type = $parameter->getType();

if ($type === null) {
return null;
}

// Check if the method exists for PHP 7.0 compatibility (it exits as of PHP 7.1)
if (method_exists($type, 'getName')) {
return $type->getName();
}

return (string)$type;
}

/**
* Takes a validation rule string and returns the corresponding rule instance.
*
* @since 1.0.0
*/
private function getRuleFromString(string $rule): ValidationRule
{
[$ruleId, $ruleOptions] = array_pad(explode(':', $rule, 2), 2, null);

/**
* @var ValidationRule $ruleClass
*/
$ruleClass = $this->register->getRule($ruleId);

if (!$ruleClass) {
Config::throwInvalidArgumentException(
sprintf(
'Validation rule with id %s has not been registered.',
$ruleId
)
);
}

return $ruleClass::fromString($ruleOptions);
}
}
Loading

0 comments on commit 03aa1f4

Please sign in to comment.