Как написать getter / setter для доступа к многоуровневому массиву по ключевым словам?

Я должен реализовать сеттер в PHP, который позволяет мне указывать ключ или дополнительный ключ массива (цель), передавая это имя как значение, разделенное точками.

Учитывая следующий код:

$arr = array('a' => 1, 'b' => array( 'y' => 2, 'x' => array('z' => 5, 'w' => 'abc') ), 'c' => null); $key = 'bxz'; $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 ), но я не могу установить значение в исходном массиве, и я не знаю, как указать ключ все сразу.

Должен ли я использовать какой-то откат? Или я могу избежать этого?

Предполагая, что $path уже является массивом через explode() (или добавить к функции), используйте ссылки. Вам нужно добавить некоторую проверку ошибок в случае недопустимого $path т. Д. …

 $key = 'bxz'; $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'); 

* Первоначальный ответ имел некоторые ограниченные функции, которые я оставлю на случай, если они будут полезны кому-то:

сеттер

Обязательно определите $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; } 

У меня есть решение для вас не в чистом PHP, но с использованием узо- лайтов конкретно. Метод массивов :: getNestedValue :

 $arr = array('a' => 1, 'b' => array( 'y' => 2, 'x' => array('z' => 5, 'w' => 'abc') ), 'c' => null); $key = 'bxz'; $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); 

У меня есть утилита, которую я регулярно использую, и я поделюсь ею. Разница заключается в том, что он использует нотацию доступа к массиву (например, b[x][z] ) вместо точечной нотации (например, bxz ). С документацией и кодом это довольно понятно.

 <?php class Utils { /** * Gets the value from input based on path. * Handles objects, arrays and scalars. Nesting can be mixed. * Eg: $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, * eg 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 ?> 

Если ключи массива уникальны, вы можете решить проблему в нескольких строках кода с помощью 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); 

Как «геттер», я использовал это в прошлом:

 $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); 

У меня есть очень простое и грязное решение для этого ( действительно грязное! НЕ ИСПОЛЬЗУЙТЕ, если значение ключа не доверено! ). Это может быть более эффективно, чем цикл через массив.

 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-код. И он возвращает или задает значение массива в соответствующем ключе.

Эта функция выполняет то же самое, что и принятый ответ, плюс добавляет третий параметр по ссылке, который установлен в 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; }