-1

I´m trying from various paths to generate a dimensional array. For that, I´m using the function found here.

My code

function get_dir_content_to_array( string $dir_path, string $dir_filter = null, string $file_filter = null ) {
    if( is_dir_empty( $dir_path ) )
        return false;
    $output = array();
    $files = get_subdir_filtered( $dir_path, $dir_filter, $file_filter );
    if ( isset( $files ) ) {
        foreach ( $files as $name => $object ) {
            if ( $object->getFilename() !== "." && $object->getFilename() !== ".." ) {
                // Split by the delimiter.
                $delimiter       = "/";
                $name            = str_replace( "\\", $delimiter, $name );
                $relative_path   = str_replace( $dir_path, "", $name );
                $a_relative_path = explode( $delimiter, $relative_path );
                $path = [ array_pop( $a_relative_path ) ];
                foreach ( array_reverse( $a_relative_path ) as $pathPart ) {
                    $path = [ $pathPart => $path ];
                }

                // Add it to a temp list.
                $paths[] = $path;
            }
            $output = call_user_func_array( 'array_merge_recursive', $paths );

        }
    }
    return $output;
}

function get_subdir_filtered( $dir_path, $dir_filter, $file_filter ) {
    $path      = realpath( $dir_path );
    $directory = new \RecursiveDirectoryIterator( $path );
    $files = null;
    if ( ! empty( $dir_filter ) || ! empty( $file_filter ) ) {
        if ( ! empty( $dir_filter ) ) {
            $filter = new DirnameFilter( $directory, $dir_filter );
        }
        if ( ! empty( $file_filter ) ) {
            $filter = new FilenameFilter( $filter, $file_filter );
        }
        $files = new \RecursiveIteratorIterator( $filter );
    } else {
        $files = new \RecursiveIteratorIterator( $directory );
    }
    return $files;
}

class DirnameFilter extends FilesystemRegexFilter {
    // Filter directories against the regex
    public function accept() {
        return ( ! $this->isDir() || preg_match( $this->regex, $this->getFilename() ) );
    }
}

That works except when a folder is named "0"

How can I fixed that ? Why array_pop skip the value "0" even if it´s a string ?

J.BizMai
  • 2,621
  • 3
  • 25
  • 49

2 Answers2

2

When json_encode() encodes an array whose keys are sequential integers starting from 0, or the string equivalents of them, it produces a JSON array rather than an object.

You could use the JSON_FORCE_OBJECT flag, but this will then turn the arrays of filenames inside a folder into objects, which you don't want.

What you can do is use a PHP object instead of an array to represent a folder. json_encode() will encode this as an object, even if it has numeric properties.

I think this might do it:

function get_dir_content_to_array( string $dir_path, string $dir_filter = null, string $file_filter = null ) {
    if( is_dir_empty( $dir_path ) )
        return false;
    $output = array();
    $files = get_subdir_filtered( $dir_path, $dir_filter, $file_filter );
    if ( isset( $files ) ) {
        foreach ( $files as $name => $object ) {
            if ( $object->getFilename() !== "." && $object->getFilename() !== ".." ) {
                // Split by the delimiter.
                $delimiter       = "/";
                $name            = str_replace( "\\", $delimiter, $name );
                $relative_path   = str_replace( $dir_path, "", $name );
                $a_relative_path = explode( $delimiter, $relative_path );
                $path = [ array_pop( $a_relative_path ) ];
                foreach ( array_reverse( $a_relative_path ) as $pathPart ) {
                    $folder = new StdClass;
                    $folder->{$pathPart} = $path;
                    $path = $folder;
                }

                // Add it to a temp list.
                $paths[] = $path;
            }
            $output = call_user_func_array( 'array_merge_recursive', $paths);

        }
    }
    return $output;
}

