php - 将数据从数据库转换为分层数组

时间:2014-02-20 08:22:28

标签: php arrays hierarchical-data

这几天我一直在困惑这个问题,没有运气。我希望你们中的一些人能提供帮助。 从我的数据库中我得到一个文件列表,其中附有各种信息,包括虚拟路径。一些典型的数据是:

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,而键是目录名称。

4 个答案:

答案 0 :(得分:1)

功能

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.'

答案 1 :(得分:0)

有这样的事情:

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
        )

)

我知道这不是你想要的,但我认为这可以让你朝着正确的方向前进。

答案 2 :(得分:0)

事件虽然你在发布之前应该尝试过一些东西,但我喜欢你的问题并认为这是一个有趣的问题。所以,你去吧

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);

提供您想要的确切输出

答案 3 :(得分:0)

这是使用2个递归函数执行此操作的简单方法。 一个解析一行数据的函数。 另一个合并每个解析的数据行。

// 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;
}