stanislav-belichenko
@stanislav-belichenko
Full-stack PHP Developer

Как взять из многомерного массива значение, имея ключи этого значения в другом массиве или строке?

Вопрос звучит совершенно непонятно, поэтому поясню на примере кода:

$data['name1']['name2']['name3'] = 'some data'; // это элемент, который мы хотим получить

$keys_str = 'name1,name2,name3'; // можно вот так получить ключи в строку
$keys_arr = ['name1', 'name2', 'name3'] // а можно вот так в массив
$keys_str = '[name1][name2][name3]'; // а можно в строку и так
$keys_str = '["name1"]["name2"]["name3"]'; // или даже так

И в итоге, имея только ключи в том или ином виде и массив, к которому они подойдут (могут подойти) нам нужно получить значение по этим ключам в массиве, ну или ответ, что такого элемента не существует. Как это можно наиболее красиво и понятно реализовать?

Уточнение: ключи собираются из определенной строки при ее рекурсивной обработке, то есть мы их получаем последовательно и в целом можем сложить куда угодно, хоть в какой угодно массив, хоть в какую угодно строку, но суть в том, что после этого мы должны проверить, есть ли с набором таких ключей элемент в некоем отдельном массиве, или нет.
  • Вопрос задан
  • 5945 просмотров
Решения вопроса 3
/**
 * Retrieves an element within multidimensional array stored on any level by it's keys.
 * @param array $data A multidimensional array with data
 * @param array $keys A list of keys to element stored in $data
 * @return null|mixed Returns null if elements is not found. Element's value otherwise.
 */
function getElement(array $data, array $keys)
{
    /** перебираем ключи */
    foreach($keys as $key) {
        /** 
         * Если текущий элемент - массив, и в нём есть ключ, то текущий массив перезаписываем на новый.
         * А если ключа такого нет или это не массив, то возвращаем null.
         */
        if (is_array($data) && array_key_exists($key, $data)) {
            $data = $data[$key];
        } else {
            return null;
        }
    }
    
    return $data;
}

Использовать как-то так:
$data = [
  'key1' => [
    'key2' => [
      'key3' => [
        'value' => 123
      ]
    ]
  ]
];

echo getElement($data, ['key1', 'key2', 'key3', 'value']); // => 123


Можно пойти дальше и упростить себе вызов этой функции буквальной парой строк:
Добавляем обработку ключа как строки:
/**
 * Retrieves an element within multidimensional array stored on any level by it's keys.
 * @param array $data A multidimensional array with data
 * @param string|array $keys A list of keys to element stored in $data as an array or a string with joined keys with a dot (.)
 * @return null|mixed Returns null if elements is not found. Element's value otherwise.
 */
function getElement(array $data, $keys)
{
    if (is_string($keys)) $keys = explode('.', $keys);
    /** перебираем ключи */
    foreach($keys as $key) {
        /** 
         * Если текущий элемент - массив, и в нём есть ключ, то текущий массив перезаписываем на новый.
         * А если ключа такого нет или это не массив, то возвращаем null.
         */
        if (is_array($data) && array_key_exists($key, $data)) {
            $data = $data[$key];
        } else {
            return null;
        }
    }
    
    return $data;
}

И уже можно делать так:
echo getElement($data, 'key1.key2.key3.value'); // => 123

Ответ написан
Комментировать
function getElement(array $data, array $keys = [])
{
    /** присваиваем элементу текущий массив */
    $element = $data;
    
    /** перебираем ключи */
    foreach($keys as $key) {
        /** 
         * Если в текущем массиве есть ключ, то текущий массив перезаписываем на новый
         * А если ключа такого нет, то 
         * @return null
         */
        if( ($element = $element[$key]) === null) {
            return null;
        }
    }
    
    return $element;
}
Ответ написан
Stalker_RED
@Stalker_RED
Хотя я, если честно, искал какой-то вариант в одну строку, грубо говоря - некий оператор, позволяющий обратиться нужным мне образом к массиву.

Решение в одну строчку:
echo eval("return \$data$keys_str;");
Работающий пример (надеюсь, что не нужно повторять чем это опасно, в мануале все расписано же.)

Более приличное решение в одну строчку:
echo array_reduce($keys_arr,function($data, $key){return isset($data[$key])?$data[$key]:null;},$data);
Работающий пример

Конечно, будет читабельнее, если записать его в три строки:
echo array_reduce($keys_arr, function($data, $key) { 
    return isset($data[$key]) ? $data[$key] : null;
}, $data);
Используются:
array_reduce() для перебора ключей
isset() для проверки, существует ли ключ
Ну и тернарный оператор
Ответ написан
Пригласить эксперта
Ответы на вопрос 3
Sanasol
@Sanasol Куратор тега PHP
нельзя просто так взять и загуглить ошибку
ну так по мере получения ключей и забраться внутрь

$data[$key][$key2][$key3]
Ответ написан
BoShurik
@BoShurik
Symfony developer
Если есть возможность использовать сторонние компоненты:
$data['name1']['name2']['name3'] = 'some data';
$propertyAccessor = \Symfony\Component\PropertyAccess\PropertyAccess::createPropertyAccessor();
$keys = '[name1][name2][name3]';

echo $propertyAccessor->getValue($data, $keys);


Не такое быстрое, зато универсальное решение :)
Ответ написан
Bariss2013
@Bariss2013
Норм поцык!
Могу предложить вот такое решение на основе рекурсии.
/**
 * Получает из многомерного массива элемент по ключу в виде строки,
 * где каждый уровень вложенности отделен точкой, если такой элемент не будет найден,
 * то вернет значение по умолчанию
 *
 * @param $array - многомерный ассоциативный массив
 * @param $key - ключ, формата xxx.xxx.xx. Например: db.mysql.host
 * @param null $default
 *
 * @return mixed|null
 */
function arrayGet(array $array, string $key, $default = null)
{
    if (!empty($key)) {
        //разбиваем вложенность ключей на массив
        $levels = explode('.', $key);
        //получаем текущий уровень
        $currentLevel = array_shift($levels);
        //если текущий ключ существует
        if (array_key_exists($currentLevel, $array)) {
            //если значение текущего ключа является массивом, проверяем этот массив
            if (is_array($array[$currentLevel])) {
                //рекурсивный вызов с перезаписанными аргументами
                return arrayGet($array[$currentLevel], implode('.', $levels), $default);
            }

            //возвращаем значение по ключу, если оно скалярное
            return $array[$currentLevel];
        }
    }

    //возвращаем значение по умолчанию
    return $default;
}
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы