diff --git a/composer.json b/composer.json index ba2604a..e216061 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "vecode/caldera-validation", "description": "Validation layer, part of Vecode Caldera", - "version": "1.0", + "version": "1.1", "type": "library", "license": "MIT", "authors": [ diff --git a/src/Validation/Condition.php b/src/Validation/Condition.php index bdb353d..664023e 100644 --- a/src/Validation/Condition.php +++ b/src/Validation/Condition.php @@ -22,21 +22,31 @@ class Condition { use ValidatesData; + /** + * Validation instance + */ + protected Validation $validation; + /** * Rules array - * @var array */ - protected $rules = []; + protected array $rules = []; /** * Error bag - * @var array */ - protected $errors = []; + protected array $errors = []; + + /** + * Constructor + * @param Validation $validation Validation instance + */ + public function __construct(Validation $validation) { + $this->validation = $validation; + } /** * Get errors - * @return array */ public function getErrors(): array { return $this->errors; @@ -47,7 +57,7 @@ public function getErrors(): array { * @param mixed $rule Rule to add * @return $this */ - public function rule($rule) { + public function rule(mixed $rule) { if ( is_array($rule) ) { foreach ($rule as $item) { $this->rule($item); @@ -85,7 +95,6 @@ public function rule($rule) { * @param array $fields Fields array * @param string $key Field key * @param bool $bail Bail flag - * @return bool */ public function check(array $fields, string $key, bool $bail = false): bool { $passed = true; @@ -112,7 +121,23 @@ public function check(array $fields, string $key, bool $bail = false): bool { if ( method_exists($this, $method) ) { $callable = [$this, $method]; } else { - throw new RuntimeException('Unknown rule type'); + if ( $this->validation->hasRule($type) ) { + $handler = $this->validation->getRule($type); + if ($handler instanceof Closure) { + $callable = $handler; + } else if ( class_exists($handler) ) { + $instance = new $handler(); + if ($instance instanceof RuleInterface) { + $callable = $instance; + } else { + throw new RuntimeException('Must implement RuleInterface'); + } + } else { + throw new RuntimeException("Unknown rule type: {$type}"); + } + } else { + throw new RuntimeException("Unknown rule type: {$type}"); + } } } } diff --git a/src/Validation/RuleInterface.php b/src/Validation/RuleInterface.php index cc8a356..e86e80b 100644 --- a/src/Validation/RuleInterface.php +++ b/src/Validation/RuleInterface.php @@ -21,7 +21,6 @@ interface RuleInterface { * @param string $key Field key * @param array $options Options array * @param Closure $fail Failure callback - * @return void */ public function __invoke(array $fields, string $key, array $options, Closure $fail): void; } diff --git a/src/Validation/ValidatesData.php b/src/Validation/ValidatesData.php index 8347be3..68dbeba 100644 --- a/src/Validation/ValidatesData.php +++ b/src/Validation/ValidatesData.php @@ -21,7 +21,6 @@ trait ValidatesData { * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateRequired(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -37,7 +36,6 @@ public function validateRequired(array $fields, string $key, array $options, Clo * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateAlpha(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -54,7 +52,6 @@ public function validateAlpha(array $fields, string $key, array $options, Closur * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateAlphanum(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -71,7 +68,6 @@ public function validateAlphanum(array $fields, string $key, array $options, Clo * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateNum(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -88,7 +84,6 @@ public function validateNum(array $fields, string $key, array $options, Closure * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateSlug(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -105,7 +100,6 @@ public function validateSlug(array $fields, string $key, array $options, Closure * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateRegex(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -122,7 +116,6 @@ public function validateRegex(array $fields, string $key, array $options, Closur * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateEmail(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -139,7 +132,6 @@ public function validateEmail(array $fields, string $key, array $options, Closur * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateSame(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -157,7 +149,6 @@ public function validateSame(array $fields, string $key, array $options, Closure * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateDifferent(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -175,7 +166,6 @@ public function validateDifferent(array $fields, string $key, array $options, Cl * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateAfter(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -193,7 +183,6 @@ public function validateAfter(array $fields, string $key, array $options, Closur * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateBefore(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -211,7 +200,6 @@ public function validateBefore(array $fields, string $key, array $options, Closu * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateBetween(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -239,7 +227,6 @@ public function validateBetween(array $fields, string $key, array $options, Clos * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateMin(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -266,7 +253,6 @@ public function validateMin(array $fields, string $key, array $options, Closure * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateMax(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -293,7 +279,6 @@ public function validateMax(array $fields, string $key, array $options, Closure * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateSize(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -320,7 +305,6 @@ public function validateSize(array $fields, string $key, array $options, Closure * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateArray(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -336,7 +320,6 @@ public function validateArray(array $fields, string $key, array $options, Closur * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateNumeric(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -352,7 +335,6 @@ public function validateNumeric(array $fields, string $key, array $options, Clos * @param string $key Field name * @param array $options Rule options * @param Closure $fail Failure callback - * @return void */ public function validateString(array $fields, string $key, array $options, Closure $fail): void { $value = $fields[$key] ?? null; @@ -361,4 +343,4 @@ public function validateString(array $fields, string $key, array $options, Closu $fail('validation.string'); } } -} \ No newline at end of file +} diff --git a/src/Validation/Validation.php b/src/Validation/Validation.php index 9a8342a..2d4ca18 100644 --- a/src/Validation/Validation.php +++ b/src/Validation/Validation.php @@ -20,33 +20,59 @@ class Validation { /** * Conditions array - * @var array */ - protected $conditions = []; + protected array $conditions = []; + + /** + * Rules array + */ + protected array $rules = []; /** * Error bag - * @var array */ - protected $errors = []; + protected array $errors = []; /** * Get conditions array - * @return array */ public function getConditions(): array { return $this->conditions; } + /** + * Get rules array + */ + public function getRules(): array { + return $this->rules; + } + + /** + * Check if rule exists + */ + public function hasRule(string $name): bool { + return isset( $this->rules[$name] ); + } + + /** + * Get rule handler + */ + public function getRule(string $name): mixed { + if (! isset( $this->rules[$name] ) ) { + throw new InvalidArgumentException("Rule {$name} does not exist"); + } + return $this->rules[$name]; + } + /** * Add condition * @param string $field Field name * @param mixed $rules Condition rules * @return $this */ - public function condition(string $field, $rules) { + public function condition(string $field, mixed $rules) { if (! isset( $this->conditions[$field] ) ) { - $this->conditions[$field] = new Condition(); + $this->conditions[$field] = new Condition($this); } if ( is_array($rules) || is_string($rules) || $rules instanceof Closure ) { $this->conditions[$field]->rule($rules); @@ -56,11 +82,25 @@ public function condition(string $field, $rules) { return $this; } + /** + * Add rule + * @param string $name Rule name + * @param mixed $handler Rule handler + * @return $this + */ + public function rule(string $name, mixed $handler) { + if ( is_string($handler) || $handler instanceof Closure ) { + $this->rules[$name] = $handler; + } else { + throw new InvalidArgumentException('Invalid handler type specified'); + } + return $this; + } + /** * Validate fields * @param array $fields Fields array * @param bool $bail Bail flag - * @return bool */ public function validate(array $fields, bool $bail = false): bool { $this->errors = []; diff --git a/src/Validation/ValidationException.php b/src/Validation/ValidationException.php index f537b47..4dbb633 100644 --- a/src/Validation/ValidationException.php +++ b/src/Validation/ValidationException.php @@ -18,9 +18,8 @@ class ValidationException extends RuntimeException { /** * Error bag - * @var array */ - protected $errors; + protected array $errors; /** * Constructor @@ -33,7 +32,6 @@ public function __construct(array $errors, string $message = '', int $code = 0, /** * Get errors instance - * @return array */ public function getErrors(): array { return $this->errors; diff --git a/tests/Validation/ValidationTest.php b/tests/Validation/ValidationTest.php index de5b73c..e9e7e22 100644 --- a/tests/Validation/ValidationTest.php +++ b/tests/Validation/ValidationTest.php @@ -32,7 +32,8 @@ public function testInvalidArgumentOnValidation() { } public function testInvalidArgumentOnCondition() { - $condition = new Condition(); + $validation = new Validation(); + $condition = new Condition($validation); $this->expectException(InvalidArgumentException::class); $condition->rule(5); } @@ -51,6 +52,12 @@ public function testAddConditions() { $this->assertContainsOnlyInstancesOf(Condition::class, $conditions); } + public function testGetInvalidCustomRule() { + $validation = new Validation(); + $this->expectException(InvalidArgumentException::class); + $rule = $validation->getRule('foo'); + } + public function testAddClosureCondition() { $validation = new Validation(); $validation->condition('name', function() {}); @@ -68,7 +75,27 @@ public function testAddCustomRuleCondition() { public function testAddInvalidCustomRuleCondition() { $validation = new Validation(); $this->expectException(RuntimeException::class); - $validation->condition('name', Condition::class)->validate([]); + $validation->condition('name', RuntimeException::class)->validate([]); + } + + public function testAddCustomRuleClosure() { + $validation = new Validation(); + $validation->rule('url', function() {}); + $rules = $validation->getRules(); + $this->assertArrayHasKey('url', $rules); + } + + public function testAddCustomRuleClass() { + $validation = new Validation(); + $validation->rule('url', CustomRule::class); + $rules = $validation->getRules(); + $this->assertArrayHasKey('url', $rules); + } + + public function testGetInvalidCustomRuleInvalidType() { + $validation = new Validation(); + $this->expectException(InvalidArgumentException::class); + $validation->rule('url', 42); } public function testValidateBail() { @@ -383,13 +410,47 @@ public function testValidateCustomRule() { $this->expectException(ValidationException::class); $validation->validate(['test' => 'Lorem ipsum']); } + + public function testValidateCustomRuleGlobalClosure() { + $validation = new Validation(); + $validation->rule('url', function(array $fields, string $key, array $options, Closure $fail) { + $https = $options[1] ?? ''; + $value = $fields[$key] ?? ''; + $valid = $value && filter_var($value, FILTER_VALIDATE_URL); + if ( $https && ! str_starts_with($value, 'https://') ) { + $valid = false; + } + if ( !$valid ) { + $fail('Invalid Website specified'); + } + }); + $validation->condition('test', 'url:https'); + $validation->validate(['test' => 'https://vecode.net']); + $validation->validate(['test' => 'https://caldera.vecode.net']); + $this->expectException(ValidationException::class); + $validation->validate(['test' => 'http://localhost:8080']); + } + + public function testValidateCustomRuleGlobalClass() { + $validation = new Validation(); + $validation->rule('url', CustomRule::class); + $validation->condition('test', 'url:https'); + $validation->validate(['test' => 'https://vecode.net']); + $validation->validate(['test' => 'https://caldera.vecode.net']); + $this->expectException(ValidationException::class); + $validation->validate(['test' => 'http://localhost:8080']); + } } class CustomRule implements RuleInterface { public function __invoke(array $fields, string $key, array $options, Closure $fail): void { + $https = $options[1] ?? ''; $value = $fields[$key] ?? ''; - $valid = $value && filter_var($value, FILTER_VALIDATE_URL);; + $valid = $value && filter_var($value, FILTER_VALIDATE_URL); + if ( $https && ! str_starts_with($value, 'https://') ) { + $valid = false; + } if ( !$valid ) { $fail('Invalid Website specified'); }