php – преобразование данных из базы данных в иерархический массив

Я задумывался над этой проблемой в течение нескольких дней, без везения. Надеюсь, некоторые из вас помогут. Из моей базы данных я получаю список файлов, к которым прилагается вся информация, включая виртуальный путь. Некоторые типичные данные:

Array ( [0] => Array ( [name] => guide_to_printing.txt [virtual_path] => guides/it ) [1] => Array ( [name] => guide_to_vpn.txt [virtual_path] => guides/it ) [2] => Array ( [name] => for_new_employees.txt [virtual_path] => guides ) ) 

Я хочу преобразовать это в иерархическую структуру массива из виртуальных путей, поэтому вывод выше должен быть:

 Array ( [0] => Array ( [type] => dir [name] => guides [children] => Array ( [0] => Array ( [type] => dir [name] => it [children] = Array ( [0] => Array ( [type] => file [name] => guide_to_printing.txt ) [1] => Array ( [type] => file [name] => guide_to_vpn.txt ) ) ) [1] => Array ( [type] => file [name] => for_new_employees.txt ) ) ) ) 

Если свойство type указывает, является ли это каталогом или файлом.

Может кто-то помочь с созданием функции, которая делает это преобразование. Это будет очень полезно. Благодарю.

Мое собственное лучшее решение пока:

 foreach($docs as $doc) { $path = explode("/",$doc['virtual_path']); $arrayToInsert = array( 'name' => $doc['name'], 'path' => $doc['virtual_path'], ); if(count($path)==1) { $r[$path[0]][] = $arrayToInsert; } if(count($path)==2) { $r[$path[0]][$path[1]][] = $arrayToInsert; } if(count($path)==3) { $r[$path[0]][$path[1]][$path[2]][] = $arrayToInsert; } } 

Конечно, это работает только для глубины 3 в структуре каталогов, а ключи – имена каталогов.

