如何通过键名/路径访问和操作多维数组?
How to access and manipulate multi-dimensional array by key names / path?
我必须在 PHP 中实现 setter,它允许我指定数组的键或子键(目标),将名称作为点分隔键值传递。
给定以下代码:
$arr = array('a' => 1,
'b' => array(
'y' => 2,
'x' => array('z' => 5, 'w' => 'abc')
),
'c' => null);
$key = 'b.x.z';
$path = explode('.', $key);
从$key
的值我想达到$arr['b']['x']['z']
的5的值。
现在,给定一个变量值 $key
和一个不同的 $arr
值(具有不同的深度)。
如何设置 $key
引用的元素的值?
为了getterget()
我写了这段代码:
public static function get($name, $default = null)
{
$setting_path = explode('.', $name);
$val = $this->settings;
foreach ($setting_path as $key) {
if(array_key_exists($key, $val)) {
$val = $val[$key];
} else {
$val = $default;
break;
}
}
return $val;
}
写一个 setter 比较困难,因为我成功地到达了正确的元素(从 $key
),但是我不能在原始数组中设置值,我不知道如何一次指定所有键。
我应该使用某种回溯吗?或者我可以避免吗?
我为您提供的解决方案不是纯 PHP,而是使用 ouzo goodies concretely Arrays::getNestedValue 方法:
$arr = array('a' => 1,
'b' => array(
'y' => 2,
'x' => array('z' => 5, 'w' => 'abc')
),
'c' => null);
$key = 'b.x.z';
$path = explode('.', $key);
print_r(Arrays::getNestedValue($arr, $path));
同样,如果你需要设置嵌套值,你可以使用Arrays::setNestedValue方法。
$arr = array('a' => 1,
'b' => array(
'y' => 2,
'x' => array('z' => 5, 'w' => 'abc')
),
'c' => null);
Arrays::setNestedValue($arr, array('d', 'e', 'f'), 'value');
print_r($arr);
假设$path
通过explode
已经是一个数组(或添加到函数),那么你可以使用引用。您需要添加一些错误检查以防 $path
等无效(想想 isset
):
$key = 'b.x.z';
$path = explode('.', $key);
Getter
function get($path, $array) {
//$path = explode('.', $path); //if needed
$temp =& $array;
foreach($path as $key) {
$temp =& $temp[$key];
}
return $temp;
}
$value = get($path, $arr); //returns NULL if the path doesn't exist
Setter/创作者
此组合将在现有数组中设置一个值,或者如果您传递一个尚未定义的数组,则创建该数组。确保定义要通过引用传递的 $array
&$array
:
function set($path, &$array=array(), $value=null) {
//$path = explode('.', $path); //if needed
$temp =& $array;
foreach($path as $key) {
$temp =& $temp[$key];
}
$temp = $value;
}
set($path, $arr);
//or
set($path, $arr, 'some value');
取消设置
这将 unset
路径中的最终键:
function unsetter($path, &$array) {
//$path = explode('.', $path); //if needed
$temp =& $array;
foreach($path as $key) {
if(!is_array($temp[$key])) {
unset($temp[$key]);
} else {
$temp =& $temp[$key];
}
}
}
unsetter($path, $arr);
*最初的答案有一些有限的功能,我将保留这些功能以防它们对某人有用:
Setter
确保定义 $array
以通过引用传递 &$array
:
function set(&$array, $path, $value) {
//$path = explode('.', $path); //if needed
$temp =& $array;
foreach($path as $key) {
$temp =& $temp[$key];
}
$temp = $value;
}
set($arr, $path, 'some value');
或者如果你想 return 更新数组(因为我很无聊):
function set($array, $path, $value) {
//$path = explode('.', $path); //if needed
$temp =& $array;
foreach($path as $key) {
$temp =& $temp[$key];
}
$temp = $value;
return $array;
}
$arr = set($arr, $path, 'some value');
创作者
如果您不想创建数组并有选择地设置值:
function create($path, $value=null) {
//$path = explode('.', $path); //if needed
foreach(array_reverse($path) as $key) {
$value = array($key => $value);
}
return $value;
}
$arr = create($path);
//or
$arr = create($path, 'some value');
为了好玩
给定一个字符串 b.x.z
:
构造和计算类似 $array['b']['x']['z']
的东西
function get($array, $path) {
//$path = explode('.', $path); //if needed
$path = "['" . implode("']['", $path) . "']";
eval("$result = $array{$path};");
return $result;
}
设置类似 $array['b']['x']['z'] = 'some value';
:
function set(&$array, $path, $value) {
//$path = explode('.', $path); //if needed
$path = "['" . implode("']['", $path) . "']";
eval("$array{$path} = $value;");
}
取消设置 $array['b']['x']['z']
:
function unsetter(&$array, $path) {
//$path = explode('.', $path); //if needed
$path = "['" . implode("']['", $path) . "']";
eval("unset($array{$path});");
}
我有一个经常使用的实用程序,我将与大家分享。不同之处在于它使用数组访问符号(例如 b[x][z]
)而不是点符号(例如 b.x.z
)。通过文档和代码,它是不言自明的。
<?php
class Utils {
/**
* Gets the value from input based on path.
* Handles objects, arrays and scalars. Nesting can be mixed.
* E.g.: $input->a->b->c = 'val' or $input['a']['b']['c'] = 'val' will
* return "val" with path "a[b][c]".
* @see Utils::arrayParsePath
* @param mixed $input
* @param string $path
* @param mixed $default Optional default value to return on failure (null)
* @return NULL|mixed NULL on failure, or the value on success (which may also be NULL)
*/
public static function getValueByPath($input,$path,$default=null) {
if ( !(isset($input) && (static::isIterable($input) || is_scalar($input))) ) {
return $default; // null already or we can't deal with this, return early
}
$pathArray = static::arrayParsePath($path);
$last = &$input;
foreach ( $pathArray as $key ) {
if ( is_object($last) && property_exists($last,$key) ) {
$last = &$last->$key;
} else if ( (is_scalar($last) || is_array($last)) && isset($last[$key]) ) {
$last = &$last[$key];
} else {
return $default;
}
}
return $last;
}
/**
* Parses an array path like a[b][c] into a lookup array like array('a','b','c')
* @param string $path
* @return array
*/
public static function arrayParsePath($path) {
preg_match_all('/\[([^[]*)]/',$path,$matches);
if ( isset($matches[1]) ) {
$matches = $matches[1];
} else {
$matches = array();
}
preg_match('/^([^[]+)/',$path,$name);
if ( isset($name[1]) ) {
array_unshift($matches,$name[1]);
} else {
$matches = array();
}
return $matches;
}
/**
* Check if a value/object/something is iterable/traversable,
* e.g. can it be run through a foreach?
* Tests for a scalar array (is_array), an instance of Traversable, and
* and instance of stdClass
* @param mixed $value
* @return boolean
*/
public static function isIterable($value) {
return is_array($value) || $value instanceof Traversable || $value instanceof stdClass;
}
}
$arr = array('a' => 1,
'b' => array(
'y' => 2,
'x' => array('z' => 5, 'w' => 'abc')
),
'c' => null);
$key = 'b[x][z]';
var_dump(Utils::getValueByPath($arr,$key)); // int 5
?>
作为 "getter",我过去用过这个:
$array = array('data' => array('one' => 'first', 'two' => 'second'));
$key = 'data.one';
function find($key, $array) {
$parts = explode('.', $key);
foreach ($parts as $part) {
$array = $array[$part];
}
return $array;
}
$result = find($key, $array);
var_dump($result);
如果数组的键是唯一的,您可以使用 array_walk_recursive:
在几行代码中解决问题
$arr = array('a' => 1,
'b' => array(
'y' => 2,
'x' => array('z' => 5, 'w' => 'abc')
),
'c' => null);
function changeVal(&$v, $key, $mydata) {
if($key == $mydata[0]) {
$v = $mydata[1];
}
}
$key = 'z';
$value = '56';
array_walk_recursive($arr, 'changeVal', array($key, $value));
print_r($arr);
此函数与已接受的答案相同,另外是通过引用添加第三个参数,如果存在密钥,则该参数设置为 true/false
function drupal_array_get_nested_value(array &$array, array $parents, &$key_exists = NULL) {
$ref = &$array;
foreach ($parents as $parent) {
if (is_array($ref) && array_key_exists($parent, $ref)) {
$ref = &$ref[$parent];
}
else {
$key_exists = FALSE;
$null = NULL;
return $null;
}
}
$key_exists = TRUE;
return $ref;
}
我有一个非常简单但很脏的解决方案(真的很脏!如果密钥的值不受信任,请不要使用!)。它可能比遍历数组更有效。
function array_get($key, $array) {
return eval('return $array["' . str_replace('.', '"]["', $key) . '"];');
}
function array_set($key, &$array, $value=null) {
eval('$array["' . str_replace('.', '"]["', $key) . '"] = $value;');
}
这两个函数都对一段代码执行 eval
,其中键被转换为数组的一个元素作为 PHP 代码。并且它returns或者在相应的键上设置数组值。
这里是访问和操作 MD 数组的简单代码。但是没有证券。
setter :
eval('$vars = &$array["' . implode('"]["', explode('.', strtolower($dot_seperator_path))) . '"];');
$vars = $new_value;
getter:
eval('$vars = $array["' . implode('"]["', explode('.', strtolower($dot_seperator_path))) . '"];');
return $vars;
getter
的另一种解决方案,使用简单的 array_reduce
方法
@AbraCadaver 的解决方案很好,但不完整:
- 缺少可选的分隔符参数并在需要时拆分
- 如果尝试从
['one' => 2]
中的 'one.two'
等标量值获取密钥,则会引发错误
我的解决方案是:
function get ($array, $path, $separator = '.') {
if (is_string($path)) {
$path = explode($separator, $path);
}
return array_reduce(
$path,
function ($carry, $item) {
return $carry[$item] ?? null;
},
$array
);
}
由于 ??
运算符,它需要 PHP 7,但这可以很容易地更改为旧版本...
这是一种使用静态 class 的方法。这种风格的好处是您的配置将在您的应用程序中全局访问。
它的工作原理是采用关键路径,例如 "database.mysql.username" 并将字符串拆分为每个关键部分,然后移动指针以创建嵌套数组。
这种方法的好处是您可以提供部分键并取回配置值数组,而不仅限于最终值。它还使 "default values" 的实现变得微不足道。
如果您想拥有多个配置存储,只需删除静态关键字并将其用作对象即可。
class Config
{
private static $configStore = [];
// This determines what separates the path
// Examples: "." = 'example.path.value' or "/" = 'example/path/value'
private static $separator = '.';
public static function set($key, $value)
{
$keys = explode(self::$separator, $key);
// Start at the root of the configuration array
$pointer = &self::$configStore;
foreach ($keys as $keySet) {
// Check to see if a key exists, if it doesn't, set that key as an empty array
if (!isset($pointer[$keySet])) {
$pointer[$keySet] = [];
}
// Set the pointer to the current key
$pointer = &$pointer[$keySet];
}
// Because we kept changing the pointer in the loop above, the pointer should be sitting at our desired location
$pointer = $value;
}
public static function get($key, $defaultValue = null)
{
$keys = explode(self::$separator, $key);
// Start at the root of the configuration array
$pointer = &self::$configStore;
foreach ($keys as $keySet) {
// If we don't have a key as a part of the path, we should return the default value (null)
if (!isset($pointer[$keySet])) {
return $defaultValue;
}
$pointer = &$pointer[$keySet];
}
// Because we kept changing the pointer in the loop above, the pointer should be sitting at our desired location
return $pointer;
}
}
// Examples of how to use
Config::set('database.mysql.username', 'exampleUsername');
Config::set('database.mysql.password', 'examplePassword');
Config::set('database.mysql.database', 'exampleDatabase');
Config::set('database.mysql.host', 'exampleHost');
// Get back all the database configuration keys
var_dump(Config::get('database.mysql'));
// Get back a particular key from the database configuration
var_dump(Config::get('database.mysql.host'));
// Get back a particular key from the database configuration with a default if it doesn't exist
var_dump(Config::get('database.mysql.port', 3306));
我必须在 PHP 中实现 setter,它允许我指定数组的键或子键(目标),将名称作为点分隔键值传递。
给定以下代码:
$arr = array('a' => 1,
'b' => array(
'y' => 2,
'x' => array('z' => 5, 'w' => 'abc')
),
'c' => null);
$key = 'b.x.z';
$path = explode('.', $key);
从$key
的值我想达到$arr['b']['x']['z']
的5的值。
现在,给定一个变量值 $key
和一个不同的 $arr
值(具有不同的深度)。
如何设置 $key
引用的元素的值?
为了getterget()
我写了这段代码:
public static function get($name, $default = null)
{
$setting_path = explode('.', $name);
$val = $this->settings;
foreach ($setting_path as $key) {
if(array_key_exists($key, $val)) {
$val = $val[$key];
} else {
$val = $default;
break;
}
}
return $val;
}
写一个 setter 比较困难,因为我成功地到达了正确的元素(从 $key
),但是我不能在原始数组中设置值,我不知道如何一次指定所有键。
我应该使用某种回溯吗?或者我可以避免吗?
我为您提供的解决方案不是纯 PHP,而是使用 ouzo goodies concretely Arrays::getNestedValue 方法:
$arr = array('a' => 1,
'b' => array(
'y' => 2,
'x' => array('z' => 5, 'w' => 'abc')
),
'c' => null);
$key = 'b.x.z';
$path = explode('.', $key);
print_r(Arrays::getNestedValue($arr, $path));
同样,如果你需要设置嵌套值,你可以使用Arrays::setNestedValue方法。
$arr = array('a' => 1,
'b' => array(
'y' => 2,
'x' => array('z' => 5, 'w' => 'abc')
),
'c' => null);
Arrays::setNestedValue($arr, array('d', 'e', 'f'), 'value');
print_r($arr);
假设$path
通过explode
已经是一个数组(或添加到函数),那么你可以使用引用。您需要添加一些错误检查以防 $path
等无效(想想 isset
):
$key = 'b.x.z';
$path = explode('.', $key);
Getter
function get($path, $array) {
//$path = explode('.', $path); //if needed
$temp =& $array;
foreach($path as $key) {
$temp =& $temp[$key];
}
return $temp;
}
$value = get($path, $arr); //returns NULL if the path doesn't exist
Setter/创作者
此组合将在现有数组中设置一个值,或者如果您传递一个尚未定义的数组,则创建该数组。确保定义要通过引用传递的 $array
&$array
:
function set($path, &$array=array(), $value=null) {
//$path = explode('.', $path); //if needed
$temp =& $array;
foreach($path as $key) {
$temp =& $temp[$key];
}
$temp = $value;
}
set($path, $arr);
//or
set($path, $arr, 'some value');
取消设置
这将 unset
路径中的最终键:
function unsetter($path, &$array) {
//$path = explode('.', $path); //if needed
$temp =& $array;
foreach($path as $key) {
if(!is_array($temp[$key])) {
unset($temp[$key]);
} else {
$temp =& $temp[$key];
}
}
}
unsetter($path, $arr);
*最初的答案有一些有限的功能,我将保留这些功能以防它们对某人有用:
Setter
确保定义 $array
以通过引用传递 &$array
:
function set(&$array, $path, $value) {
//$path = explode('.', $path); //if needed
$temp =& $array;
foreach($path as $key) {
$temp =& $temp[$key];
}
$temp = $value;
}
set($arr, $path, 'some value');
或者如果你想 return 更新数组(因为我很无聊):
function set($array, $path, $value) {
//$path = explode('.', $path); //if needed
$temp =& $array;
foreach($path as $key) {
$temp =& $temp[$key];
}
$temp = $value;
return $array;
}
$arr = set($arr, $path, 'some value');
创作者
如果您不想创建数组并有选择地设置值:
function create($path, $value=null) {
//$path = explode('.', $path); //if needed
foreach(array_reverse($path) as $key) {
$value = array($key => $value);
}
return $value;
}
$arr = create($path);
//or
$arr = create($path, 'some value');
为了好玩
给定一个字符串 b.x.z
:
$array['b']['x']['z']
的东西
function get($array, $path) {
//$path = explode('.', $path); //if needed
$path = "['" . implode("']['", $path) . "']";
eval("$result = $array{$path};");
return $result;
}
设置类似 $array['b']['x']['z'] = 'some value';
:
function set(&$array, $path, $value) {
//$path = explode('.', $path); //if needed
$path = "['" . implode("']['", $path) . "']";
eval("$array{$path} = $value;");
}
取消设置 $array['b']['x']['z']
:
function unsetter(&$array, $path) {
//$path = explode('.', $path); //if needed
$path = "['" . implode("']['", $path) . "']";
eval("unset($array{$path});");
}
我有一个经常使用的实用程序,我将与大家分享。不同之处在于它使用数组访问符号(例如 b[x][z]
)而不是点符号(例如 b.x.z
)。通过文档和代码,它是不言自明的。
<?php
class Utils {
/**
* Gets the value from input based on path.
* Handles objects, arrays and scalars. Nesting can be mixed.
* E.g.: $input->a->b->c = 'val' or $input['a']['b']['c'] = 'val' will
* return "val" with path "a[b][c]".
* @see Utils::arrayParsePath
* @param mixed $input
* @param string $path
* @param mixed $default Optional default value to return on failure (null)
* @return NULL|mixed NULL on failure, or the value on success (which may also be NULL)
*/
public static function getValueByPath($input,$path,$default=null) {
if ( !(isset($input) && (static::isIterable($input) || is_scalar($input))) ) {
return $default; // null already or we can't deal with this, return early
}
$pathArray = static::arrayParsePath($path);
$last = &$input;
foreach ( $pathArray as $key ) {
if ( is_object($last) && property_exists($last,$key) ) {
$last = &$last->$key;
} else if ( (is_scalar($last) || is_array($last)) && isset($last[$key]) ) {
$last = &$last[$key];
} else {
return $default;
}
}
return $last;
}
/**
* Parses an array path like a[b][c] into a lookup array like array('a','b','c')
* @param string $path
* @return array
*/
public static function arrayParsePath($path) {
preg_match_all('/\[([^[]*)]/',$path,$matches);
if ( isset($matches[1]) ) {
$matches = $matches[1];
} else {
$matches = array();
}
preg_match('/^([^[]+)/',$path,$name);
if ( isset($name[1]) ) {
array_unshift($matches,$name[1]);
} else {
$matches = array();
}
return $matches;
}
/**
* Check if a value/object/something is iterable/traversable,
* e.g. can it be run through a foreach?
* Tests for a scalar array (is_array), an instance of Traversable, and
* and instance of stdClass
* @param mixed $value
* @return boolean
*/
public static function isIterable($value) {
return is_array($value) || $value instanceof Traversable || $value instanceof stdClass;
}
}
$arr = array('a' => 1,
'b' => array(
'y' => 2,
'x' => array('z' => 5, 'w' => 'abc')
),
'c' => null);
$key = 'b[x][z]';
var_dump(Utils::getValueByPath($arr,$key)); // int 5
?>
作为 "getter",我过去用过这个:
$array = array('data' => array('one' => 'first', 'two' => 'second'));
$key = 'data.one';
function find($key, $array) {
$parts = explode('.', $key);
foreach ($parts as $part) {
$array = $array[$part];
}
return $array;
}
$result = find($key, $array);
var_dump($result);
如果数组的键是唯一的,您可以使用 array_walk_recursive:
在几行代码中解决问题 $arr = array('a' => 1,
'b' => array(
'y' => 2,
'x' => array('z' => 5, 'w' => 'abc')
),
'c' => null);
function changeVal(&$v, $key, $mydata) {
if($key == $mydata[0]) {
$v = $mydata[1];
}
}
$key = 'z';
$value = '56';
array_walk_recursive($arr, 'changeVal', array($key, $value));
print_r($arr);
此函数与已接受的答案相同,另外是通过引用添加第三个参数,如果存在密钥,则该参数设置为 true/false
function drupal_array_get_nested_value(array &$array, array $parents, &$key_exists = NULL) {
$ref = &$array;
foreach ($parents as $parent) {
if (is_array($ref) && array_key_exists($parent, $ref)) {
$ref = &$ref[$parent];
}
else {
$key_exists = FALSE;
$null = NULL;
return $null;
}
}
$key_exists = TRUE;
return $ref;
}
我有一个非常简单但很脏的解决方案(真的很脏!如果密钥的值不受信任,请不要使用!)。它可能比遍历数组更有效。
function array_get($key, $array) {
return eval('return $array["' . str_replace('.', '"]["', $key) . '"];');
}
function array_set($key, &$array, $value=null) {
eval('$array["' . str_replace('.', '"]["', $key) . '"] = $value;');
}
这两个函数都对一段代码执行 eval
,其中键被转换为数组的一个元素作为 PHP 代码。并且它returns或者在相应的键上设置数组值。
这里是访问和操作 MD 数组的简单代码。但是没有证券。
setter :
eval('$vars = &$array["' . implode('"]["', explode('.', strtolower($dot_seperator_path))) . '"];');
$vars = $new_value;
getter:
eval('$vars = $array["' . implode('"]["', explode('.', strtolower($dot_seperator_path))) . '"];');
return $vars;
getter
的另一种解决方案,使用简单的 array_reduce
方法
@AbraCadaver 的解决方案很好,但不完整:
- 缺少可选的分隔符参数并在需要时拆分
- 如果尝试从
['one' => 2]
中的
'one.two'
等标量值获取密钥,则会引发错误
我的解决方案是:
function get ($array, $path, $separator = '.') {
if (is_string($path)) {
$path = explode($separator, $path);
}
return array_reduce(
$path,
function ($carry, $item) {
return $carry[$item] ?? null;
},
$array
);
}
由于 ??
运算符,它需要 PHP 7,但这可以很容易地更改为旧版本...
这是一种使用静态 class 的方法。这种风格的好处是您的配置将在您的应用程序中全局访问。
它的工作原理是采用关键路径,例如 "database.mysql.username" 并将字符串拆分为每个关键部分,然后移动指针以创建嵌套数组。
这种方法的好处是您可以提供部分键并取回配置值数组,而不仅限于最终值。它还使 "default values" 的实现变得微不足道。
如果您想拥有多个配置存储,只需删除静态关键字并将其用作对象即可。
class Config
{
private static $configStore = [];
// This determines what separates the path
// Examples: "." = 'example.path.value' or "/" = 'example/path/value'
private static $separator = '.';
public static function set($key, $value)
{
$keys = explode(self::$separator, $key);
// Start at the root of the configuration array
$pointer = &self::$configStore;
foreach ($keys as $keySet) {
// Check to see if a key exists, if it doesn't, set that key as an empty array
if (!isset($pointer[$keySet])) {
$pointer[$keySet] = [];
}
// Set the pointer to the current key
$pointer = &$pointer[$keySet];
}
// Because we kept changing the pointer in the loop above, the pointer should be sitting at our desired location
$pointer = $value;
}
public static function get($key, $defaultValue = null)
{
$keys = explode(self::$separator, $key);
// Start at the root of the configuration array
$pointer = &self::$configStore;
foreach ($keys as $keySet) {
// If we don't have a key as a part of the path, we should return the default value (null)
if (!isset($pointer[$keySet])) {
return $defaultValue;
}
$pointer = &$pointer[$keySet];
}
// Because we kept changing the pointer in the loop above, the pointer should be sitting at our desired location
return $pointer;
}
}
// Examples of how to use
Config::set('database.mysql.username', 'exampleUsername');
Config::set('database.mysql.password', 'examplePassword');
Config::set('database.mysql.database', 'exampleDatabase');
Config::set('database.mysql.host', 'exampleHost');
// Get back all the database configuration keys
var_dump(Config::get('database.mysql'));
// Get back a particular key from the database configuration
var_dump(Config::get('database.mysql.host'));
// Get back a particular key from the database configuration with a default if it doesn't exist
var_dump(Config::get('database.mysql.port', 3306));