diff --git a/src/ValidationRule.php b/src/ValidationRule.php index b0f3486..a1caf2c 100644 --- a/src/ValidationRule.php +++ b/src/ValidationRule.php @@ -24,10 +24,19 @@ public static function make(string $name, Closure $closure, array $args = []): s return static::$pool[$hash] ?? (static::$pool[$hash] = new static($name, $closure, $args)); } + public string $rule; + protected function __construct( public string $name, public Closure $closure, public array $args = [] ) { + $this->rule = $this->name; + } + + public function setRule(string $rule): static + { + $this->rule = $rule; + return $this; } } diff --git a/src/ValidationRuleset.php b/src/ValidationRuleset.php index ad1a6b6..c8ec9c3 100644 --- a/src/ValidationRuleset.php +++ b/src/ValidationRuleset.php @@ -3,6 +3,7 @@ namespace KK\Validation; use Closure; +use Hyperf\Validation\ValidationRuleParser; use InvalidArgumentException; use JetBrains\PhpStorm\Pure; use SplFileInfo; @@ -41,6 +42,7 @@ class ValidationRuleset protected const PRIORITY_MAP = [ 'required' => 50, + 'required_if' => 50, 'numeric' => 100, 'integer' => 100, 'string' => 100, @@ -111,6 +113,17 @@ protected function __construct(array $ruleMap) $rules[] = ValidationRule::make('required', static::getClosure('validateRequired' . static::fetchTypedRule($ruleMap))); } break; + case 'required_if': + if (count($ruleArgs) < 2) { + throw new InvalidArgumentException("Rule '{$rule}' must have at least 2 parameters"); + } + // 第一个参数是字段名,后续所有参数都是要匹配的值 + $fieldName = $ruleArgs[0]; + $matchingValues = array_slice($ruleArgs, 1); // 获取所有要匹配的值 + $name = "{$rule}:{$fieldName}," . implode(',', $matchingValues); + $closureArgs = [$fieldName, $matchingValues, 'validateRequired' . static::fetchTypedRule($ruleMap)]; + $rules[] = ValidationRule::make($name, static::getClosure('validateRequiredIf'), $closureArgs)->setRule($rule); + break; case 'nullable': $flags |= static::FLAG_NULLABLE; break; @@ -199,7 +212,7 @@ public function getRules(): array /** * @return string[] Error attribute names */ - public function check(mixed $data): array + public function check(mixed $data, array $attributes = [], ?string $ruleName = null): array { if (($this->flags & static::FLAG_NULLABLE) && $data === null) { return []; @@ -208,8 +221,12 @@ public function check(mixed $data): array $errors = []; foreach ($this->rules as $rule) { + if ($ruleName && $ruleName !== $rule->rule) { + continue; + } + $closure = $rule->closure; - $valid = $closure($data, ...$rule->args); + $valid = $closure($data, $attributes, ...$rule->args); if (!$valid) { $errors[] = $rule->name; /* Always bail here, for example: @@ -322,7 +339,7 @@ protected static function getClosure(string $method): Closure (static::$closureCache[$method] = Closure::fromCallable([static::class, $method])); } - protected static function validateRequired(mixed $value): bool + protected static function validateRequired(mixed $value, array $attributes): bool { if (is_null($value)) { return false; @@ -340,22 +357,39 @@ protected static function validateRequired(mixed $value): bool return true; } - protected static function validateRequiredString(mixed $value): bool + protected static function validateRequiredIf(mixed $value, array $attributes, string $key, array $keyValues, string $validator): bool + { + if (array_key_exists($key, $attributes)) { + // 检查字段值是否匹配任何一个指定的值 + foreach ($keyValues as $keyValue) { + if ($attributes[$key] == $keyValue) { + if ($value === null) { + return false; + } + return self::$validator($value, $attributes); + } + } + } + + return true; + } + + protected static function validateRequiredString(mixed $value, array $attributes): bool { return $value !== '' && !ctype_space($value); } - protected static function validateRequiredArray(array $value): bool + protected static function validateRequiredArray(array $value, array $attributes): bool { return count($value) !== 0; } - protected static function validateRequiredFile(SplFileInfo $value): bool + protected static function validateRequiredFile(SplFileInfo $value, array $attributes): bool { return $value->getPath() !== ''; } - protected static function validateNumeric(mixed &$value): bool + protected static function validateNumeric(mixed &$value, array $attributes): bool { if (!is_numeric($value)) { return false; @@ -364,7 +398,7 @@ protected static function validateNumeric(mixed &$value): bool return true; } - protected static function validateInteger(mixed &$value): bool + protected static function validateInteger(mixed &$value, array $attributes): bool { if (filter_var($value, FILTER_VALIDATE_INT) === false) { return false; @@ -373,17 +407,17 @@ protected static function validateInteger(mixed &$value): bool return true; } - protected static function validateString(mixed $value): bool + protected static function validateString(mixed $value, array $attributes): bool { return is_string($value); } - protected static function validateArray(mixed $value): bool + protected static function validateArray(mixed $value, array $attributes): bool { return is_array($value); } - protected static function getLength(mixed $value): int + protected static function getLength(mixed $value, array $attributes): int { if (is_numeric($value)) { return $value + 0; @@ -400,49 +434,60 @@ protected static function getLength(mixed $value): int return mb_strlen((string) $value); } - protected static function validateMin(mixed $value, int|float $min): bool + protected static function validateMin(mixed $value, array $attributes, int|float $min): bool { // TODO: file min support b, kb, mb, gb ... - return static::getLength($value) >= $min; + return static::getLength($value, $attributes) >= $min; } - protected static function validateMax(mixed $value, int|float $max): bool + protected static function validateMax(mixed $value, array $attributes, int|float $max): bool { // TODO: file max support b, kb, mb, gb ... - return static::getLength($value) <= $max; + return static::getLength($value, $attributes) <= $max; } - protected static function validateMinInteger(int $value, int|float $min): bool + protected static function validateMinInteger(int $value, array $attributes, int|float $min): bool { return $value >= $min; } - protected static function validateMaxInteger(int $value, int|float $max): bool + protected static function validateMaxInteger(int $value, array $attributes, int|float $max): bool { return $value <= $max; } - protected static function validateMinNumeric(int|float $value, int|float $min): bool + protected static function validateMinNumeric(int|float $value, array $attributes, int|float $min): bool { return $value >= $min; } - protected static function validateMaxNumeric(int|float $value, int|float $max): bool + protected static function validateMaxNumeric(int|float $value, array $attributes, int|float $max): bool { return $value <= $max; } - protected static function validateMinString(string $value, int|float $min): bool + protected static function validateMinString(string $value, array $attributes, int|float $min): bool { return mb_strlen($value) >= $min; } - protected static function validateMaxString(string $value, int|float $max): bool + protected static function validateMaxString(string $value, array $attributes, int|float $max): bool { return mb_strlen($value) <= $max; } - #[Pure] protected static function validateInList(mixed $value, array $list): bool + protected static function validateMinArray(array $value, array $attributes, int|float $min): bool + { + return count($value) >= $min; + } + + protected static function validateMaxArray(array $value, array $attributes, int|float $max): bool + { + return count($value) <= $max; + } + + #[Pure] + protected static function validateInList(mixed $value, array $attributes, array $list): bool { if (!is_array($value)) { return in_array((string) $value, $list, true); @@ -451,7 +496,7 @@ protected static function validateMaxString(string $value, int|float $max): bool } } - protected static function validateInListArray(array $value, array $list): bool + protected static function validateInListArray(array $value, array $attributes, array $list): bool { foreach ($value as $item) { if (is_array($item)) { @@ -464,7 +509,8 @@ protected static function validateInListArray(array $value, array $list): bool return true; } - #[Pure] protected static function validateInMap(mixed $value, array $map): bool + #[Pure] + protected static function validateInMap(mixed $value, array $attributes, array $map): bool { if (!is_array($value)) { return $map[(string) $value] ?? false; @@ -473,7 +519,7 @@ protected static function validateInListArray(array $value, array $list): bool } } - protected static function validateInMapArray(array $value, array $map): bool + protected static function validateInMapArray(array $value, array $attributes, array $map): bool { foreach ($value as $item) { if (is_array($item)) { @@ -486,32 +532,32 @@ protected static function validateInMapArray(array $value, array $map): bool return true; } - public static function validateAlpha(string $value): bool + public static function validateAlpha(string $value, array $attributes): bool { return preg_match('/^[\pL\pM]+$/u', $value); } - public static function validateAlphaNum(string $value): bool + public static function validateAlphaNum(string $value, array $attributes): bool { return preg_match('/^[\pL\pM\pN]+$/u', $value) > 0; } - public static function validateAlphaDash(string $value): bool + public static function validateAlphaDash(string $value, array $attributes): bool { return preg_match('/^[\pL\pM\pN_-]+$/u', $value) > 0; } - public static function validateIP(string $value): bool + public static function validateIP(string $value, array $attributes): bool { return filter_var($value, FILTER_VALIDATE_IP) !== false; } - public static function validateIPV4(string $value): bool + public static function validateIPV4(string $value, array $attributes): bool { return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false; } - public static function validateIPV6(string $value): bool + public static function validateIPV6(string $value, array $attributes): bool { return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false; } diff --git a/src/Validator.php b/src/Validator.php index 0d37d35..21b109c 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -112,10 +112,16 @@ protected function validRecursive(array $data, array $validationPairs, array $cu $this->recordError($currentPatternPart, 'required'); $invalid = true; } + + if($errors = $ruleset->check(null, $data, 'required_if')){ + $this->recordErrors($currentPatternPart, $errors); + $invalid = true; + } + continue; } $value = $data[$currentPatternPart]; - $errors = $ruleset->check($value); + $errors = $ruleset->check($value, $data); if ($errors) { $this->recordErrors($currentPatternPart, $errors); $invalid = true; diff --git a/tests/HyperfValidatorTest.php b/tests/HyperfValidatorTest.php index de60eac..fe2d57b 100644 --- a/tests/HyperfValidatorTest.php +++ b/tests/HyperfValidatorTest.php @@ -44,6 +44,51 @@ public function testFails() $this->assertFalse($validator->fails()); } + public function testRequiredIf() + { + $validator = $this->makeValidator(['type' => 1], ['id' => 'required_if:type,1', 'type' => 'required']); + $this->assertTrue($validator->fails()); + } + + public function testMax() + { + $validator = $this->makeValidator(['id' => 256], ['id' => 'max:255']); + $this->assertTrue($validator->fails()); + } + + public function testMaxArray() + { + $validator = $this->makeValidator(['id' => [1,2,3]], ['id' => 'max:2']); + $this->assertTrue($validator->fails()); + + $validator = $this->makeValidator(['id' => [1,2,3]], ['id' => 'max:3']); + $this->assertFalse($validator->fails()); + } + + public function testRequiredIfArray() + { + $validator = $this->makeValidator(['id' => 3, 'type' => 1], ['id' => 'required_if:type,1,2,3', 'type' => 'required']); + $this->assertFalse($validator->fails()); + + $validator = $this->makeValidator(['type' => 2], ['id' => 'required_if:type,1,2,3']); + $this->assertTrue($validator->fails()); + } + + public function testRequiredIfArray2() + { + $validator = $this->makeValidator(['type' => 1], ['id' => 'array|required_if:type,1,2,3']); + $this->assertTrue($validator->fails()); + + $validator = $this->makeValidator(['type' => 1, 'id' => ['1']], ['id' => 'array|required_if:type,1,2,3']); + $this->assertFalse($validator->fails()); + + $validator = $this->makeValidator(['type' => 1, 'id' => '1'], ['id' => 'array|required_if:type,1,2,3']); + $this->assertTrue($validator->fails()); + + $validator = $this->makeValidator(['type' => 5], ['id' => 'array|required_if:type,1,2,3']); + $this->assertFalse($validator->fails()); + } + public function testGetMessageBag() { $data = [['id' => 256], ['id' => 'required|integer|max:255']];