feat(Validator): Add confirmed, required_with, and required_if rules
This commit is contained in:
@@ -42,6 +42,16 @@ class Validator
|
||||
*/
|
||||
public static string $lastFailed = '';
|
||||
|
||||
/**
|
||||
* Rules that need to know the current field name and the whole data set
|
||||
* (e.g. to look up sibling fields such as the `confirmed` companion).
|
||||
* For these rules, checkRule() injects $field and $haystack as extra
|
||||
* arguments before the user-provided ones.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private const DATA_AWARE_RULES = ['confirmed', 'required_with', 'required_if'];
|
||||
|
||||
/**
|
||||
* Validates a list of rules against the properties of an object.
|
||||
*
|
||||
@@ -54,8 +64,14 @@ class Validator
|
||||
{
|
||||
foreach ($rulesList as $target => $rules) {
|
||||
$rules = preg_split('/\|/', $rules);
|
||||
$value = $haystack->{$target};
|
||||
|
||||
if (in_array('nullable', $rules, true) && static::isEmpty($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($rules as $rule) {
|
||||
if (static::checkRule($haystack->{$target}, $rule)) {
|
||||
if (static::checkRule($value, $rule, $target, $haystack)) {
|
||||
continue;
|
||||
}
|
||||
static::$lastFailed = $target . '.' . $rule;
|
||||
@@ -93,18 +109,30 @@ class Validator
|
||||
/**
|
||||
* Checks if a rule is met.
|
||||
*
|
||||
* @param mixed $subject The value to verify.
|
||||
* @param string $rule The rule to test.
|
||||
* @param mixed $subject The value to verify.
|
||||
* @param string $rule The rule to test.
|
||||
* @param string|null $field Optional name of the field being validated.
|
||||
* Required by "data-aware" rules to locate sibling fields.
|
||||
* @param mixed $haystack Optional full data set (Neuron or array) the
|
||||
* subject belongs to. Also used by data-aware rules.
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception If the rule is not callable.
|
||||
*/
|
||||
public static function checkRule(mixed $subject, string $rule): bool
|
||||
public static function checkRule(mixed $subject, string $rule, ?string $field = null, mixed $haystack = null): bool
|
||||
{
|
||||
[$name, $rawArguments] = static::parseRule($rule);
|
||||
|
||||
$method = [static::class, $name];
|
||||
$arguments = array_merge([$subject], static::splitArguments($name, $rawArguments));
|
||||
$method = [static::class, $name];
|
||||
|
||||
if (in_array($name, self::DATA_AWARE_RULES, true)) {
|
||||
$arguments = array_merge(
|
||||
[$subject, $field, $haystack],
|
||||
static::splitArguments($name, $rawArguments)
|
||||
);
|
||||
} else {
|
||||
$arguments = array_merge([$subject], static::splitArguments($name, $rawArguments));
|
||||
}
|
||||
|
||||
if (is_callable($method)) {
|
||||
return call_user_func_array($method, $arguments);
|
||||
@@ -381,6 +409,100 @@ class Validator
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a field as allowed to be empty.
|
||||
*
|
||||
* As a rule it always passes; the real effect happens in validateList(),
|
||||
* which skips all the other rules for a field when "nullable" is present
|
||||
* and the value is empty (see isEmpty()).
|
||||
*
|
||||
* @param mixed $subject The value to check.
|
||||
*
|
||||
* @return bool Always true.
|
||||
*/
|
||||
public static function nullable(mixed $subject): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the field has a matching companion "<field>_confirmation".
|
||||
*
|
||||
* @param mixed $subject The value to check.
|
||||
* @param string|null $field Name of the field being validated.
|
||||
* @param mixed $haystack The full data set (Neuron or array).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function confirmed(mixed $subject, ?string $field = null, mixed $haystack = null): bool
|
||||
{
|
||||
if ($field === null || $haystack === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return static::readField($haystack, $field . '_confirmation') === $subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requires the field only when at least one of the listed fields is present.
|
||||
*
|
||||
* @param mixed $subject The value to check.
|
||||
* @param string $field Name of the field being validated (unused here).
|
||||
* @param mixed $haystack The full data set (Neuron or array).
|
||||
* @param string ...$others Names of the fields whose presence triggers the requirement.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function required_with(
|
||||
mixed $subject,
|
||||
?string $field = null,
|
||||
mixed $haystack = null,
|
||||
string ...$others
|
||||
): bool {
|
||||
if ($haystack === null || empty($others)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($others as $other) {
|
||||
if (!static::isEmpty(static::readField($haystack, $other))) {
|
||||
return isset($subject) && !empty($subject);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requires the field only when another field equals a given value.
|
||||
*
|
||||
* @param mixed $subject The value to check.
|
||||
* @param string $field Name of the field being validated (unused here).
|
||||
* @param mixed $haystack The full data set (Neuron or array).
|
||||
* @param string|null $other Name of the field to inspect.
|
||||
* @param mixed $value Expected value that triggers the requirement.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function required_if(
|
||||
mixed $subject,
|
||||
?string $field = null,
|
||||
mixed $haystack = null,
|
||||
?string $other = null,
|
||||
mixed $value = null
|
||||
): bool {
|
||||
if ($haystack === null || $other === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$otherValue = static::readField($haystack, $other);
|
||||
|
||||
if (is_scalar($otherValue) && (string) $otherValue === (string) $value) {
|
||||
return isset($subject) && !empty($subject);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value is a valid email address.
|
||||
*
|
||||
@@ -408,7 +530,7 @@ class Validator
|
||||
/**
|
||||
* Checks if the value is present in a list of allowed values.
|
||||
*
|
||||
* @param mixed $subject The value to check.
|
||||
* @param mixed $subject The value to check.
|
||||
* @param mixed ...$values A variable number of allowed values.
|
||||
*
|
||||
* @return bool
|
||||
@@ -417,4 +539,45 @@ class Validator
|
||||
{
|
||||
return in_array($subject, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a field from a data set that may be a Neuron object or an array.
|
||||
*
|
||||
* Returns null when the field is not defined. Neuron already yields null
|
||||
* for undefined properties, so no notice is raised for either source.
|
||||
*
|
||||
* @param mixed $haystack The data set (Neuron or array).
|
||||
* @param string $field The field name to read.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private static function readField(mixed $haystack, string $field): mixed
|
||||
{
|
||||
if (is_array($haystack)) {
|
||||
return $haystack[$field] ?? null;
|
||||
}
|
||||
|
||||
if (is_object($haystack)) {
|
||||
return $haystack->{$field};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a value counts as "empty" for optionality rules
|
||||
* (nullable / required_with / required_if).
|
||||
*
|
||||
* Matches the conventional definition: null, an empty string or an empty
|
||||
* array. Note that, unlike PHP's empty(), "0" and 0 are NOT treated as
|
||||
* empty so they cannot silently bypass a required-style rule.
|
||||
*
|
||||
* @param mixed $value The value to test.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function isEmpty(mixed $value): bool
|
||||
{
|
||||
return $value === null || $value === '' || $value === [];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user