PHP recursive folder scan into multi array (subfolders and files) - php

I'm a little bit lost here at the moment. My goal is to recursive scan a folder with subfolders and images in each subfolder, to get it into a multidimensional array and then to be able to parse each subfolder with its containing images.
I have the following starting code which is basically scanning each subfolders containing files and just lost to get it into a multi array now.
$dir = 'data/uploads/farbmuster';
$results = array();
if(is_dir($dir)) {
$iterator = new RecursiveDirectoryIterator($dir);
foreach(new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::CHILD_FIRST) as $file) {
if($file->isFile()) {
$thispath = str_replace('\\','/',$file->getPath());
$thisfile = utf8_encode($file->getFilename());
$results[] = 'path: ' . $thispath. ', filename: ' . $thisfile;
}
}
}
Can someone help me with this?
Thanks in advance!

You can try
$dir = 'test/';
$results = array();
if (is_dir($dir)) {
$iterator = new RecursiveDirectoryIterator($dir);
foreach ( new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::CHILD_FIRST) as $file ) {
if ($file->isFile()) {
$thispath = str_replace('\\', '/', $file);
$thisfile = utf8_encode($file->getFilename());
$results = array_merge_recursive($results, pathToArray($thispath));
}
}
}
echo "<pre>";
print_r($results);
Output
Array
(
[test] => Array
(
[css] => Array
(
[0] => a.css
[1] => b.css
[2] => c.css
[3] => css.php
[4] => css.run.php
)
[CSV] => Array
(
[0] => abc.csv
)
[image] => Array
(
[0] => a.jpg
[1] => ab.jpg
[2] => a_rgb_0.jpg
[3] => a_rgb_1.jpg
[4] => a_rgb_2.jpg
[5] => f.jpg
)
[img] => Array
(
[users] => Array
(
[0] => a.jpg
[1] => a_rgb_0.jpg
)
)
)
Function Used
function pathToArray($path , $separator = '/') {
if (($pos = strpos($path, $separator)) === false) {
return array($path);
}
return array(substr($path, 0, $pos) => pathToArray(substr($path, $pos + 1)));
}

RecursiveDirectoryIterator scans recursively into a flat structure. To create a deep structure you need a recursive function (calls itself) using DirectoryIterator. And if your current file isDir() and !isDot() go deep on it by calling the function again with the new directory as arguments. And append the new array to your current set.
If you can't handle this holler and I'll dump some code here. Have to document (has ninja comments right now) it a bit so... trying my luck the lazy way, with instructions.
CODE:
/**
* List files and folders inside a directory into a deep array.
*
* #param string $Path
* #return array/null
*/
function EnumFiles($Path){
// Validate argument
if(!is_string($Path) or !strlen($Path = trim($Path))){
trigger_error('$Path must be a non-empty trimmed string.', E_USER_WARNING);
return null;
}
// If we get a file as argument, resolve its folder
if(!is_dir($Path) and is_file($Path)){
$Path = dirname($Path);
}
// Validate folder-ness
if(!is_dir($Path) or !($Path = realpath($Path))){
trigger_error('$Path must be an existing directory.', E_USER_WARNING);
return null;
}
// Store initial Path for relative Paths (second argument is reserved)
$RootPath = (func_num_args() > 1) ? func_get_arg(1) : $Path;
$RootPathLen = strlen($RootPath);
// Prepare the array of files
$Files = array();
$Iterator = new DirectoryIterator($Path);
foreach($Iterator as /** #var \SplFileInfo */ $File){
if($File->isDot()) continue; // Skip . and ..
if($File->isLink() or (!$File->isDir() and !$File->isFile())) continue; // Skip links & other stuff
$FilePath = $File->getPathname();
$RelativePath = str_replace('\\', '/', substr($FilePath, $RootPathLen));
$Files[$RelativePath] = $FilePath; // Files are string
if(!$File->isDir()) continue;
// Calls itself recursively [regardless of name :)]
$SubFiles = call_user_func(__FUNCTION__, $FilePath, $RootPath);
$Files[$RelativePath] = $SubFiles; // Folders are arrays
}
return $Files; // Return the tree
}
Test its output and figure it out :) You can do it!

If you want to get list of files with subdirectories, then use (but change the foldername)
<?php
$path = realpath('yourfold/samplefolder');
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)) as $filename)
{
echo "$filename\n";
}
?>

Related

Getting a list of sub-directories form an array of paths based on current path

