Подтвердить что ты не робот

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

Я должен реализовать сеттер в 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), но я не могу установить значение в исходном массиве, и я не знаю, как указать ключи одновременно.

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

4b9b3361

Ответ 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;
}