I haven't tested it because I don't have the get_subdir_filtered() function. It's possible that array_merge_recursive won't do the correct merge of this. You might need to merge the objects, too. This tutorial contains an implementation of mergeObjectsRecursively that I think should be a useful substitute.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • I used `JSON_FORCE_OBJECT` but my problem is this following one : `/0/1/0/filename.zip` return this : `a:1:{i:0;a:1:{i:1;a:1:{i:0;a:1:{i:0;s:23:"filename.zip";}}}}` so with one file more inside the root, I got this : `{"0":"update.json","1":{"1":{"0":{"0":"filename.zip"}}}}` instead of `{"0":"update.json","1":{"0":{"1":{"0":"filename.zip"}}}}` – J.BizMai Dec 15 '18 at 00:04
  • 1
    You're mixing real arrays and associative arrays that should have "0"` as the key? That's tougher. – Barmar Dec 15 '18 at 00:06
  • Yes, it´s a little messy. I don´t know if I should modify `get_dir_content_to_array` and makes a different structure or array or choose the correct option for `json_encode`. – J.BizMai Dec 15 '18 at 00:08
  • If I do a hack : `$path = [ "_" . $pathPart => $path ];`, that works of course but with an undersore. – J.BizMai Dec 15 '18 at 00:17
  • That does not work for the moment. I added the full code to help to test it. I got this result : `O:8:"stdClass":1:{s:1:"0";O:8:"stdClass":1:{s:1:"1";O:8:"stdClass":1:{s:1:"0";O:8:"stdClass":1:{s:23:"filename.zip";a:1:{i:0;N;}}}}}` and with `json_encode` gives a `null` result. – J.BizMai Dec 15 '18 at 11:10
  • The problem seems happen during the merge with `array_merge_recursive` – J.BizMai Dec 15 '18 at 11:25
  • Did you read the part of the answer where I said that part might not work, and you need to use `mergeObjectRecursively` instead? – Barmar Dec 17 '18 at 16:12
  • ah ok, I did not see. I will try this. – J.BizMai Dec 17 '18 at 16:50
  • I thought it works, but in some cases, that does not work like if you have `0/1/file.zip` and `0/1/3/file.zip` the merge generates `O:8:"stdClass":2:{s:1:"0";O:8:"stdClass":1:{s:1:"1";s:32:"file_0.1.zip";}s:1:"1";O:8:"stdClass":1:{s:1:"1";O:8:"stdClass":1:{s:1:"3";s:35:"file_0.1.3.zip";}}}` The problem is always the same, when you try to cast the zero value as a string, the "0" becomes an integer after to cast the object as an array. If you do `$folder->{ "_" . $pathPart} = $path;`, that merges perfectly. – J.BizMai Dec 18 '18 at 23:40
0

In my case I got this path : update.json and 0/1/0/filename.zip

As @Barmar said, basically, the problem is when I try to convert an array to a json object.

In PHP, an array is in fact, all the time an object so... $arr = [ "0" => [ "1" => ... ] ] for php, is equal to : $arr = [ 0 => [ 1 => ... ] ]. In PHP 7.2.x, each time you will merge objects, The "0" as string key will be cast to 0 as int.

Consequences

1 . with json_encode

echo json_encode( get_dir_content_to_array(...) );

//result = ["update.json",{"1":[["filename.zip"]]}]

2 . with json_encode and JSON_FORCE_OBJECT

echo json_encode( get_dir_content_to_array(...), JSON_FORCE_OBJECT );

//result = {"0":"update.json","1":{"1":{"0":{"0":"filename.zip"}}}}
//I got 1,0,0,filename.zip instead of 0,1,0,filename.zip 

3 . Add (string)

 $path = [array_pop( $a_relative_path)];
 foreach ( array_reverse( $a_relative_path ) as $pathPart ) {
    $path = [ (string) $pathPart => $path ];
 }
 //Same result of 2.

To keep the right order for the path, I found for the moment a hacky solution :

Add underscore before each key

foreach ( array_reverse(  $a_relative_path ) as $pathPart ) {
    $path = [ "_" .  $a_relative_path => $path ];
}
//result = {"0":"update.json", "_0":{"_1":{"_0":{"0":"filenamezip"}}}} 

//With a new file path, I got this :
// {"0":"update.json","_toto":{"_foo":{"0":"test.txt"}},"_0":{"_1":{"_0":{"0":"filename.zip"}}}}

This solution is a hack, but I can make a difference between a kee which is a path "_0":{"_1":{"_0" and a key to index a file "0":"filename.zip"

J.BizMai
  • 2,621
  • 3
  • 25
  • 49
  • Your result has keys `"0"` and `"_0"`, something seems wrong with that. I think the problem may be that you're trying to mix indexed and keyed data in the same object. – Barmar Dec 17 '18 at 16:18
  • Yes, you´re all right. There is a mix indexed. That´s why I add an underscore to make the difference between the key which is a dirname "_0", "_1" and the 0,1,2... which is a common indexation. The other way could be to do a bigger object where the dirname is a value and keys could be "dir" or "file" or something like that. – J.BizMai Dec 17 '18 at 16:47