I want to get an array of immediate sub-directories based on the current path from a given list of paths. Here is an example list of paths and the desired result:
$dirs = [
'/chris',
'/chris/usa',
'/david',
'/',
'/chris/canada/2022',
'/david/uk',
];
$current_path = "/chris";
// Required result:
$sub_dirs = ['/usa', '/canada'];
The current path could be anything from / to the last sub directory, in which case I would end up with an empty $sub_dirs.
My best attempt at it is this:
$dirs = [
'/chris',
'/chris/usa',
'/david',
'/',
'/chris/canada/2022',
'/david/uk',
];
$sub_dirs = [];
$current_path = "/";
foreach($dirs as $dir){
if(strstr($dir, $current_path)){
$sub_dirs[] = str_replace($current_path, '', $dir);
}
}
The above fails at two things. If the $current_path = "/" then the returned array is just paths without slashes since I strip them out with strstr() and if the $current_path = "/chris" then I still get the sub-directories.
How should I correctly solve it?
Thank you!
You can search for /chris/ to get only sub directories.
To get only the first directory of matching paths, you could use explode(), and get the first.
As of PHP8, you can use str_starts_with()
$dirs = ['/chris', '/chris/usa', '/david', '/', '/chris/canada/2022', '/david/uk'];
$current_path = "/";
$sub_dirs = [];
foreach($dirs as $dir)
{
// Ensure $dir starts with path + '/' (sub dir of $current_path)
if (!str_starts_with($dir, $current_path)) {
continue;
}
// Get the relative path from $current_path
$relPath = substr($dir, strlen($current_path));
// Get the first dir of this path
[$dir2] = explode('/', ltrim($relPath,'/'));
// skip same directory
if (empty($dir2)) {
continue;
}
// Store and add removed slash
$sub_dirs[] = '/' . $dir2;
}
var_export(array_unique($sub_dirs));
Output
array (
0 => '/usa',
1 => '/canada',
)
I like the approach of creating a tree out of the paths (adapted from this answer).
Then it's easier to get the children based on a path.
function buildTree($paths)
{
// Create a hierarchy where keys are the labels
$rootChildren = [];
foreach ($paths as $path) {
$branch = explode("/", $path);
$children = &$rootChildren;
foreach ($branch as $label) {
if ($label) {
if (!isset($children[$label])) {
$children[$label] = [];
}
$children = &$children[$label];
}
}
}
// Create target structure from that hierarchy
function recur($children)
{
$result = [];
foreach ($children as $label => $grandchildren) {
$result[$label] = recur($grandchildren);
}
return $result;
}
return recur($rootChildren);
}
function findChildren($tree, $path)
{
$branch = explode("/", $path);
$pointer = $tree;
foreach ($branch as $label) {
if ($label) {
$pointer = $pointer[$label];
}
}
return array_keys($pointer);
}
$dirs = [
'/chris',
'/chris/usa',
'/david',
'/',
'/chris/canada/2022',
'/david/uk',
];
$tree = buildTree($dirs);
print_r($tree);
$current_path = "/chris";
print_r(findChildren($tree, $current_path));
Output:
Array
(
[chris] => Array
(
[usa] => Array
(
)
[canada] => Array
(
[2022] => Array
(
)
)
)
[david] => Array
(
[uk] => Array
(
)
)
)
Array
(
[0] => usa
[1] => canada
)
I feel like I have a bit shorter answer than the previous ones, but not sure if it's the most efficient one though.
$dirs = [
'/chris',
'/chris/usa',
'/david',
'/',
'/chris/canada/2022',
'/david/uk',
];
$current_path = "/chris";
$sub_dirs = array_map(function($s) { return '/'.explode("/", $s)[2]; },array_filter($dirs, function($dir) use ($current_path) {
return substr($dir, 0, strlen($current_path) + 1) === $current_path . '/';
}));
print_r($sub_dirs); // Output: Array ( [1] => /usa [4] => /canada )

PHP - Create multidimensional recursive array from string (Files/folders structure)

