Array Map of Multidimensional Arrays
I am trying to get a directory structure in a multidimensional array.
I've come this far:
function dirtree($dir, $regex = '', $ignoreEmpty = false)
{
if (!$dir instanceof DirectoryIterator) {
$dir = new DirectoryIterator((string) $dir);
}
$dirs = array();
$files = array();
foreach ($dir as $node) {
if ($node->isDir() && !$node->isDot()) {
$tree = dirtree($node->getPathname(), $regex, $ignoreEmpty);
if (!$ignoreEmpty || count($tree)) {
$dirs[$node->getFilename()] = $tree;
}
} elseif ($node->isFile()) {
$name = $node->getFilename();
if ('' == $regex || preg_match($regex, $name)) {
$files[] = $name;
}
}
}
asort($dirs);
sort($files);
return array_merge($dirs, $files);
}
But I am having trouble getting the folder name instead of the index 0,1.etc. Is it because my directories have numeric names?
Array
(
[0] => Array // 0 should be the folder name
(
[0] => m_109225488_1.jpg
[1] => t_109225488_1.jpg
)
[1] => Array
(
[0] => m_252543961_1.jpg
[1] => t_252543961_1.jpg
)
source to share
The solution was quite simple thanks to: Concatenate array without index of loss key
Instead, array_merge
just do$dirs + $files
Potential solution (potential problem noted by Roger G.):
function dirtree($dir, $regex = '', $ignoreEmpty = false)
{
if (!$dir instanceof DirectoryIterator) {
$dir = new DirectoryIterator((string) $dir);
}
$dirs = array();
$files = array();
foreach ($dir as $node) {
if ($node->isDir() && !$node->isDot()) {
$tree = dirtree($node->getPathname(), $regex, $ignoreEmpty);
if (!$ignoreEmpty || count($tree)) {
$dirs[$node->getFilename()] = $tree;
}
} elseif ($node->isFile()) {
$name = $node->getFilename();
if ('' == $regex || preg_match($regex, $name)) {
$files[] = $name;
}
}
}
return $dirs + $files;
}
The best decision?
function dirtree($dir, $regex = '', $ignoreEmpty = false)
{
if (!$dir instanceof DirectoryIterator) {
$dir = new DirectoryIterator((string) $dir);
}
$filedata = array();
foreach ($dir as $node) {
if ($node->isDir() && !$node->isDot()) {
$tree = dirtree($node->getPathname(), $regex, $ignoreEmpty);
if (!$ignoreEmpty || count($tree)) {
$filedata[$node->getFilename()] = $tree;
}
} elseif ($node->isFile()) {
$name = $node->getFilename();
if ('' == $regex || preg_match($regex, $name)) {
$filedata[] = $name;
}
}
}
return $filedata;
}
source to share
Using the array join operation is dangerous because you could overwrite existing files. Consider the following directory structure:
a <-- directory
├── 0 <-- directory (empty)
├── b <-- regular file
└── c <-- directory
└── d <-- regular file
Now let's look at starting an operation using array concatenation. I am getting the following output:
array(2) {
[0]=>
array(0) {
}
["c"]=>
array(1) {
[0]=>
string(1) "d"
}
}
Notice that the regular file is b
missing? This is due to the fact that the merge operation array prefers existing index 0
on an index 0
of right operand (which contains normal files).
I would stick with the original implementation as given in the question, or use a dedicated bucket for files that do not contain a valid filesystem name (e.g. :files:
). Please note that this may be specific to the platform that you choose.
In the case of the original implementation, you can decide whether the index is a directory versus a regular file by calling is_array
or is_scalar
for value. Please note, since the directory array is the first parameter array_merge
, you are guaranteed that directory indices will not grow and will always refer to the correct directory names.
You can only define directory names here:
function getDirectoryNames($result) {
$ds = [];
foreach ($result as $key => $value) {
if (is_array($value)) {
$ds[] = $key;
}
}
return $ds;
}
source to share
What you are looking for is ksort instead of asort.
<html>
<body>
<?php
function dirtree($dir, $regex = '', $ignoreEmpty = false)
{
if (!$dir instanceof DirectoryIterator) {
$dir = new DirectoryIterator((string) $dir);
}
$dirs = array();
$files = array();
foreach ($dir as $node) {
if ($node->isDir() && !$node->isDot()) {
$tree = dirtree($node->getPathname(), $regex, $ignoreEmpty);
if (!$ignoreEmpty || count($tree)) {
$dirs[$node->getFilename()] = $tree;
}
} elseif ($node->isFile()) {
$name = $node->getFilename();
if ('' == $regex || preg_match($regex, $name)) {
$files[] = $name;
}
}
}
ksort($dirs);
sort($files);
return array_merge($dirs, $files);
}
?>
<body>
<pre>
<?=var_dump(dirtree(getcwd());?>
</pre>
</body>
</html>
This will do your job. But as mentioned, the best solution would be to separate directories and files like this:
<html>
<body>
<?php
class DirNode {
public $name;
public $dirs=[];
public $files=[];
public function DirNode($dirName) {
$this->name = $dirName;
}
public function printDir($prefix="") {
echo($prefix.$this->name."\n");
foreach($this->dirs as $dir=>$subDir) {
echo($prefix.$dir."\n");
$subDir->printDir($prefix." ");
echo("\n");
}
foreach($this->files as $file) {
echo($prefix.$file."\n");
}
}
}
function dirtree($dir, $regex = '', $ignoreEmpty = false)
{
if (!$dir instanceof DirectoryIterator) {
$dir = new DirectoryIterator((string) $dir);
}
$directory = new DirNode($dir);
foreach ($dir as $node) {
if ($node->isDir() && !$node->isDot()) {
$tree = dirtree($node->getPathname(), $regex, $ignoreEmpty);
if (!$ignoreEmpty || count($tree)) {
$directory->dirs[$node->getFilename()] = $tree;
}
} elseif ($node->isFile()) {
$name = $node->getFilename();
if ('' == $regex || preg_match($regex, $name)) {
$directory->files[] = $name;
}
}
}
ksort($directory->dirs);
sort($directory->files);
return $directory;
}
$dirfiles = dirtree(getcwd().'/..');
echo("<pre>");
echo($dirfiles->printDir());
echo("</pre>");
?>
</body>
</html>
source to share