Skip to content

Add array items validation and wildcard support. #1340

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 58 additions & 5 deletions src/Illuminate/Support/helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ function array_forget(&$array, $key)
if ( ! function_exists('array_get'))
{
/**
* Get an item from an array using "dot" notation.
* Get an item from an array using "dot" notation and "wildcards".
*
* @param array $array
* @param string $key
Expand All @@ -216,12 +216,65 @@ function array_forget(&$array, $key)
function array_get($array, $key, $default = null)
{
if (is_null($key)) return $array;

if (isset($array[$key])) return $array[$key];

foreach (explode('.', $key) as $segment)
// Store resulting array if key contains wildcard.
$deepArray = array();
$keys = explode('.', $key);
foreach ($keys as $n => $segment)
{
if ( ! is_array($array) or ! array_key_exists($segment, $array))
if ($segment == '*') {
// Get the rest of the keys besides current one.
$keySlice = array_slice($keys, $n+1);
// Generate new dot notation key string.
$innerKey = implode('.', $keySlice);
if (is_array($array))
{
foreach ($array as $item)
{
// Empty slice - last segment is a wildcard.
if (empty($keySlice))
{
// Last segment is a wildcard. Put item into deepArray which will be returned
// containing all of the items of the current array.
$deepArray[] = $item;
}
else
{
// Pass current array item deeper.
$innerItem = array_get($item, $innerKey, $default);
if (is_array($innerItem) and count(array_keys($keys, '*')) > 1)
{
// Multiple wildcards, add each item of inner array to the resulting new array.
foreach ($innerItem as $innerItem)
{
$deepArray[] = $innerItem;
}
}
else
{
// Only one wildcard in current key string. Add whole inner array to the resulting array.
$deepArray[] = $innerItem;
}
}
}
// Return new resulting array.
return $deepArray;
}
elseif ($n == count($keys)-1)
{
// This is the last key, so we can simply return whole array.
return $array;
}
else
{
// This is not the last key and $array is not really an array
// so we can't proceed deeper. Return default.
return value($default);
}
}
elseif ( ! is_array($array) or ! array_key_exists($segment, $array))
{
return value($default);
}
Expand Down Expand Up @@ -522,7 +575,7 @@ function link_to_action($action, $title = null, $parameters = array(), $attribut
function object_get($object, $key, $default = null)
{
if (is_null($key)) return $object;

foreach (explode('.', $key) as $segment)
{
if ( ! is_object($object) or ! isset($object->{$segment}))
Expand Down
99 changes: 88 additions & 11 deletions src/Illuminate/Validation/Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,27 @@ class Validator implements MessageProviderInterface {
*/
protected $numericRules = array('Numeric', 'Integer');

/**
* The array related validation rules.
*
* @var array
*/
protected $arrayRules = array('Array');

/**
* The implicit validation rules.
*
* @var array
*/
protected $implicitRules = array('Required', 'RequiredWith', 'RequiredWithout', 'RequiredIf', 'Accepted');

/**
* The non iterable validation rules.
*
* @var array
*/
protected $nonIterableRules = array('Exists');

/**
* Create a new Validator instance.
*
Expand Down Expand Up @@ -202,14 +216,51 @@ protected function validate($attribute, $rule)
// that the attribute is required, rules are not run for missing values.
$value = $this->getValue($attribute);

$validatable = $this->isValidatable($rule, $attribute, $value);

$method = "validate{$rule}";

if ($validatable and ! $this->$method($attribute, $value, $parameters, $this))
if ($this->isIterable($attribute, $value))
{
// Required to test items even if array is empty.
$value = empty($value) ? array(null) : $value;

foreach ($value as $item)
{
$validatable = $this->isValidatable($rule, $attribute, $item);

if ($validatable and ! $this->$method($attribute, $item, $parameters, $this))
{
$this->addError($attribute, $rule, $parameters);
}
}
}
else
{
$this->addError($attribute, $rule, $parameters);
$validatable = $this->isValidatable($rule, $attribute, $value);

if ($validatable and ! $this->$method($attribute, $value, $parameters, $this))
{
$this->addError($attribute, $rule, $parameters);
}
}

}

protected function isIterable($attribute, $rule)
{
$value = $this->getValue($attribute);

// Preg Match checksw if asterisk is a wildcard, not part of an attribute name.
// Possible wildcard positions: *.foo | foo.*.bar | foo.*
if (is_array($value) and preg_match('/(^|\.)\*(\.|$)/', $attribute))
{
// Do not iterate over value if rule is one of nonIterableRules (namely, Exists or any Extended).
if (in_array($rule, $this->nonIterableRules))
return false;

return true;
}

return false;
}

/**
Expand Down Expand Up @@ -288,6 +339,10 @@ protected function validateRequired($attribute, $value)
{
return false;
}
elseif (is_array($value) and empty($value))
{
return false;
}
elseif ($value instanceof File)
{
return (string) $value->getPath() != '';
Expand Down Expand Up @@ -368,7 +423,7 @@ protected function validateRequiredWithout($attribute, $value, $parameters)
*/
protected function validateRequiredIf($attribute, $value, $parameters)
{
if ($parameters[1] == array_get($this->data, $parameters[0]))
if ($parameters[1] == $this->getValue($parameters[0]))
{
return $this->validateRequired($attribute, $value);
}
Expand All @@ -386,9 +441,9 @@ protected function getPresentCount($attributes)
{
$count = 0;

foreach ($attributes as $key)
foreach ($attributes as $attribute)
{
if (isset($this->data[$key]) or isset($this->files[$key]))
if ( ! is_null($this->getValue($attribute)))
{
$count++;
}
Expand Down Expand Up @@ -421,7 +476,7 @@ protected function validateSame($attribute, $value, $parameters)
{
$other = $parameters[0];

return isset($this->data[$other]) and $value == $this->data[$other];
return ! is_null($otherValue = $this->getValue($other)) and $value == $otherValue;
}

/**
Expand All @@ -436,7 +491,7 @@ protected function validateDifferent($attribute, $value, $parameters)
{
$other = $parameters[0];

return isset($this->data[$other]) and $value != $this->data[$other];
return ! is_null($otherValue = $this->getValue($other)) and $value != $otherValue;
}

/**
Expand Down Expand Up @@ -467,6 +522,18 @@ protected function validateNumeric($attribute, $value)
return is_numeric($value);
}

/**
* Validate that an attribute is an array.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
protected function validateArray($attribute, $value)
{
return is_array($value);
}

/**
* Validate that an attribute is an integer.
*
Expand Down Expand Up @@ -572,13 +639,17 @@ protected function getSize($attribute, $value)
{
$hasNumeric = $this->hasRule($attribute, $this->numericRules);

// This method will determine if the attribute is a number, string, or file and
// This method will determine if the attribute is a number, string, array or file and
// return the proper size accordingly. If it is a number, then number itself
// is the size. If it is a file, we take kilobytes, and for a string the
// entire length of the string will be considered the attribute size.
if (is_numeric($value) and $hasNumeric)
{
return $this->data[$attribute];
return $value;
}
elseif (is_array($value))
{
return count($value);
}
elseif ($value instanceof File)
{
Expand Down Expand Up @@ -1057,6 +1128,10 @@ protected function getAttributeType($attribute)
{
return 'numeric';
}
elseif ($this->hasRule($attribute, $this->arrayRules))
{
return 'array';
}
elseif (array_key_exists($attribute, $this->files))
{
return 'file';
Expand Down Expand Up @@ -1452,6 +1527,7 @@ public function addImplicitExtensions(array $extensions)
foreach ($extensions as $rule => $extension)
{
$this->implicitRules[] = studly_case($rule);
$this->nonIterableRules[] = studly_case($rule);
}
}

Expand Down Expand Up @@ -1479,6 +1555,7 @@ public function addImplicitExtension($rule, Closure $extension)
$this->addExtension($rule, $extension);

$this->implicitRules[] = studly_case($rule);
$this->nonIterableRules[] = studly_case($rule);
}

/**
Expand Down
21 changes: 21 additions & 0 deletions tests/Support/SupportHelpersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,27 @@ public function testArrayGet()
}


public function testArrayGetWithWildcard()
{
$array = array('users' => array(
0 => array(
'name' => 'Taylor',
'password' => 'abcdef',
'friends' => array('Shawn', 'Dayle')
),
1 => array(
'name' => 'Dayle',
'password' => 123456,
'friends' => array('Rommie', 'Jessee')
)
));
$this->assertEquals(array('Taylor', 'Dayle'), array_get($array, 'users.*.name'));
$this->assertEquals(array('abcdef', 123456), array_get($array, 'users.*.password'));
$this->assertEquals(array(array('Shawn', 'Dayle'), array('Rommie', 'Jessee')), array_get($array, 'users.*.friends'));
$this->assertEquals(array('Shawn', 'Dayle', 'Rommie', 'Jessee'), array_get($array, 'users.*.friends.*'));
}


public function testArraySet()
{
$array = array();
Expand Down
Loading