Сгруппировать элементы двумерного массива, используя одно из полей как ключ, а второе - как значение
Есть массив:
Array
(
[0] => Array
(
[entry_id] => 3
[element_id] => 4
[value] => a:2:{i:2;s:14:"Имя";i:4;s:18:"Фамилия";}
)
[1] => Array
(
[entry_id] => 3
[element_id] => 6
[value] => Тестовый отзыв
)
[2] => Array
(
[entry_id] => 3
[element_id] => 11
[value] => a:1:{i:2;s:16:"г. Москва";}
)
)
Данный массив собирает плагин из формы, которые заполняют люди на сайте. Все эти данные хранятся в одной таблице БД по entry_id. element_id - в массиве много ( номер вопроса в форме). Как собрать все эти данные, одного заявления в один массив для его дальнейшего вывода на сайте. Чтобы это выглядело, примерно так:
Array
(
[0] => Array
(
[entry_id] => 3
[value] => a:2:{i:2;s:14:"Имя";i:4;s:18:"Фамилия";}
[feedback] => Тестовый отзыв
[city] => a:1:{i:2;s:16:"г. Москва";}
))
Либо вставив ключ из element_id
Array
(
[0] => Array
(
[entry_id] => 3
[4] => a:2:{i:2;s:14:"Имя";i:4;s:18:"Фамилия";}
[6] => Тестовый отзыв
[city] => a:1:{i:2;s:16:"г. Москва";}
))
Ответы (3 шт):
Нет ничего проще, предлагаю даже с entry_id в виде ключей:
<?php
//Пополнить справочник названиями полей.
$fields=[
4=> ['name'=>'value', 'is_text'=>0],
6=> ['name'=>'feedback','is_text'=>1],
11=>['name'=>'city', 'is_text'=>0],
//Причина, по которой не надо ставить ?? вместо ?: при подстановках,
//ибо нам нужно конкретное имя поля, а не его "вероятность присутствия"
//Ведь справочник может быть сгенерирован из каких-то внешних мест.
12=>['name'=>'', 'is_text'=>1]
];
// Для повышения производительности, но в ущерб читабельности и отладки
// справочник может быть в таком виде:
// 4=> ['value',0],
// А также получение данных может быть в виде неассоциативного массива:
// $src=[[3,6,'Тестовый отзыв'] ...
// Такой подход будет требовать значительной внимательности,
// но иногда может оказаться единственным возможным в условиях недостатка
// ресурсов (памяти, CPU, пропускной способности и т.д.)
$src=[
[
'entry_id' => 3,
'element_id' => 4,
//Эта строка сериализована криво
// Возможная причина: текст мог быть в кодировке типа utf8mb4,
// в процессе ручного копи-паста из системы в систему,
// буквы утратили внутреннюю длину и стали utf8mb2 а может
// потерялись непечатаемые символы
// Это хороший пример того, что в БД надо складывать только
// **надёжно проверенные данные**
//'value' => 'a:2:{i:2;s:14:"Имя";i:4;s:18:"Фамилия";}'
'value' => 'a:2:{i:2;s:6:"Имя";i:4;s:14:"Фамилия";}'
],
[
'entry_id' => 3,
'element_id' => 6,
'value' => 'Тестовый отзыв'
],
[
'entry_id' => 3,
'element_id' => 11,
'value' => 'a:1:{i:2;s:16:"г. Москва";}'
],
[
'entry_id' => 3,
'element_id' => 12,
'value' => 'блаблабла'
],
];
$dst = [];
foreach($src as $row){
//Версия, исключающая появление предупреждения
//$field=(isset($fields[$row['element_id']]['name']) && $fields[$row['element_id']]['name']) ? $fields[$row['element_id']]['name'] : $row['element_id'];
//Версия, использующая игнорирование предупреждений о несуществующих переменных, но работающая быстрее и читается легче
$field=$fields[$row['element_id']]['name'] ?: $row['element_id'];
//Искать через in_array пару десятков значений в миллионном цикле - не лучшее решение.
//$dst[$row['entry_id']][$field] = (in_array($row['element_id'], $text_fields)) ? $row['value'] : unserialize($row['value']);
$dst[$row['entry_id']][$field] = $fields[$row['element_id']]['is_text'] ? $row['value'] : unserialize($row['value']);
}
print_r($dst);
А вообще, опыт показывает, что хранить данные удобней в JSON. Он и портируется куда угодно, и запаковка/распаковка работает не медленней.
Несколько альтернативных способов решения этой задачи, используя вместо итерирования элементов массива внутри цикла встроенные возможности PHP для работы с такими массивами. Я привожу их не потому, что решение Сергея чем-то плохо, а просто для демонстрации этих возможностей.
Во-первых, в PHP есть функция array_column(), которая позволяет перестроить один массив в другой, используя в качестве индексов одни поля элементов массива, а в качестве значений другие. И если мы гарантированно знаем, что все элементы массива содержат только одно, уникальное значение поля entry_id, как это показано в примере исходных данных в исходном вопросе, то можно сделать так:
$dst = array_column($src, 'value', 'element_id');
print_r($dst);
Пример вывода:
Array
(
[4] => a:2:{i:2;s:14:"Имя";i:4;s:18:"Фамилия";}
[6] => Тестовый отзыв
[11] => a:1:{i:2;s:16:"г. Москва";}
[12] => блаблабла
)
Если это не так, то данный способ не подойдёт, каждый очередной элемент исходного массива будет перезаписывать в результирующем массиве элемент с соответствующим индексом element_id. Так, например, на таком наборе исходных данных:
$src = [
[
'entry_id' => 3,
'element_id' => 4,
'value' => 'a:2:{i:2;s:14:"Имя";i:4;s:18:"Фамилия";}'
],
[
'entry_id' => 3,
'element_id' => 6,
'value' => 'Тестовый отзыв'
],
[
'entry_id' => 3,
'element_id' => 11,
'value' => 'a:1:{i:2;s:16:"г. Москва";}'
],
[
'entry_id' => 3,
'element_id' => 12,
'value' => 'блаблабла'
],
[
'entry_id' => 4,
'element_id' => 12,
'value' => 'блинблинблин'
],
];
мы получим такой результат:
Array
(
[4] => a:2:{i:2;s:14:"Имя";i:4;s:18:"Фамилия";}
[6] => Тестовый отзыв
[11] => a:1:{i:2;s:16:"г. Москва";}
[12] => блинблинблин
)
Но если наша задача - получить такой массив только для одного конкретного значения entry_id, то можно совместить эту функцию с функцией фильтрации массива array_filter() по данному конкретному значению entry_id:
function filter_by_entry($entry) {
return function($item) use ($entry) {
return $item['entry_id'] == $entry;
};
}
$dst = array_column(array_filter($src, filter_by_entry(3)), 'value', 'element_id');
print_r($dst);
Если же наша задача действительно получить полное перестроенное представление исходного массива, с сохранением всех возможных значений entry_id, то для этого мы можем воспользоваться функцией array_reduce():
$dst = array_reduce($src, function($acc, $item) {
$acc[$item['entry_id']][$item['element_id']] = $item['value'];
return $acc;
}, []); // начинаем с пустого массива как начального значения результата
print_r($dst);
Пример вывода:
Array
(
[3] => Array
(
[4] => a:2:{i:2;s:14:"Имя";i:4;s:18:"Фамилия";}
[6] => Тестовый отзыв
[11] => a:1:{i:2;s:16:"г. Москва";}
[12] => блаблабла
)
[4] => Array
(
[12] => блинблинблин
)
)
Общий ответ на этот вопрос дал Ivan Shatsky (всегда надо помнить, что хороший ответ должен быть более общим, чтобы в первую очередь быть полезным всем тем, кто придёт в поисках ответа на такой же вопрос) - array_column() с дополнительным параметром для ключа прекрасно справляется с задачей.
Если же говорить про конкретную проблему автора вопроса, которая кроме завяленной группировки включает ещё и другие элементарные задачи - такие как сопоставление числовых индексов строковым, возможное наличие разных entry_id, необходимость распаковки сериализованного массива и превращение этого массива в строку - то можно посоветовать вот такой простой код, без заморочек и выдуманных условий:
<?php
// Справочник строковых значений для element_id
$fields=[4=>'value', 6=>'feedback', 11=>'city'];
// Справочник для element_id, которым не нужно делать unserialize
$text_fields = [6];
// массив надо всегда надо инициализировать перед использованием
$dst = [];
foreach($src as $row){
$field = $fields[$row['element_id']];
if (in_array($row['element_id'], $text_fields)) {
$dst[$row['entry_id']][$field] = $row['value'];
} else {
$dst[$row['entry_id']][$field] = implode(" ", unserialize($row['value']));
}
}
Здесь мы перебираем массив в цикле, и группируем результат так, чтобы для каждого entry_id собирался массив соответствующих значений.
Для корректной работы этого кода необходимо указать строковые значения для всех возможных element_id, а так же указать, для каких element_id не нужно применять unserialize().