filter out non-required array parts. Fixes #262

This commit is contained in:
Willem Wollebrants 2018-08-22 12:54:14 +02:00
parent c5a1664dab
commit aaf543517d
2 changed files with 284 additions and 228 deletions

View File

@ -1,4 +1,5 @@
<?php <?php
namespace Valitron; namespace Valitron;
/** /**
@ -105,7 +106,7 @@ class Validator
// Load language file in directory // Load language file in directory
$langFile = rtrim($langDir, '/') . '/' . $lang . '.php'; $langFile = rtrim($langDir, '/') . '/' . $lang . '.php';
if (stream_resolve_include_path($langFile) ) { if (stream_resolve_include_path($langFile)) {
$langMessages = include $langFile; $langMessages = include $langFile;
static::$_ruleMessages = array_merge(static::$_ruleMessages, $langMessages); static::$_ruleMessages = array_merge(static::$_ruleMessages, $langMessages);
} else { } else {
@ -151,9 +152,9 @@ class Validator
* @param array $params * @param array $params
* @return bool * @return bool
*/ */
protected function validateRequired($field, $value, $params= array()) protected function validateRequired($field, $value, $params = array())
{ {
if (isset($params[0]) && (bool) $params[0]){ if (isset($params[0]) && (bool)$params[0]) {
$find = $this->getPart($this->_fields, explode('.', $field), true); $find = $this->getPart($this->_fields, explode('.', $field), true);
return $find[1]; return $find[1];
} }
@ -249,7 +250,7 @@ class Validator
*/ */
protected function validateInteger($field, $value, $params) protected function validateInteger($field, $value, $params)
{ {
if (isset($params[0]) && (bool) $params[0]){ if (isset($params[0]) && (bool)$params[0]) {
//strict mode //strict mode
return preg_match('/^([0-9]|-[1-9]|-?[1-9][0-9]*)$/i', $value); return preg_match('/^([0-9]|-[1-9]|-?[1-9][0-9]*)$/i', $value);
} }
@ -387,7 +388,6 @@ class Validator
* @param string $field * @param string $field
* @param mixed $value * @param mixed $value
* @param array $params * @param array $params
* @return bool * @return bool
*/ */
protected function validateBetween($field, $value, $params) protected function validateBetween($field, $value, $params)
@ -461,7 +461,7 @@ class Validator
$strict = true; $strict = true;
if (isset($params[1])) { if (isset($params[1])) {
$strict = (bool) $params[1]; $strict = (bool)$params[1];
} }
$isContains = false; $isContains = false;
@ -596,7 +596,7 @@ class Validator
*/ */
protected function validateSlug($field, $value) protected function validateSlug($field, $value)
{ {
if(is_array($value)) { if (is_array($value)) {
return false; return false;
} }
return preg_match('/^([-a-z0-9_-])+$/i', $value); return preg_match('/^([-a-z0-9_-])+$/i', $value);
@ -741,7 +741,7 @@ class Validator
return false; return false;
} }
for ($i = 0; $i < $strlen; $i++) { for ($i = 0; $i < $strlen; $i++) {
$digit = (int) substr($number, $strlen - $i - 1, 1); $digit = (int)substr($number, $strlen - $i - 1, 1);
if ($i % 2 == 1) { if ($i % 2 == 1) {
$sub_total = $digit * 2; $sub_total = $digit * 2;
if ($sub_total > 9) { if ($sub_total > 9) {
@ -827,7 +827,8 @@ class Validator
} }
//Validate optional field //Validate optional field
protected function validateOptional($field, $value, $params) { protected function validateOptional($field, $value, $params)
{
//Always return true //Always return true
return true; return true;
} }
@ -940,24 +941,21 @@ class Validator
} }
} }
return array($values, true); return array($values, true);
} } // Dead end, abort
// Dead end, abort elseif ($identifier === NULL || !isset($data[$identifier])) {
elseif ($identifier === NULL || ! isset($data[$identifier])) { if ($allow_empty) {
if ($allow_empty){
//when empty values are allowed, we only care if the key exists //when empty values are allowed, we only care if the key exists
return array(null, array_key_exists($identifier, $data)); return array(null, array_key_exists($identifier, $data));
} }
return array(null, false); return array(null, false);
} } // Match array element
// Match array element
elseif (count($identifiers) === 0) { elseif (count($identifiers) === 0) {
if ($allow_empty){ if ($allow_empty) {
//when empty values are allowed, we only care if the key exists //when empty values are allowed, we only care if the key exists
return array(null, array_key_exists($identifier, $data)); return array(null, array_key_exists($identifier, $data));
} }
return array($data[$identifier], false); return array($data[$identifier], $allow_empty);
} } // We need to go deeper
// We need to go deeper
else { else {
return $this->getPart($data[$identifier], $identifiers, $allow_empty); return $this->getPart($data[$identifier], $identifiers, $allow_empty);
} }
@ -973,7 +971,7 @@ class Validator
$set_to_break = false; $set_to_break = false;
foreach ($this->_validations as $v) { foreach ($this->_validations as $v) {
foreach ($v['fields'] as $field) { foreach ($v['fields'] as $field) {
list($values, $multiple) = $this->getPart($this->_fields, explode('.', $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 // Don't validate if the field is not required and the value is empty
if ($this->hasRule('optional', $field) && isset($values)) { if ($this->hasRule('optional', $field) && isset($values)) {
@ -981,7 +979,7 @@ class Validator
} elseif ( } elseif (
$v['rule'] !== 'required' && !$this->hasRule('required', $field) && $v['rule'] !== 'required' && !$this->hasRule('required', $field) &&
$v['rule'] !== 'accepted' && $v['rule'] !== 'accepted' &&
(! isset($values) || $values === '' || ($multiple && count($values) == 0)) (!isset($values) || $values === '' || ($multiple && count($values) == 0))
) { ) {
continue; continue;
} }
@ -996,6 +994,8 @@ class Validator
if (!$multiple) { if (!$multiple) {
$values = array($values); $values = array($values);
} else if (! $this->hasRule('required', $field)){
$values = array_filter($values);
} }
$result = true; $result = true;
@ -1005,13 +1005,13 @@ class Validator
if (!$result) { if (!$result) {
$this->error($field, $v['message'], $v['params']); $this->error($field, $v['message'], $v['params']);
if($this->stop_on_first_fail) { if ($this->stop_on_first_fail) {
$set_to_break = true; $set_to_break = true;
break; break;
} }
} }
} }
if($set_to_break) break; if ($set_to_break) break;
} }
return count($this->errors()) === 0; return count($this->errors()) === 0;
@ -1021,8 +1021,9 @@ class Validator
* Should the validation stop a rule is failed * Should the validation stop a rule is failed
* @param bool $stop * @param bool $stop
*/ */
public function stopOnFirstFail($stop = true) { public function stopOnFirstFail($stop = true)
$this->stop_on_first_fail = (bool) $stop; {
$this->stop_on_first_fail = (bool)$stop;
} }
/** /**
@ -1101,8 +1102,7 @@ class Validator
*/ */
public static function addRule($name, $callback, $message = null) public static function addRule($name, $callback, $message = null)
{ {
if ($message === null) if ($message === null) {
{
$message = static::ERROR_DEFAULT; $message = static::ERROR_DEFAULT;
} }
@ -1114,16 +1114,14 @@ class Validator
public function getUniqueRuleName($fields) public function getUniqueRuleName($fields)
{ {
if (is_array($fields)) if (is_array($fields)) {
{
$fields = implode("_", $fields); $fields = implode("_", $fields);
} }
$orgName = "{$fields}_rule"; $orgName = "{$fields}_rule";
$name = $orgName; $name = $orgName;
$rules = $this->getRules(); $rules = $this->getRules();
while (isset($rules[$name])) while (isset($rules[$name])) {
{
$name = $orgName . "_" . rand(0, 10000); $name = $orgName . "_" . rand(0, 10000);
} }
@ -1158,8 +1156,7 @@ class Validator
$params = array_slice(func_get_args(), 2); $params = array_slice(func_get_args(), 2);
if (is_callable($rule) if (is_callable($rule)
&& !(is_string($rule) && $this->hasValidator($rule))) && !(is_string($rule) && $this->hasValidator($rule))) {
{
$name = $this->getUniqueRuleName($fields); $name = $this->getUniqueRuleName($fields);
$msg = isset($params[0]) ? $params[0] : null; $msg = isset($params[0]) ? $params[0] : null;
$this->addInstanceRule($name, $rule, $msg); $this->addInstanceRule($name, $rule, $msg);
@ -1190,8 +1187,8 @@ class Validator
$this->_validations[] = array( $this->_validations[] = array(
'rule' => $rule, 'rule' => $rule,
'fields' => (array) $fields, 'fields' => (array)$fields,
'params' => (array) $params, 'params' => (array)$params,
'message' => $message 'message' => $message
); );
@ -1240,7 +1237,7 @@ class Validator
if (is_array($params)) { if (is_array($params)) {
$i = 1; $i = 1;
foreach ($params as $k => $v) { foreach ($params as $k => $v) {
$tag = '{field'. $i .'}'; $tag = '{field' . $i . '}';
$label = isset($params[$k]) && (is_numeric($params[$k]) || is_string($params[$k])) && isset($this->_labels[$params[$k]]) ? $this->_labels[$params[$k]] : $tag; $label = isset($params[$k]) && (is_numeric($params[$k]) || is_string($params[$k])) && isset($this->_labels[$params[$k]]) ? $this->_labels[$params[$k]] : $tag;
$msg = str_replace($tag, $label, $msg); $msg = str_replace($tag, $label, $msg);
$i++; $i++;
@ -1263,8 +1260,8 @@ class Validator
foreach ($rules as $ruleType => $params) { foreach ($rules as $ruleType => $params) {
if (is_array($params)) { if (is_array($params)) {
foreach ($params as $innerParams) { foreach ($params as $innerParams) {
if (! is_array($innerParams)){ if (!is_array($innerParams)) {
$innerParams = (array) $innerParams; $innerParams = (array)$innerParams;
} }
array_unshift($innerParams, $ruleType); array_unshift($innerParams, $ruleType);
call_user_func_array(array($this, 'rule'), $innerParams); call_user_func_array(array($this, 'rule'), $innerParams);
@ -1296,10 +1293,11 @@ class Validator
* @param string field_name * @param string field_name
* @param array $rules * @param array $rules
*/ */
public function mapFieldRules($field_name, $rules){ public function mapFieldRules($field_name, $rules)
{
$me = $this; $me = $this;
array_map(function($rule) use($field_name, $me){ array_map(function ($rule) use ($field_name, $me) {
//rule must be an array //rule must be an array
$rule = (array)$rule; $rule = (array)$rule;
@ -1309,16 +1307,16 @@ class Validator
//find a custom message, if any //find a custom message, if any
$message = null; $message = null;
if (isset($rule['message'])){ if (isset($rule['message'])) {
$message = $rule['message']; $message = $rule['message'];
unset($rule['message']); unset($rule['message']);
} }
//Add the field and additional parameters to the rule //Add the field and additional parameters to the rule
$added = call_user_func_array(array($me, 'rule'), array_merge(array($rule_name, $field_name), $rule)); $added = call_user_func_array(array($me, 'rule'), array_merge(array($rule_name, $field_name), $rule));
if (! empty($message)){ if (!empty($message)) {
$added->message($message); $added->message($message);
} }
}, (array) $rules); }, (array)$rules);
} }
/** /**
@ -1326,9 +1324,10 @@ class Validator
* *
* @param array $rules * @param array $rules
*/ */
public function mapFieldsRules($rules){ public function mapFieldsRules($rules)
{
$me = $this; $me = $this;
array_map(function($field_name) use($rules, $me){ array_map(function ($field_name) use ($rules, $me) {
$me->mapFieldRules($field_name, $rules[$field_name]); $me->mapFieldRules($field_name, $rules[$field_name]);
}, array_keys($rules)); }, array_keys($rules));
} }

View File

@ -1,4 +1,5 @@
<?php <?php
use Valitron\Validator; use Valitron\Validator;
class ValidateTest extends BaseTestCase class ValidateTest extends BaseTestCase
@ -21,6 +22,7 @@ class ValidateTest extends BaseTestCase
$v->rule('required', 'name'); $v->rule('required', 'name');
$this->assertFalse($v->errors('name')); $this->assertFalse($v->errors('name'));
} }
public function testArrayOfFieldsToValidate() public function testArrayOfFieldsToValidate()
{ {
$v = new Validator(array('name' => 'Chester Tester', 'email' => 'chester@tester.com')); $v = new Validator(array('name' => 'Chester Tester', 'email' => 'chester@tester.com'));
@ -158,7 +160,8 @@ class ValidateTest extends BaseTestCase
$this->assertFalse($v->validate()); $this->assertFalse($v->validate());
} }
public function testAcceptedNotSet(){ public function testAcceptedNotSet()
{
$v = new Validator(); $v = new Validator();
$v->rule('accepted', 'agree'); $v->rule('accepted', 'agree');
$this->assertFalse($v->validate()); $this->assertFalse($v->validate());
@ -189,7 +192,8 @@ class ValidateTest extends BaseTestCase
$this->assertTrue($v->validate()); $this->assertTrue($v->validate());
} }
public function testIntegerStrict(){ public function testIntegerStrict()
{
$v = new Validator(array('num' => ' 41243')); $v = new Validator(array('num' => ' 41243'));
$v->rule('integer', 'num'); $v->rule('integer', 'num');
@ -645,13 +649,15 @@ class ValidateTest extends BaseTestCase
$this->assertFalse($v->validate()); $this->assertFalse($v->validate());
} }
public function testEmailDnsValid(){ public function testEmailDnsValid()
{
$v = new Validator(array('name' => 'Chester Tester', 'email' => 'chester@tester.com')); $v = new Validator(array('name' => 'Chester Tester', 'email' => 'chester@tester.com'));
$v->rule('emailDNS', 'email'); $v->rule('emailDNS', 'email');
$this->assertTrue($v->validate()); $this->assertTrue($v->validate());
} }
public function testEmailDnsInvalid(){ public function testEmailDnsInvalid()
{
$v = new Validator(array('name' => 'Chester Tester', 'email' => 'chester@tester.zyx')); $v = new Validator(array('name' => 'Chester Tester', 'email' => 'chester@tester.zyx'));
$v->rule('emailDNS', 'email'); $v->rule('emailDNS', 'email');
$this->assertFalse($v->validate()); $this->assertFalse($v->validate());
@ -995,11 +1001,12 @@ class ValidateTest extends BaseTestCase
$this->assertEquals($v1->errors(), $v2->errors()); $this->assertEquals($v1->errors(), $v2->errors());
} }
public function testMalformedBulkRules(){ public function testMalformedBulkRules()
{
$v = new Validator(); $v = new Validator();
$v->rules( $v->rules(
array( array(
'required'=>array('foo', 'bar') 'required' => array('foo', 'bar')
) )
); );
@ -1053,7 +1060,9 @@ class ValidateTest extends BaseTestCase
public function testAddRuleClosure() public function testAddRuleClosure()
{ {
$v = new Validator(array('name' => 'Chester Tester')); $v = new Validator(array('name' => 'Chester Tester'));
$v->addRule('testRule', function() { return true; }); $v->addRule('testRule', function () {
return true;
});
$v->rule('testRule', 'name'); $v->rule('testRule', 'name');
$this->assertTrue($v->validate()); $this->assertTrue($v->validate());
} }
@ -1061,7 +1070,9 @@ class ValidateTest extends BaseTestCase
public function testAddRuleClosureReturnsFalse() public function testAddRuleClosureReturnsFalse()
{ {
$v = new Validator(array('name' => 'Chester Tester')); $v = new Validator(array('name' => 'Chester Tester'));
$v->addRule('testRule', function() { return false; }); $v->addRule('testRule', function () {
return false;
});
$v->rule('testRule', 'name'); $v->rule('testRule', 'name');
$this->assertFalse($v->validate()); $this->assertFalse($v->validate());
} }
@ -1069,7 +1080,9 @@ class ValidateTest extends BaseTestCase
public function testAddRuleClosureWithFieldArray() public function testAddRuleClosureWithFieldArray()
{ {
$v = new Validator(array('name' => 'Chester Tester', 'email' => 'foo@example.com')); $v = new Validator(array('name' => 'Chester Tester', 'email' => 'foo@example.com'));
$v->addRule('testRule', function() { return true; }); $v->addRule('testRule', function () {
return true;
});
$v->rule('testRule', array('name', 'email')); $v->rule('testRule', array('name', 'email'));
$this->assertTrue($v->validate()); $this->assertTrue($v->validate());
} }
@ -1077,7 +1090,9 @@ class ValidateTest extends BaseTestCase
public function testAddRuleClosureWithArrayAsExtraParameter() public function testAddRuleClosureWithArrayAsExtraParameter()
{ {
$v = new Validator(array('name' => 'Chester Tester')); $v = new Validator(array('name' => 'Chester Tester'));
$v->addRule('testRule', function() { return true; }); $v->addRule('testRule', function () {
return true;
});
$v->rule('testRule', 'name', array('foo', 'bar')); $v->rule('testRule', 'name', array('foo', 'bar'));
$this->assertTrue($v->validate()); $this->assertTrue($v->validate());
} }
@ -1090,8 +1105,16 @@ class ValidateTest extends BaseTestCase
$this->assertTrue($v->validate()); $this->assertTrue($v->validate());
} }
public function sampleObjectCallback() { return true; } public function sampleObjectCallback()
public function sampleObjectCallbackFalse() { return false; } {
return true;
}
public function sampleObjectCallbackFalse()
{
return false;
}
public function testAddRuleCallbackArray() public function testAddRuleCallbackArray()
{ {
$v = new Validator(array('name' => 'Chester Tester')); $v = new Validator(array('name' => 'Chester Tester'));
@ -1162,7 +1185,7 @@ class ValidateTest extends BaseTestCase
$discover = array(6011712400392605, 6011536340491809, 6011785775263015, 6011984124619056, 6011320958064251); $discover = array(6011712400392605, 6011536340491809, 6011785775263015, 6011984124619056, 6011320958064251);
foreach (compact('visa', 'mastercard', 'amex', 'dinersclub', 'discover') as $type => $numbers) { foreach (compact('visa', 'mastercard', 'amex', 'dinersclub', 'discover') as $type => $numbers) {
foreach($numbers as $number) { foreach ($numbers as $number) {
$v = new Validator(array('test' => $number)); $v = new Validator(array('test' => $number));
$v->rule('creditCard', 'test'); $v->rule('creditCard', 'test');
$this->assertTrue($v->validate()); $this->assertTrue($v->validate());
@ -1186,7 +1209,7 @@ class ValidateTest extends BaseTestCase
$discover = array(5011712400392605, 5011536340491809, 5011785775263015, 5011984124619056, 5011320958064251); $discover = array(5011712400392605, 5011536340491809, 5011785775263015, 5011984124619056, 5011320958064251);
foreach (compact('visa', 'mastercard', 'amex', 'dinersclub', 'discover') as $type => $numbers) { foreach (compact('visa', 'mastercard', 'amex', 'dinersclub', 'discover') as $type => $numbers) {
foreach($numbers as $number) { foreach ($numbers as $number) {
$v = new Validator(array('test' => $number)); $v = new Validator(array('test' => $number));
$v->rule('creditCard', 'test'); $v->rule('creditCard', 'test');
$this->assertFalse($v->validate()); $this->assertFalse($v->validate());
@ -1341,30 +1364,31 @@ class ValidateTest extends BaseTestCase
public function testRequiredEdgeCases() public function testRequiredEdgeCases()
{ {
$v = new Validator(array( $v = new Validator(array(
'zero'=>0, 'zero' => 0,
'zero_txt' => '0', 'zero_txt' => '0',
'false'=>false, 'false' => false,
'empty_array'=>array() 'empty_array' => array()
)); ));
$v->rule('required', array('zero', 'zero_txt', 'false', 'empty_array')); $v->rule('required', array('zero', 'zero_txt', 'false', 'empty_array'));
$this->assertTrue($v->validate()); $this->assertTrue($v->validate());
} }
public function testRequiredAllowEmpty(){ public function testRequiredAllowEmpty()
$data= array( {
'empty_text'=>'', $data = array(
'empty_text' => '',
'null_value' => null, 'null_value' => null,
'in_array'=>array( 'in_array' => array(
'empty_text'=>'' 'empty_text' => ''
) )
); );
$v1= new Validator($data); $v1 = new Validator($data);
$v1->rule('required', array('empty_text', 'null_value', 'in_array.empty_text')); $v1->rule('required', array('empty_text', 'null_value', 'in_array.empty_text'));
$this->assertFalse($v1->validate()); $this->assertFalse($v1->validate());
$v2= new Validator($data); $v2 = new Validator($data);
$v2->rule('required', array('empty_text', 'null_value', 'in_array.empty_text'), true); $v2->rule('required', array('empty_text', 'null_value', 'in_array.empty_text'), true);
$this->assertTrue($v2->validate()); $this->assertTrue($v2->validate());
} }
@ -1457,8 +1481,41 @@ class ValidateTest extends BaseTestCase
$this->assertFalse($v->validate()); $this->assertFalse($v->validate());
} }
/**
* @see https://github.com/vlucas/valitron/issues/262
*/
public function testOptionalArrayPartsAreIgnored()
{
$v = new Validator(array(
'data' => array(
array('foo' => '2018-01-01'),
array('bar' => 1)
)
)
);
$v->rule('date', 'data.*.foo');
$this->assertTrue($v->validate());
}
/**
* @see https://github.com/vlucas/valitron/issues/262
*/
public function testRequiredArrayPartsAreNotIgnored()
{
$v = new Validator(array(
'data' => array(
array('foo' => '2018-01-01'),
array('bar' => 1)
)
)
);
$v->rule('required', 'data.*.foo');
$v->rule('date', 'data.*.foo');
$this->assertFalse($v->validate());
}
} }
function sampleFunctionCallback($field, $value, array $params) { function sampleFunctionCallback($field, $value, array $params)
{
return true; return true;
} }