функция

 function hierarchify(array $files) { /* prepare root node */ $root = new stdClass; $root->children = array(); /* file iteration */ foreach ($files as $file) { /* argument validation */ switch (true) { case !isset($file['name'], $file['virtual_path']): case !is_string($name = $file['name']): case !is_string($virtual_path = $file['virtual_path']): throw new InvalidArgumentException('invalid array structure detected.'); case strpos($virtual_path, '/') === 0: throw new InvalidArgumentException('absolute path is not allowed.'); } /* virtual url normalization */ $parts = array(); $segments = explode('/', preg_replace('@/++@', '/', $virtual_path)); foreach ($segments as $segment) { if ($segment === '.') { continue; } if (null === $tail = array_pop($parts)) { $parts[] = $segment; } elseif ($segment === '..') { if ($tail === '..') { $parts[] = $tail; } if ($tail === '..' or $tail === '') { $parts[] = $segment; } } else { $parts[] = $tail; $parts[] = $segment; } } if ('' !== $tail = array_pop($parts)) { // skip empty $parts[] = $tail; } if (reset($parts) === '..') { // invalid upper traversal throw new InvalidArgumentException('invalid upper traversal detected.'); } $currents = &$root->children; /* hierarchy iteration */ foreach ($parts as $part) { while (true) { foreach ($currents as $current) { if ($current->type === 'dir' and $current->name === $part) { // directory already exists! $currents = &$current->children; break 2; } } // create new directory... $currents[] = $new = new stdClass; $new->type = 'dir'; $new->name = $part; $new->children = array(); $currents = &$new->children; break; } } // create new file... $currents[] = $new = new stdClass; $new->type = 'file'; $new->name = $name; } /* convert into array completely */ return json_decode(json_encode($root->children), true); } 

пример

Дело 1:

 $files = array( 0 => array ( 'name' => 'b.txt', 'virtual_path' => 'A/B//', ), 1 => array( 'name' => 'a.txt', 'virtual_path' => '././A/B/C/../..', ), 2 => array( 'name' => 'c.txt', 'virtual_path' => './A/../A/B/C//////', ), 3 => array( 'name' => 'root.txt', 'virtual_path' => '', ), ); var_dump(hierarchify($files)); 

выйдет …

 array(2) { [0]=> array(3) { ["type"]=> string(3) "dir" ["name"]=> string(1) "A" ["children"]=> array(2) { [0]=> array(3) { ["type"]=> string(3) "dir" ["name"]=> string(1) "B" ["children"]=> array(2) { [0]=> array(2) { ["type"]=> string(4) "file" ["name"]=> string(5) "b.txt" } [1]=> array(3) { ["type"]=> string(3) "dir" ["name"]=> string(1) "C" ["children"]=> array(1) { [0]=> array(2) { ["type"]=> string(4) "file" ["name"]=> string(5) "c.txt" } } } } } [1]=> array(2) { ["type"]=> string(4) "file" ["name"]=> string(5) "a.txt" } } } [1]=> array(2) { ["type"]=> string(4) "file" ["name"]=> string(8) "root.txt" } } 

Случай 2:

 $files = array( 0 => array ( 'name' => 'invalid.txt', 'virtual_path' => '/A/B/C', ), ); var_dump(hierarchify($files)); 

будет бросать …

 Fatal error: Uncaught exception 'InvalidArgumentException' with message 'absolute path is not allowed.' 

Случай 3:

 $files = array( 0 => array ( 'name' => 'invalid.txt', 'virtual_path' => 'A/B/C/../../../../../../../..', ), ); var_dump(hierarchify($files)); 

будет бросать …

 Fatal error: Uncaught exception 'InvalidArgumentException' with message 'invalid upper traversal detected.' 

Что-то вроде этого:

 foreach ($array as $k => $v) { $tmp = explode('/',$v['virtual_path']); if(sizeof($tmp) > 1){ $array_result[$tmp[0]]['children'][$k]['type'] = 'file'; $array_result[$tmp[0]]['children'][$k]['name'] = $v['name']; $array_result[$tmp[0]]['type'] = 'dir'; $array_result[$tmp[0]]['name'] = $v['name']; } } 

Я получаю такой массив:

 Array ( [guides] => Array ( [children] => Array ( [0] => Array ( [type] => file [name] => guide_to_printing.txt ) [1] => Array ( [type] => file [name] => guide_to_vpn.txt ) ) [type] => dir [name] => guide_to_vpn.txt ) ) 

Я знаю, что это не совсем то, что вы хотите, но я думаю, что это может привести вас в правильном направлении.

Событие, хотя вы должны были что-то попробовать перед тем, как публиковать здесь, мне нравится ваш вопрос и думаю, что это весело. Итак, здесь вы идете

 function &createVirtualDirectory(&$structure, $path) { $key_parts = $path ? explode('/', $path) : null; $last_key = &$structure; if (is_array($key_parts) && !empty($key_parts)) { foreach ($key_parts as $name) { // maybe directory exists? $index = null; if (is_array($last_key) && !empty($last_key)) { foreach ($last_key as $key => $item) { if ($item['type'] == 'dir' && $item['name'] == $name) { $index = $key; break; } } } // if directory not exists - create one if (is_null($index)) { $last_key[] = array( 'type' => 'dir', 'name' => $name, 'children' => array(), ); $index = count($last_key)-1; } $last_key =& $last_key[$index]['children']; } } return $last_key; } $input = array( 0 => array ( 'name' => 'guide_to_printing.txt', 'virtual_path' => 'guides/it', ), 1 => array( 'name' => 'guide_to_vpn.txt', 'virtual_path' => 'guides/it', ), 2 => array( 'name' => 'for_new_employees.txt', 'virtual_path' => 'guides', ) ); $output = array(); foreach ($input as $file) { $dir =& createVirtualDirectory($output, $file['virtual_path']); $dir[] = array( 'type' => 'file', 'name' => $file['name'] ); unset($dir); } print_r($output); с function &createVirtualDirectory(&$structure, $path) { $key_parts = $path ? explode('/', $path) : null; $last_key = &$structure; if (is_array($key_parts) && !empty($key_parts)) { foreach ($key_parts as $name) { // maybe directory exists? $index = null; if (is_array($last_key) && !empty($last_key)) { foreach ($last_key as $key => $item) { if ($item['type'] == 'dir' && $item['name'] == $name) { $index = $key; break; } } } // if directory not exists - create one if (is_null($index)) { $last_key[] = array( 'type' => 'dir', 'name' => $name, 'children' => array(), ); $index = count($last_key)-1; } $last_key =& $last_key[$index]['children']; } } return $last_key; } $input = array( 0 => array ( 'name' => 'guide_to_printing.txt', 'virtual_path' => 'guides/it', ), 1 => array( 'name' => 'guide_to_vpn.txt', 'virtual_path' => 'guides/it', ), 2 => array( 'name' => 'for_new_employees.txt', 'virtual_path' => 'guides', ) ); $output = array(); foreach ($input as $file) { $dir =& createVirtualDirectory($output, $file['virtual_path']); $dir[] = array( 'type' => 'file', 'name' => $file['name'] ); unset($dir); } print_r($output); 

Обеспечивает точный результат, который вы хотите

Вот простой способ сделать это с помощью двух рекурсивных функций. Одна функция для анализа одной строки данных. Другой – объединить каждую анализируемую строку данных.

 // Assuming your data are in $data $tree = array(); foreach ($data as $item) { $tree = merge($tree, parse($item['name'], $item['virtual_path'])); } print json_encode($tree); // Simple parser to extract data function parse($name, $path){ $parts = explode('/', $path); $level = array( 'type' => 'dir', 'name' => $parts[0], 'children' => array() ); if(count($parts) > 1){ $path = str_replace($parts[0] . '/', '', $path); $level['children'][] = parse($name, $path); } else { $level['children'][] = array( 'type' => 'file', 'name' => $name ); } return $level; } // Merge a new item to the current tree function merge($tree, $new_item){ if(!$tree){ $tree[] = $new_item; return $tree; } $found = false; foreach($tree as $key => &$item) { if($item['type'] === $new_item['type'] && $item['name'] === $new_item['name']){ $item['children'] = merge($item['children'], $new_item['children'][0]); $found = true; break; } } if(!$found) { $tree[] = $new_item; } return $tree; }