Someone asked same question 10 years ago but there is no answer to that
Reference: Trying to create Array from String (file/folder structure)
Update Jan 8, 2022: This is how array and tree should look like:
https://3v4l.org/6ULBZ#v8.1.1
The SQL output should be formed into this array structure from a string.
[
'id' => $key //Increment from foreach loop,
'file' => $row['name'] //Name of folders and files
'parent_id' => 0 // 0 - if no subfolder, $id if there is subfolder
]
I want to return a tree-level multidimensional array from file list stored in database as a string.
For example I store my work related path in database like this:
SELECT name FROM files;
... SQL Output
2021/Dec/File1.doc
2021/Dec/File2.doc
2021/Dec/File3.doc
2021/Nov/File1.doc
2021/Nov/File2.doc
2021/Nov/File3.doc
2021/Nov/File4.doc
2020/Jan/File1.doc
2020/Jan/File2.doc
2020/Jan/File3.doc
... PHP recursive array output should be categorized by folders, subfolders, more subfolders if exists and files on end.
-2021
--Dec
---File1.doc
---File2.doc
---File3.doc
--Nov
---File1.doc
---File2.doc
---File3.doc
---File4.doc
-2020
--Jan
---File1.doc
---File2.doc
---File3.doc
What would be the best way to achieve this performance wise?
What I got so far...
$files = [];
foreach ($sql as $row)
{
// Separate directories
$separator = explode('/', $row['name']);
/* Output:
Array
(
[0] => 2021
[1] => Dec
[2] => file1.doc
)
*/
// Find the file via regex
if (preg_match('/[^\/]*\.(doc|txt)/', $row['name'], $o))
{
$row['name'] = $o[0]; //Output: file1.doc
}
$files[] = $row;
}
... For now I only have a file names, now I need directories as well and then make a multidimensional array from it.
$files = [];
foreach ($sql as $row)
{
// Separate directories
$separator = explode('/', $row['name']);
/* Output:
Array
(
[0] => 2021
[1] => Dec
[2] => file1.doc
)
*/
if (preg_match('/[^\/]*\.(doc|txt)/', $row['name']))
{
$node = &$files;
while (count($separator) > 1) {
$folder = array_shift($separator);
if (!array_key_exists($folder, $node)) {
$node[$folder] = [];
}
$node = &$node[$folder];
}
$node[] = $separator[0];
}
}
I have one solution for you, I hope this will help.
$files="2021/Dec/File1.doc,
2021/Dec/File2.doc,
2021/Dec/File3.doc,
2021/Nov/File1.doc,
2021/Nov/File2.doc,
2021/Nov/File3.doc,
2021/Nov/File4.doc,
2020/Jan/File1.doc,
2020/Jan/File2.doc,
2020/Jan/File3.doc";
$files=explode(',',$files);
foreach($files as $file)
{
$i=1;
$paths=explode('/',$file);
foreach($paths as $path)
{
for($j=0;$j<$i;++$j){
echo '-';
}
echo $path;
echo "<br/>";
++$i;
}
$i=0;
}
You can use your database files. by removing my files constant value and use your.

get entire folders/files tree and rename

