diff --git a/README.md b/README.md index 81329da..30b538a 100644 --- a/README.md +++ b/README.md @@ -125,11 +125,36 @@ V::lang('ar'); ``` +You can conditionally require values using required conditional rules. In this example, for authentication, we're requiring either a token when both the email and password are not present, or a password when the email address is present. +```php +// this rule set would work for either data set... +$data = ['email' => 'test@test.com', 'password' => 'mypassword']; +// or... +$data = ['token' => 'jashdjahs83rufh89y38h38h']; +$v = new Valitron\Validator($data); +$v->rules([ + 'requiredWithout' => [ + ['token', ['email', 'password'], true] + ], + 'requiredWith' => [ + ['password', ['email']] + ], + 'email' => [ + ['email'] + ] + 'optional' => [ + ['email'] + ] +]); +$this->assertTrue($v->validate()); +``` ## Built-in Validation Rules * `required` - Field is required + * `requiredWith` - Field is required if any other fields are present + * `requiredWithout` - Field is required if any other fields are NOT present * `equals` - Field must match another field (email/password confirmation) * `different` - Field must be different than another field * `accepted` - Checkbox or Radio must be accepted (yes, on, 1, true) @@ -200,7 +225,133 @@ $v->rules([ $v->validate(); ``` -Example using alternate syntax. +## requiredWith fields usage +The `requiredWith` rule checks that the field is required, not null, and not the empty string, if any other fields are present, not null, and not the empty string. +```php +// password field will be required when the username field is provided and not empty +$v->rule('requiredWith', 'password', 'username'); +``` + +Alternate syntax. +```php +$v = new Valitron\Validator(['username' => 'spiderman', 'password' => 'Gr33nG0Blin']); +$v->rules([ + 'requiredWith' => [ + ['password', 'username'] + ] +]); +$v->validate(); +``` + +*Note* You can provide multiple values as an array. In this case if ANY of the fields are present the field will be required. +```php +// in this case the password field will be required if the username or email fields are present +$v->rule('requiredWith', 'password', ['username', 'email']); +``` + +Alternate syntax. +```php +$v = new Valitron\Validator(['username' => 'spiderman', 'password' => 'Gr33nG0Blin']); +$v->rules([ + 'requiredWith' => [ + ['password', ['username', 'email']] + ] +]); +$v->validate(); +``` + +### Strict flag +The strict flag will change the `requiredWith` rule to `requiredWithAll` which will require the field only if ALL of the other fields are present, not null, and not the empty string. +```php +// in this example the suffix field is required only when both the first_name and last_name are provided +$v->rule('requiredWith', 'suffix', ['first_name', 'last_name'], true); +``` +Alternate syntax. +```php +$v = new Valitron\Validator(['first_name' => 'steve', 'last_name' => 'holt', 'suffix' => 'Mr']); +$v->rules([ + 'requiredWith' => [ + ['suffix', ['first_name', 'last_name'], true] + ] +]); +$v->validate(); +``` + +Likewise, in this case `validate()` would still return true, as the suffix field would not be required in strict mode, as not all of the fields are provided. +```php +$v = new Valitron\Validator(['first_name' => 'steve']); +$v->rules([ + 'requiredWith' => [ + ['suffix', ['first_name', 'last_name'], true] + ] +]); +$v->validate(); +``` + +## requiredWithout fields usage +The `requiredWithout` rule checks that the field is required, not null, and not the empty string, if any other fields are NOT present. +```php +// this rule will require the username field when the first_name is not present +$v->rule('requiredWithout', 'username', 'first_name') +``` + +Alternate syntax. +```php +// this will return true, as the username is provided when the first_name is not provided +$v = new Valitron\Validator(['username' => 'spiderman']); +$v->rules([ + 'requiredWithout' => [ + ['username', 'first_name'] + ] +]); +$v->validate(); +``` + +*Note* You can provide multiple values as an array. In this case if ANY of the fields are NOT present the field will be required. +```php +// in this case the username field will be required if either the first_name or last_name fields are not present +$v->rule('requiredWithout', 'username', ['first_name', 'last_name']); +``` + +Alternate syntax. +```php +// this passes validation because although the last_name field is not present, the username is provided +$v = new Valitron\Validator(['username' => 'spiderman', 'first_name' => 'Peter']); +$v->rules([ + 'requiredWithout' => [ + ['username', ['first_name', 'last_name']] + ] +]); +$v->validate(); +``` + +### Strict flag +The strict flag will change the `requiredWithout` rule to `requiredWithoutAll` which will require the field only if ALL of the other fields are not present. +```php +// in this example the username field is required only when both the first_name and last_name are not provided +$v->rule('requiredWithout', 'username', ['first_name', 'last_name'], true); +``` +Alternate syntax. +```php +$v = new Valitron\Validator(['username' => 'BatMan']); +$v->rules([ + 'requiredWithout' => [ + ['username', ['first_name', 'last_name'], true] + ] +]); +$v->validate(); +``` + +Likewise, in this case `validate()` would still return true, as the username field would not be required in strict mode, as all of the fields are provided. +```php +$v = new Valitron\Validator(['first_name' => 'steve', 'last_name' => 'holt']); +$v->rules([ + 'requiredWithout' => [ + ['suffix', ['first_name', 'last_name'], true] + ] +]); +$v->validate(); +``` ## equals fields usage The `equals` rule checks if two fields are equals in the data array, and that the second field is not null. diff --git a/lang/en.php b/lang/en.php index 43bc6b7..8e66cb5 100644 --- a/lang/en.php +++ b/lang/en.php @@ -35,6 +35,8 @@ 'lengthMax' => "must not exceed %d characters", 'instanceOf' => "must be an instance of '%s'", 'containsUnique' => "must contain unique elements only", + 'requiredWith' => "is required", + 'requiredWithout'=> "is required", 'subset' => "contains an item that is not in the list", 'arrayHasKeys' => "does not contain all required keys", ); diff --git a/src/Valitron/Validator.php b/src/Valitron/Validator.php index e19fd6a..b4886a7 100644 --- a/src/Valitron/Validator.php +++ b/src/Valitron/Validator.php @@ -920,6 +920,94 @@ protected function validateInstanceOf($field, $value, $params) return $isInstanceOf; } + /** + * Validates whether or not a field is required based on whether or not other fields are present. + * + * @param string $field name of the field in the data array + * @param mixed $value value of this field + * @param array $params parameters for this rule + * @param array $fields full list of data to be validated + * @return bool + */ + protected function validateRequiredWith($field, $value, $params, $fields) + { + $conditionallyReq = false; + // if we actually have conditionally required with fields to check against + if (isset($params[0])) { + // convert single value to array if it isn't already + $reqParams = is_array($params[0]) ? $params[0] : array($params[0]); + // check for the flag indicating if all fields are required + $allRequired = isset($params[1]) && (bool)$params[1]; + $emptyFields = 0; + foreach ($reqParams as $requiredField) { + // check the field is set, not null, and not the empty string + if (isset($fields[$requiredField]) && !is_null($fields[$requiredField]) + && (is_string($fields[$requiredField]) ? trim($fields[$requiredField]) !== '' : true)) { + if (!$allRequired) { + $conditionallyReq = true; + break; + } else { + $emptyFields++; + } + } + } + // if all required fields are present in strict mode, we're requiring it + if ($allRequired && $emptyFields === count($reqParams)) { + $conditionallyReq = true; + } + } + // if we have conditionally required fields + if ($conditionallyReq && (is_null($value) || + is_string($value) && trim($value) === '')) { + return false; + } + return true; + } + + /** + * Validates whether or not a field is required based on whether or not other fields are present. + * + * @param string $field name of the field in the data array + * @param mixed $value value of this field + * @param array $params parameters for this rule + * @param array $fields full list of data to be validated + * @return bool + */ + protected function validateRequiredWithout($field, $value, $params, $fields) + { + $conditionallyReq = false; + // if we actually have conditionally required with fields to check against + if (isset($params[0])) { + // convert single value to array if it isn't already + $reqParams = is_array($params[0]) ? $params[0] : array($params[0]); + // check for the flag indicating if all fields are required + $allEmpty = isset($params[1]) && (bool)$params[1]; + $filledFields = 0; + foreach ($reqParams as $requiredField) { + // check the field is NOT set, null, or the empty string, in which case we are requiring this value be present + if (!isset($fields[$requiredField]) || (is_null($fields[$requiredField]) + || (is_string($fields[$requiredField]) && trim($fields[$requiredField]) === ''))) { + if (!$allEmpty) { + $conditionallyReq = true; + break; + } else { + $filledFields++; + } + } + } + // if all fields were empty, then we're requiring this in strict mode + if ($allEmpty && $filledFields === count($reqParams)) { + $conditionallyReq = true; + } + } + // if we have conditionally required fields + if ($conditionallyReq && (is_null($value) || + is_string($value) && trim($value) === '')) { + return false; + } + return true; + } + /** * Validate optional field * @@ -1091,8 +1179,9 @@ public function validate() foreach ($v['fields'] as $field) { list($values, $multiple) = $this->getPart($this->_fields, explode('.', $field), false); - // Don't validate if the field is not required and the value is empty - if ($this->hasRule('optional', $field) && isset($values)) { + // Don't validate if the field is not required and the value is empty and we don't have a conditionally required rule present on the field + if (($this->hasRule('optional', $field) && isset($values)) + || ($this->hasRule('requiredWith', $field) || $this->hasRule('requiredWithout', $field))) { //Continue with execution below if statement } elseif ( $v['rule'] !== 'required' && !$this->hasRule('required', $field) && diff --git a/tests/Valitron/ValidateTest.php b/tests/Valitron/ValidateTest.php index 292a599..d6e6dd3 100644 --- a/tests/Valitron/ValidateTest.php +++ b/tests/Valitron/ValidateTest.php @@ -2258,6 +2258,338 @@ public function testInstanceOfWithAlternativeSyntaxInvalid() $this->assertFalse($v->validate()); } + public function testRequiredWithValid() + { + $v = new Validator(array('username' => 'tester', 'password' => 'mypassword')); + $v->rule('requiredWith', 'password', 'username'); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithValidNoParams() + { + $v = new Validator(array()); + $v->rule('requiredWith', 'password', 'username'); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithValidEmptyString() + { + $v = new Validator(array('username' => '')); + $v->rule('requiredWith', 'password', 'username'); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithValidNullValue() + { + $v = new Validator(array('username' => null)); + $v->rule('requiredWith', 'password', 'username'); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithValidAltSyntax() + { + $v = new Validator(array('username' => 'tester', 'password' => 'mypassword')); + $v->rules(array( + 'requiredWith' => array( + array('password', 'username') + ) + )); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithValidArray() + { + $v = new Validator(array('username' => 'tester', 'email' => 'test@test.com', 'password' => 'mypassword')); + $v->rule('requiredWith', 'password', array('username', 'email')); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithStrictValidArray() + { + $v = new Validator(array('username' => 'tester', 'email' => 'test@test.com', 'password' => 'mypassword')); + $v->rule('requiredWith', 'password', array('username', 'email'), true); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithStrictInvalidArray() + { + $v = new Validator(array('email' => 'test@test.com', 'username' => 'batman')); + $v->rule('requiredWith', 'password', array('username', 'email'), true); + $this->assertFalse($v->validate()); + } + + public function testRequiredWithStrictValidArrayNotRequired() + { + $v = new Validator(array('username' => 'tester', 'email' => 'test@test.com')); + $v->rule('requiredWith', 'password', array('username', 'email', 'nickname'), true); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithStrictValidArrayEmptyValues() + { + $v = new Validator(array('email' => '', 'username' => null)); + $v->rule('requiredWith', 'password', array('username', 'email'), true); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithStrictInvalidArraySingleValue() + { + $v = new Validator(array('email' => 'tester', 'username' => null)); + $v->rule('requiredWith', 'password', array('username', 'email'), true); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithValidArrayAltSyntax() + { + $v = new Validator(array('password' => 'mypassword')); + $v->rules(array( + 'requiredWith' => array( + array('password', array('username', 'email')) + ) + )); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithInvalid() + { + $v = new Validator(array('username' => 'tester')); + $v->rule('requiredWith', 'password', 'username'); + $this->assertFalse($v->validate()); + } + + public function testRequiredWithInvalidAltSyntax() + { + $v = new Validator(array('username' => 'tester')); + $v->rules(array( + 'requiredWith' => array( + array('password', 'username') + ) + )); + $this->assertFalse($v->validate()); + } + + public function testRequiredWithInvalidArray() + { + $v = new Validator(array('email' => 'test@test.com', 'nickname' => 'kevin')); + $v->rule('requiredWith', 'password', array('username', 'email', 'nickname')); + $this->assertFalse($v->validate()); + } + + public function testRequiredWithInvalidStrictArray() + { + $v = new Validator(array('email' => 'test@test.com', 'username' => 'batman', 'nickname' => 'james')); + $v->rule('requiredWith', 'password', array('username', 'email', 'nickname'), true); + $this->assertFalse($v->validate()); + } + + public function testRequiredWithInvalidArrayAltSyntax() + { + $v = new Validator(array('username' => 'tester', 'email' => 'test@test.com')); + $v->rules(array( + 'requiredWith' => array( + array('password', array('username', 'email', 'nickname')) + ) + )); + $this->assertFalse($v->validate()); + } + + public function testRequiredWithStrictInvalidArrayAltSyntax() + { + $v = new Validator(array('username' => 'tester', 'email' => 'test@test.com', 'nickname' => 'joseph')); + $v->rules(array( + 'requiredWith' => array( + array('password', array('username', 'email', 'nickname'), true) + ) + )); + $this->assertFalse($v->validate()); + } + + // required without tests + + public function testRequiredWithoutValid() + { + $v = new Validator(array('password' => 'mypassword')); + $v->rule('requiredWithout', 'password', 'username'); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithoutInvalidNotPresent() + { + $v = new Validator(array()); + $v->rule('requiredWithout', 'password', 'username'); + $this->assertFalse($v->validate()); + } + + public function testRequiredWithoutValidEmptyString() + { + $v = new Validator(array('username' => '', 'password' => 'mypassword')); + $v->rule('requiredWithout', 'password', 'username'); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithoutInvalidEmptyStringNotPresent() + { + $v = new Validator(array('username' => '')); + $v->rule('requiredWithout', 'password', 'username'); + $this->assertFalse($v->validate()); + } + + public function testRequiredWithoutValidNullValue() + { + $v = new Validator(array('username' => null, 'password' => 'mypassword')); + $v->rule('requiredWithout', 'password', 'username'); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithoutInvlidNullValueNotPresent() + { + $v = new Validator(array('username' => null)); + $v->rule('requiredWithout', 'password', 'username'); + $this->assertFalse($v->validate()); + } + + public function testRequiredWithoutValidAltSyntax() + { + $v = new Validator(array('password' => 'mypassword')); + $v->rules(array( + 'requiredWithout' => array( + array('password', 'username') + ) + )); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithoutInvalidAltSyntaxNotPresent() + { + $v = new Validator(array()); + $v->rules(array( + 'requiredWithout' => array( + array('password', 'username') + ) + )); + $this->assertFalse($v->validate()); + } + + public function testRequiredWithoutValidArray() + { + $v = new Validator(array('password' => 'mypassword')); + $v->rule('requiredWithout', 'password', array('username', 'email')); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithoutInvalidArrayNotPresent() + { + $v = new Validator(array()); + $v->rule('requiredWithout', 'password', array('username', 'email')); + $this->assertFalse($v->validate()); + } + + public function testRequiredWithoutValidArrayPartial() + { + $v = new Validator(array('password' => 'mypassword', 'email' => 'test@test.com')); + $v->rule('requiredWithout', 'password', array('username', 'email')); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithoutInvalidArrayPartial() + { + $v = new Validator(array('email' => 'test@test.com')); + $v->rule('requiredWithout', 'password', array('username', 'email')); + $this->assertFalse($v->validate()); + } + + public function testRequiredWithoutValidArrayStrict() + { + $v = new Validator(array('email' => 'test@test.com')); + $v->rule('requiredWithout', 'password', array('username', 'email'), true); + $this->assertTrue($v->validate()); + } + + public function testRequiredWithoutInvalidArrayStrict() + { + $v = new Validator(array()); + $v->rule('requiredWithout', 'password', array('username', 'email'), true); + $this->assertFalse($v->validate()); + } + + public function testRequiredWithoutInvalidArrayNotProvided() + { + $v = new Validator(array('email' => 'test@test.com')); + $v->rule('requiredWithout', 'password', array('username', 'email')); + $this->assertFalse($v->validate()); + } + + public function testRequiredWithoutValidArrayAltSyntax() + { + $v = new Validator(array('password' => 'mypassword')); + $v->rules(array( + 'requiredWithout' => array( + array('password', array('username', 'email')) + ) + )); + $this->assertTrue($v->validate()); + } + + public function testConditionallyRequiredAuthSampleToken() + { + $v = new Validator(array('token' => 'ajkdhieyf2834fsuhf8934y89')); + $v->rule('requiredWithout', 'token', array('email', 'password')); + $v->rule('requiredWith', 'password', 'email'); + $v->rule('email', 'email'); + $v->rule('optional', 'email'); + $this->assertTrue($v->validate()); + } + + public function testConditionallyRequiredAuthSampleMissingPassword() + { + $v = new Validator(array('email' => 'test@test.com')); + $v->rule('requiredWithout', 'token', array('email', 'password')); + $v->rule('requiredWith', 'password', 'email'); + $v->rule('email', 'email'); + $v->rule('optional', 'email'); + $this->assertFalse($v->validate()); + } + + public function testConditionallyRequiredAuthSampleTokenAltSyntax() + { + $v = new Validator(array('token' => 'ajkdhieyf2834fsuhf8934y89')); + $v->rules(array( + 'requiredWithout' => array( + array('token', array('email', 'password')) + ), + 'requiredWith' => array( + array('password', array('email')) + ), + 'email' => array( + array('email') + ), + 'optional' => array( + array('email') + ) + )); + $this->assertTrue($v->validate()); + } + + public function testConditionallyRequiredAuthSampleEmailPasswordAltSyntax() + { + $v = new Validator(array('email' => 'test@test.com', 'password' => 'mypassword')); + $v->rules(array( + 'requiredWithout' => array( + array('token', array('email', 'password')) + ), + 'requiredWith' => array( + array('password', array('email')) + ), + 'email' => array( + array('email') + ), + 'optional' => array( + array('email') + ) + )); + $this->assertTrue($v->validate()); + } + /** * @dataProvider dataProviderFor_testError */