Я начинаю с вложенного массива произвольной глубины. В этом массиве некоторые ключи представляют собой серию токенов, разделенных точками. Например, «billingAddress.street» или «foo.bar.baz». Я хотел бы расширить эти ключевые элементы до массивов, поэтому в результате получается вложенный массив со всеми этими ключами.
Например:
[
'billingAddress.street' => 'My Street',
'foo.bar.baz' => 'biz',
]
следует расширить до:
[
'billingAddress' => [
'street' => 'My Street',
],
'foo' => [
'bar' => [
'baz' => 'biz',
]
]
]
Исходный файл «billingAddress.street» можно оставить рядом с новым массивом «billingAddress», но в этом нет необходимости (поэтому решение может работать с исходным массивом или создать новый массив). Другие элементы, такие как «billingAddress.city», возможно, должны быть добавлены к той же расширенной части массива.
Некоторые ключи могут иметь более двух токенов, разделенных точками, поэтому их нужно будет расширить глубже.
Я смотрел на array_walk_recursive()
но это действует только на элементы. Для каждого соответствующего ключа элемента я на самом деле хочу изменить родительский массив, в котором находятся эти элементы.
Я смотрел на array_map
, но это не обеспечивает доступ к ключам, и, насколько я знаю, не является рекурсивным.
Пример массива для расширения:
[
'name' => 'Name',
'address.city' => 'City',
'address.street' => 'Street',
'card' => [
'type' => 'visa',
'details.last4' => '1234',
],
]
Это должно быть расширено до:
[
'name' => 'Name',
'address.city' => 'City', // Optional
'address' => [
'city' => 'City',
'street' => 'Street',
],
'address.street' => 'Street', // Optional
'card' => [
'type' => 'visa',
'details.last4' => '1234', // Optional
'details' => [
'last4' => '1234',
],
],
]
То, что мне нужно, это то, что подходит каждому array
во вложенном массиве и может применить к нему пользовательскую функцию. Но я подозреваю, что упускаю что-то очевидное. Платежный шлюз, с которым я работаю, отправляет мне это сочетание массивов и «притворных массивов» с использованием точечной нотации, и моя цель состоит в том, чтобы преобразовать его в массив для извлечения порций.
Я полагаю, что проблема отличается от аналогичных вопросов по SO из-за этого сочетания массивов и не массивов для расширения. Концептуально это вложенный массив, где звук группы элементов в любой уровень должен быть заменен новыми массивами, поэтому здесь происходит два уровня рекурсии: прогулка по дереву и расширение, а затем прогулка по расширенным деревьям, чтобы увидеть, нужно ли дополнительное расширение.
Может оказаться полезным изменить порядок ключей, которые вы получите, взорвав комбинированный (пунктирный) ключ. В этом обратном порядке легче постепенно перенести предыдущий результат в новый массив, создавая тем самым вложенный результат для одной пары ключ / значение с точками.
Наконец, этот частичный результат может быть объединен с накопленным «великим» результатом с помощью встроенного array_merge_recursive
функция:
function expandKeys($arr) {
$result = [];
foreach($arr as $key => $value) {
if (is_array($value)) $value = expandKeys($value);
foreach(array_reverse(explode(".", $key)) as $key) $value = [$key => $value];
$result = array_merge_recursive($result, $value);
}
return $result;
}
Смотрите, это работает на repl.it
Вот рекурсивная попытка. Обратите внимание, что это не удаляет старые ключи, не поддерживает порядок ключей и игнорирует ключи типа foo.bar.baz
,
function expand(&$data) {
if (is_array($data)) {
foreach ($data as $k => $v) {
$e = explode(".", $k);
if (count($e) == 2) {
[$a, $b] = $e;
$data[$a][$b]= $v;
}
expand($data[$k]);
}
}
}
Array
(
[name] => Name
[address.city] => City
[address.street] => Street
[card] => Array
(
[type] => visa
[details.last4] => 1234
[details] => Array
(
[last4] => 1234
)
)
[address] => Array
(
[city] => City
[street] => Street
)
)
При любом вызове функции, если параметр является массивом, перебирайте ключи и значения, ища ключи с .
в них. Для любых таких ключей раскройте их. Рекурсивно вызывайте эту функцию для всех ключей в массиве.
Вот полная версия, которая поддерживает несколько .
s и очищает ключи потом:
function expand(&$data) {
if (is_array($data)) {
foreach ($data as $k => $v) {
$e = explode(".", $k);
$a = array_shift($e);
if (count($e) == 1) {
$data[$a][$e[0]] = $v;
}
else if (count($e) > 1) {
$data[$a][implode(".", $e)] = $v;
}
}
foreach ($data as $k => $v) {
expand($data[$k]);
if (preg_match('/\./', $k)) {
unset($data[$k]);
}
}
}
}
Вот РЕПЛ.
Другое решение @trincot было признано более элегантным, и это решение, которое я использую сейчас.
Вот мое решение, которое расширяет возможности и советы, данные @ggorlen
Подход, который я выбрал:
Метод класса:
protected function expandKeys($arr)
{
$result = [];
while (count($arr)) {
// Shift the first element off the array - both key and value.
// We are treating this like a stack of elements to work through,
// and some new elements may be added to the stack as we go.
$value = reset($arr);
$key = key($arr);
unset($arr[$key]);
if (strpos($key, '.') !== false) {
list($base, $ext) = explode('.', $key, 2);
if (! array_key_exists($base, $arr)) {
// This will be another array element on the end of the
// arr stack, to recurse into.
$arr[$base] = [];
}
// Add the value nested one level in.
// Value at $arr['bar.baz.biz'] is now at $arr['bar']['baz.biz']
// We may also add to this element before we get to processing it,
// for example $arr['bar.baz.bam']
$arr[$base][$ext] = $value;
} elseif (is_array($value)) {
// We already have an array value, so give the value
// the same treatment in case any keys need expanding further.
$result[$key] = $this->expandKeys($value);
} else {
// A scalar value with no expandable key.
$result[$key] = $value;
}
}
return $result;
}
$result = $this->expandKeys($sourceArray)