I have a folders/files tree inside admin folder (windows, localhost).
All files are .html.
Each of them (files and folders) is starting with some numbers and middle dash, for example
32-somefolder
624-somefile.html
I need to list all of them and remove all prefixes from their names.
So the result should be:
somefolder
somefile.html
foreach(glob("admin/*") as $el) {
echo $el . '.' . filetype($el) . '<br>';
}
First problem - only folders are listed:
admin/32-somefolder.dir
How to get files too, and how to rename i.e. remove prefixes from all the names?
You can use the second choice to list files : scandir, and recursive function :
function removePrefixFiles($dir, &$results = array()){
$files = scandir($dir);
foreach ($files as $key => $value){
$path = realpath($dir . DIRECTORY_SEPARATOR . $value);
if (! is_dir($path)) {
// treat the filename
$file = pathinfo($path);
$filename = explode('-', $file['filename']);
if (count($filename) > 0) {
// '-' is found, rename file
rename($path, $file['dirname'] .'/'. $filename[1] .'.'. $file['extension'];
}
$results[] = $path;
} else if ($value != '.' && $value != '..') {
removePrefixFiles($path, $results);
$results[] = $path;
}
}
// no real need to return something here, but can log the files
return $results;
}
$dir = '/admin';
removePrefixFiles($dir);
I have created two folder inside admin/ name as
1-files and 2-abc
then inside folder 1-files i have two files
11-java.html
11-text.html
then inside folder 2-abc i have two files
22-php.html
22-sql.html
<?php
$dir = "admin/";
// Sort in ascending order - this is default
$a = scandir($dir);
echo "<pre>";
if(count($a)>0){
$newArr = array();
for($i=2;$i<count($a);$i++){
$test = array();
$folderArr = array();
$folderName = explode('-',$a[$i]);
$test['folder'] = $folderName[1];
$b = scandir($dir.'/'.$a[$i]);
for($j=2;$j<count($b);$j++){
$fileName = explode('-',$b[$j]);
$folderArr[] = substr($fileName[1], 0, strpos($fileName[1], "."));;
}
$test['files'] = $folderArr;
$newArr[] = $test;
}
}
print_r($newArr);
?>
This will be the output
Array
(
[0] => Array
(
[folder] => files
[files] => Array
(
[0] => java
[1] => text
)
)
[1] => Array
(
[folder] => abc
[files] => Array
(
[0] => php
[1] => sql
)
)
)
Hope this willl hellp you.

How to detect changes in directories or file inside that directory using php

I have directory structure like this:
[Products] => Array
(
[Category1] => Array
(
[product1] => Array
(
[documents] => Array
(
[0] => Attachment1.pdf
[1] => Attachment2.pdf
)
)
)
)
if there is any changes in any directory or file inside products directory
like:
product1 changed to product2
new directory added
any changes in file Attachment.pdf
new document added to directory.
What i have tried till now:
$dir="PATH TO PRODUCT DIRECTORY"
$stats = stat($dir);
but $stats['mtime'] detected change in Products children only (Like if change Category1 to Category2 or newly added directory to products) but not in grand children of products (Like if i change product1 to product2) and so on.
Can any one help me to delect any changes in product with children.
Here is my solution i have implemented. By using these function i am able to get a MD5 hash code and i can use this hash code to check if there is any changes in directory structure or in any file.
$dir="PATH TO PRODUCT DIRECTORY";
public function directoryHash($dir, $item_name){
$dirStr=$this->directoryStruture($dir, $separator= DIRECTORY_SEPARATOR, $root='');
$strHash=md5(json_encode($dirStr));
return $strHash;
}
public function directoryStruture($dir, $separator = DIRECTORY_SEPARATOR, $root = '') {
$result = array();
$cdir = scandir($dir);
$result = array();
if ($root === '') {
$root = $dir;
}
$cdir = scandir($dir);
foreach ($cdir as $key => $value) {
if (!in_array($value, array(".", ".."))) {
$current = $dir . $separator . $value;
if (is_dir($current)) {
$result['items'][] = $value;
$result[$value] = $this->directoryStruture($current, $separator, $root);
} else {
$filePath = str_replace($root, '', $current);
$result[] = $filePath.filemtime($current);
}
}
}
return $result;
}
If there is any better approach. Please let me know.
Thanks,
M.

Get specific number of files from a directory ordered by time modified

I'm trying to write a function that returns and array of the files names ordered by time modified.
Although I want to get only a specific number of files, and not the whole files in the directory.
In conclusion, I'd like to get an array that contains the newest X files from a directory.
This is my code:
public static function GetPicsDir()
{
$results = array();
$handler = opendir("pics");
while ($file = readdir($handler)) {
if ($file != "." && $file != "..") {
$results[] = $file;
}
}
closedir($handler);
return $results;
}
I don't know how to limit it and order by time modified.
I'd be glad to get any help.
Thank you
Use filemtime():
public static function GetPicsDir()
{
$results = array();
$handler = opendir("pics");
while ($file = readdir($handler)) {
if ($file != "." && $file != "..") {
$results[$time] = filemtime($file);
}
}
arsort($results);
closedir($handler);
return $results;
}
You can use glob
$date = strtotime('2013-01-10 10:00:00');//The date from you want to get the files
$matches = glob('dir/*.*');
$result=array();
if (is_array($matches)) {
$a=0;
foreach ($matches as $filename) {
if (filemtime($filename) >= $date) {//only output file >= your $date
$result[$a]['FileName'] = $filename;
$result[$a]['DateCreated'] = gmdate("Y-m-d H:i:s", filemtime($filename));
}
$a++;
}
}
if(count($result)>=2){//order array if it has at least 1 match
foreach ($result as $key => $row) {
$new_array[$key] = $row['DateCreated'];
}
array_multisort($new_array,SORT_DESC,$result);//Show most recent first
}
using array_multisort to sort by dates SORT_DESC
echo '<pre>';
print_r($result);
echo '<pre>';
Output:
Array
(
[0] => Array
(
[FileName] => test.php
[DateCreated] => 2013-10-20 05:43:06
)
[1] => Array
(
[FileName] => test.sql
[DateCreated] => 2013-09-20 23:38:05
)
[2] => Array
(
[FileName] => general.php
[DateCreated] => 2013-09-02 00:58:33
)
)
If you only want to sort the files by last modified date, you can use
ftp_nlist($conn, '-t .');
This will not tell you what the date for each file is, though.
If you want to get the modified date as well, you can use ftp_rawlist and parse the output. Here's a quick example I scraped together:
$list = ftp_rawlist($ftp, '.');
$results = array();
foreach ($list as $line) {
list($perms, $links, $user, $group, $size, $d1, $d2, $d3, $name) =
preg_split('/\s+/', $line, 9);
$stamp = strtotime(implode(' ', array($d1, $d2, $d3)));
$results[] = array('name' => $name, 'timestamp' => $stamp);
}
usort($results, function($a, $b) { return $a['timestamp'] - $b['timestamp']; });
At this point $results contains a list sorted in ascending last modified time; reverse the sort function to get the list in most recently modified first format.

Categories