Как получить доступ к многомерному массиву и управлять им с помощью имен/путей ключа?
Я должен реализовать сеттер в PHP, который позволяет мне указывать ключ или субключ для массива (цели), передавая имя как значение, разделенное точками-ключами.
Учитывая следующий код:
$arr = array('a' => 1,
'b' => array(
'y' => 2,
'x' => array('z' => 5, 'w' => 'abc')
),
'c' => null);
$key = 'b.x.z';
$path = explode('.', $key);
Из значения $key
я хочу получить значение 5 из $arr['b']['x']['z']
.
Теперь, учитывая значение переменной $key
и другое значение $arr
(с разной глубиной).
Как я могу установить значение элемента, на который ссылается $key
?
Для getter get()
я написал этот код:
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;
}
Написать установщик сложнее, потому что мне удалось найти нужный элемент (из $key
), но я не могу установить значение в исходном массиве, и я не знаю, как указать ключи одновременно.
Должен ли я использовать какой-то возврат? Или я могу избежать этого?
Ответы
Ответ 1
Предполагая, что $path
уже является массивом через explode
(или добавить к функции), вы можете использовать ссылки. Вам нужно добавить некоторую проверку ошибок в случае недопустимого $path
и т.д. (isset
):
$key = 'b.x.z';
$path = explode('.', $key);
добытчик
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
Сеттер/Создатель
Эта комбинация установит значение в существующем массиве или создаст массив, если вы передадите тот, который еще не определен. Обязательно определите $array
который будет передан с помощью reference &$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');
Unsetter
Это приведет к 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);
* Первоначальный ответ имел некоторые ограниченные функции, которые я оставлю на случай, если они будут полезны кому-то:
сеттер
Обязательно определите $array
который будет передан с помощью reference &$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');
Или если вы хотите вернуть обновленный массив (потому что мне скучно):
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');
Ради забавы
Создает и оценивает что-то вроде $array['b']['x']['z'];
заданная строка bxz
:
function get($array, $path) {
//$path = explode('.', $path); //if needed
$path = "['" . implode("']['", $path) . "']";
eval("\$result = \$array{$path};");
return $result;
}
Ответ 2
У меня есть решение для вас не в чистом PHP, но используя ouzo goodies конкретно Массивы:: 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);
Ответ 3
У меня есть утилита, которую я регулярно использую, которую я поделюсь. Разница заключается в том, что он использует нотацию доступа к массиву (например, 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
?>
Ответ 4
Если ключи массива уникальны, вы можете решить проблему в нескольких строках кода, используя 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);
Ответ 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);
Ответ 6
У меня есть очень простое и грязное решение для этого (действительно грязный! НЕ ИСПОЛЬЗУЙТЕ, если значение ключа не доверено!). Это может быть более эффективно, чем цикл через массив.
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 код. И он возвращает или задает значение массива в соответствующем ключе.
Ответ 7
Эта функция делает то же самое, что и принятый ответ, плюс добавляет третий параметр по ссылке, который установлен в 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;
}