Recursive function scanning a folder and storing it in an array - php

I am trying to make a recursive function to go through all of the folder path that I have given it in the parameters.
What I am trying to do is to store the folder tree into an array for example I have Folder1 and this folder contains 4 text files and another folder and I want the structure to be a multidimensional array like the following
Array 1 = Folder one
Array 1 = text.text.....So on so forth
I have the following function that I build but its not working as I want it too. I know that I need to check whether it is in the root directory or not but when it becomes recursive it becoems harder
function displayAllFolders($root)
{
$foldersArray = array();
$listFolderFile = scandir($root);
foreach($listFolderFile as $row)
{
if($row == "." || $row == "..")
{
continue;
}
elseif(is_dir("$root/$row") == true)
{
$foldersArray["$root/$row"] = "$row";
$folder = "$root/$row";
#$foldersArray[] = displayAllFolders("$root/$row");
}
else
{
$foldersArray[]= array("$root/$row") ;
}
}
var_dump($foldersArray);
}

Using RecursiveDirectoryIterator with RecursiveIteratorIterator this becomes rather easy, e.g.:
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
// root dir
'.',
// ignore dots
RecursiveDirectoryIterator::SKIP_DOTS
),
// include directories
RecursiveIteratorIterator::SELF_FIRST
// default is:
// RecursiveIteratorIterator::LEAVES_ONLY
//
// which would only list files
);
foreach ($it as $entry) {
/* #var $entry \SplFileInfo */
echo $entry->getPathname(), "\n";
}

Your approach isn't recursive at all.
It would be recursive if you called the same function again in case of a directory. You only make one sweep.
Have a look here:
http://php.net/manual/en/function.scandir.php
A few solutions are posted. I would advise you to start with the usercomment by mmda dot nl.
(function is named dirToArray, exactly what you are tryting to do.)
In case it will be removed, I pasted it here:
function dirToArray($dir) {
$result = array();
$cdir = scandir($dir);
foreach ($cdir as $key => $value) {
if (!in_array($value,array(".",".."))) {
if (is_dir($dir . DIRECTORY_SEPARATOR . $value)) {
$result[$value] = dirToArray($dir . DIRECTORY_SEPARATOR . $value);
}
else {
$result[] = $value;
}
}
}
return $result;
}

Why not using PHP itself? Just have a look at the RecursiveDirectoryIterator of the standard php library (SPL).
$folders = [];
$iterator = new RecursiveDirectoryIterator(new RecursiveDirectoryIterator($directory));
iterator_apply($iterator, 'scanFolders', array($iterator, $folders));
function scanFolders($iterator, $folders) {
while ($iterator->valid()) {
if ($iterator->hasChildren()) {
scanFolders($iterator->getChildren(), $folders);
} else {
$folders[] = $iterator->current();
}
$iterator->next();
}
}

Related

PHP - Directory browsing from recursive to iterative

Hello I am trying to make the following function iterative. It browses threw all directories and gives me all files in there.
function getFilesFromDirectory($directory, &$results = array()){
$files = scandir($directory);
foreach($files as $key => $value){
$path = realpath($directory.DIRECTORY_SEPARATOR.$value);
if(!is_dir($path)) {
$results[] = $path;
} else if($value != "." && $value != "..") {
getFilesFromDirectory($path, $results);
$results[] = $path;
}
}
return $results;
}
I am sure that it is possible to make this function iterative but I really have no approach how I can do this.
Your going to want to use a few PHP base classes to implement this.
Using a RecursiveDirectoryIterator inside of a RecursiveIteratorIterator will allow you to iterate over everything within a directory regardless of how nested.
Its worth noting when looping over the $iterator below each $item is an object of type SplFileinfo. Information on this class can be found here: http://php.net/manual/en/class.splfileinfo.php
<?php
//Iterate over a directory and add the filenames of all found children
function getFilesFromDirectory($directory){
//Return an empty array if the directory could not be found
if(!is_dir($directory)){
return array();
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($directory)
);
$found = array();
foreach($iterator as $item){
if(method_exists($item, 'isFile') && $item->isFile()){
//Uncomment the below to exclude dot files
//if(method_exists($item, 'isDot') && $item->isDot()){
// continue;
//}
//Pathname == full file path
$found[] = $item->getPathname();
}
}
return $found;
}
An var_dump of some found files i did using this function as a test:
Hope this helps!

PHP look through every root directory for keyword

So I tried for some days to get this done. But I still don't have a single clue how i could build this code so it works. Maybe someone has an idea.
Goal: Automaticely iterate through the root-directory and any of it's subdirectories. If there's a directory, which matches the keyword it should be stored into an array.
I am only looking for directories, that's why there's a regex to exclude every object with a dot in it's name. Not perfect yet, but that's not a problem.
I ll post the first version of my code. Now it's just scanning the directory your handling to the function when calling it. Because all my other attempts are trash and this one at least works
searchformigration('/');
/* Check for related folders, that could be used for a migration */
function searchformigration($dir)
{
$scanned_elements = scandir($dir);
for($c = 0; $c <= (count($scanned_elements) - 1); $c++)
{
/* Anything but files containing a dot (hidden files, files) */
if(preg_match('/^[^.]+$/', $scanned_elements[$c]))
{
/* Checking for the keyword "Project" */
if($scanned_elements[$c] == '*Project*')
{
echo $scanned_elements[$c];
echo '</br>';
}
else
{
continue;
}
}
else
{
continue;
}
}
}
You can recursively retrieve files and folders with RecursiveDirectoryIterator, this will search in / for directories with 'project' in the foldername.
print_r(get_dirs('/','project'));
function get_dirs($path = '.', $search='') {
$dirs = array();
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path),
RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $file) {
if ($file->isDir())
{
if (strpos($file->getFileName(), $search) !== false)
{
$dirs[] = $file->getRealPath();
}
}
}
return $dirs;
}
I would get all directories recursively and then grep for Project:
function searchformigration($dir) {
$results = glob($dir, GLOB_ONLYDIR);
foreach($results as $subdir) {
$results = array_merge($results, searchformigration($subdir));
}
return $results;
}
$results = preg_grep('/Project/', searchformigration('/'));
You can also do this on Linux:
exec("find / -type d -name 'Project'", $results);

Simplified Recursive PHP

I've been working on a method of including files recursively with the __autoload() function in php. This way, You could throw your class anywhere in the "classes" folder, and have them organized by sub directory, But the __autoload function would still be able to find them. This is what I've gotten so far, And was wondering if anyone might be able to help me simplify it so that it isn't so lengthy. It is completely functional currently, And works like a charm. I'm just trying to make it shorter.
<?php
function readRecursive($path){
if(!is_dir($path)){
return false;
}
$dir = glob($path ."/*");
$retArr = array();
foreach($dir as $f){
if(is_dir($f)){
$m = readRecursive($f);
foreach($m as $n){
$retArr[] = $n;
}
}else{
$retArr[] = $f;
}
}
return $retArr;
}
function endsWith($haystack, $needle){
return $needle === "" || substr($haystack, -strlen($needle)) === $needle;
}
/* Set up AutoLoading for object classes */
function __autoload($class_name){
$classes = readRecursive("classes");
foreach($classes as $class){
if(endsWith(strtolower($class), strtolower($class_name.".class.php"))){
include_once ($class);
}
}
}
?>
Here's my attempt at this autoload for you.
I've slightly modified Emil Condrea's Answer.
To start, I'll show you the file structure of my classes:
As you can see above, the classes are set into seperate files and such to show.
Now taking Emil's answer and slightly changing it:
(Provided the file name is something like "Class.php" as seen above in the file structure)
function getClasses($path) {
$files = array();
$dir_iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST);
foreach ($dir_iterator as $item) {
$pathname = $item->getPathName();
$filename = $item->getFileName();
if ($item->isDir()) {
getClasses($item);
} else {
$files[$filename] = $pathname;
}
}
return $files;
}
Will warrant a return array of files like the following [FILE_NAME] => [PATH_NAME]:
Array
(
[Error.php] => /home2/DERP/public_html/something.com/watch/model/Site/Error.php
[Form.php] => /home2/DERP/public_html/something.com/watch/model/Site/Form.php
[Site.php] => /home2/DERP/public_html/something.com/watch/model/Site/Site.php
[Db.php] => /home2/DERP/public_html/something.com/watch/model/Database/Db.php
[Db_pdo.php] => /home2/DERP/public_html/something.com/watch/model/Database/Db_pdo.php
[Session.php] => /home2/DERP/public_html/something.com/watch/model/Security/Session.php
[Auth.php] => /home2/DERP/public_html/something.com/watch/model/Security/Auth.php
[Input.php] => /home2/DERP/public_html/something.com/watch/model/Security/Input.php
[Postcode.php] => /home2/DERP/public_html/something.com/watch/model/Postcode.php
[Rep.php] => /home2/DERP/public_html/something.com/watch/model/User/Rep.php
[User.php] => /home2/DERP/public_html/something.com/watch/model/User/User.php
[Notifications.php] => /home2/DERP/public_html/something.com/watch/model/User/Notifications.php
[Log.php] => /home2/DERP/public_html/something.com/watch/model/Log/Log.php
[Hook.php] => /home2/DERP/public_html/something.com/watch/model/Hook.php
)
Now that would've been called by something like the following:
getClasses(realpath(dirname(__FILE__)) . '/model')
Allowing us to run an __autoload() like the following:
$model_classes = getClasses(realpath(dirname(__FILE__)) . '/model');
function __autoload($class_name) {
global $model_classes;
$filename = ucfirst($class_name) . '.php';
$model = $filename;
if (!isset($model_classes[$model])) {
// dead
return false;
} else {
// include first file (model)
include($model_classes[$model]);
}
}
Now
Obviously you shouldn't use global but to me it seemed a far better alternative to running the getClasses() function every single time within the __autoload() function.
If someone else has anything to add, feel free! I just tried my own little method on this and it works without fault!
Note: I was using file_exists() before and the above method is, in my opinion; a lot faster.
UPDATE
I had a brain wave just the other night and thought;
"Why not scan the application root and fetch all the php files then run a function to check if said file actually contains a class to make this as universal as possible.."
So I did a little research and found this nifty little function from php: token_get_all()
Now after a little digging through SO I found this answer: Determine class in file...
and after some modification, the getClasses() function now looks like this:
function getClasses($path) {
$files = array();
$dir_iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST);
foreach ($dir_iterator as $item) {
$pathname = $item->getPathName();
$filename = $item->getFileName();
if ($item->isDir()) {
get_classes($item);
} else {
if (substr($filename, -4) === '.php') {
if (get_php_classes(file_get_contents($pathname))) {
$files[$filename] = $pathname;
}
}
}
}
return $files;
}
With the addition of this new function from the above question:
function get_php_classes($php_code) {
$tokens = token_get_all($php_code);
$class_token = false;
foreach ($tokens as $token) {
if (is_array($token)) {
if ($token[0] == T_CLASS) {
$class_token = true;
} else if ($class_token && $token[0] == T_STRING) {
$classes[] = $token[1];
// $class_token = false;
}
}
}
return $class_token;
}
Now that allows you to simply run a $classes = getClasses(ROOTPATH) and itterate through them.
DOWNFALL: each of the classes will have to have unique class names and/or file names. Unless somebody could lend their hand at a modification to allow.
You can use
RecursiveDirectoryIterator to iterate through the directory recursively. This might simplify your function.
function getRecursive($path){
$files = array();
$dir_iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),RecursiveIteratorIterator::SELF_FIRST);
foreach ($dir_iterator as $item) {
$subPath = $dir_iterator->getSubPathName();
if($item->isDir())
$files[$subPath] = array();
else
$files[$subPath][] = $subPath;
}
return $files;
}

Get folders with PHP glob - unlimited levels deep

I have this working function that finds folders and creates an array.
function dua_get_files($path)
{
foreach (glob($path . "/*", GLOB_ONLYDIR) as $filename)
{
$dir_paths[] = $filename;
}
return $dir_paths;
}
This function can only find the directories on the current location. I want to find the directory paths in the child folders and their children and so on.
The array should still be a flat list of directory paths.
An example of how the output array should look like
$dir_path[0] = 'path/folder1';
$dir_path[1] = 'path/folder1/child_folder1';
$dir_path[2] = 'path/folder1/child_folder2';
$dir_path[3] = 'path/folder2';
$dir_path[4] = 'path/folder2/child_folder1';
$dir_path[5] = 'path/folder2/child_folder2';
$dir_path[6] = 'path/folder2/child_folder3';
If you want to recursively work on directories, you should take a look at the RecursiveDirectoryIterator.
$path = realpath('/etc');
$objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST);
foreach($objects as $name => $object){
echo "$name\n";
}
Very strange - everybody advice recursion, but better just cycle:
$dir ='/dir';
while($dirs = glob($dir . '/*', GLOB_ONLYDIR)) {
$dir .= '/*';
if(!$result) {
$result = $dirs;
} else {
$result = array_merge($result, $dirs);
}
}
Try this instead:
function dua_get_files($path)
{
$data = array();
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST);
foreach ($files as $file)
{
if (is_dir($file) === true)
{
$data[] = strval($file);
}
}
return $data;
}
Use this function :
function dua_get_files($path)
{
$dir_paths = array();
foreach (glob($path . "/*", GLOB_ONLYDIR) as $filename)
{
$dir_paths[] = $filename;
$a = glob("$filename/*", GLOB_ONLYDIR);
if( is_array( $a ) )
{
$b = dua_get_files( "$filename/*" );
foreach( $b as $c )
{
$dir_paths[] = $c;
}
}
}
return $dir_paths;
}
You can use php GLOB function, but you must create a recursive function to scan directories at infinite level depth. Then store results in a global variable.
function dua_get_files($path) {
global $dir_paths; //global variable where to store the result
foreach ($path as $dir) { //loop the input
$dir_paths[] = $dir; //can use also "basename($dir)" or "realpath($dir)"
$subdir = glob($dir . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR); //use DIRECTORY_SEPARATOR to be OS independent
if (!empty($subdir)) { //if subdir is not empty make function recursive
dua_get_files($subdir); //execute the function again with current subdir
}
}
}
//usage:
$path = array('galleries'); //suport absolute or relative path. support one or multiple path
dua_get_files($path);
print('<pre>'.print_r($dir_paths,true).'</pre>'); //debug
For PHP, if you are on a linux/unix, you can also use backticks (shell execution) with the unix find command. Directory searching on the filesystem can take a long time and hit a loop -- the system find command is already built for speed and to handle filesystem loops. In other words, the system exec call is likely to cost far less cpu-time than using PHP itself to search the filesystem tree.
$dirs = `find $path -type d`;
Remember to sanitize the $path input, so other users don't pass in security compromising path names (like from the url or something).
To put it into an array
$dirs = preg_split("/\s*\n+\s*/",`find $path -type d`,-1,PREG_SPLIT_NO_EMPTY);

Delete the unwanted files and folders from destination folder as compared to source folder

I am using PHP and I need to script something like below:
I have to compare two folder structure
and with reference of source folder I
want to delete all the files/folders
present in other destination folder
which do not exist in reference source
folder, how could i do this?
EDITED:
$original = scan_dir_recursive('/var/www/html/copy2');
$mirror = scan_dir_recursive('/var/www/html/copy1');
function scan_dir_recursive($dir) {
$all_paths = array();
$new_paths = scandir($dir);
foreach ($new_paths as $path) {
if ($path == '.' || $path == '..') {
continue;
}
$path = $dir . DIRECTORY_SEPARATOR . $path;
if (is_dir($path)) {
$all_paths = array_merge($all_paths, scan_dir_recursive($path));
} else {
$all_paths[] = $path;
}
}
return $all_paths;
}
foreach($mirror as $mirr)
{
if($mirr != '.' && $mirr != '..')
{
if(!in_array($mirr, $original))
{
unlink($mirr);
// delete the file
}
}
}
The above code shows what i did..
Here My copy1 folder contains extra files than copy2 folders hence i need these extra files to be deleted.
EDITED:
Below given output is are arrays of original Mirror and of difference of both..
Original Array
(
[0] => /var/www/html/copy2/Copy (5) of New Text Document.txt
[1] => /var/www/html/copy2/Copy of New Text Document.txt
)
Mirror Array
(
[0] => /var/www/html/copy1/Copy (2) of New Text Document.txt
[1] => /var/www/html/copy1/Copy (3) of New Text Document.txt
[2] => /var/www/html/copy1/Copy (5) of New Text Document.txt
)
Difference Array
(
[0] => /var/www/html/copy1/Copy (2) of New Text Document.txt
[1] => /var/www/html/copy1/Copy (3) of New Text Document.txt
[2] => /var/www/html/copy1/Copy (5) of New Text Document.txt
)
when i iterate a loop to delete on difference array all files has to be deleted as per displayed output.. how can i rectify this.. the loop for deletion is given below.
$dirs_to_delete = array();
foreach ($diff_path as $path) {
if (is_dir($path)) {
$dirs_to_delete[] = $path;
} else {
unlink($path);
}
}
while ($dir = array_pop($dirs_to_delete)) {
rmdir($dir);
}
First you need a recursive listing of both directories. A simple function like this will work:
function scan_dir_recursive($dir, $rel = null) {
$all_paths = array();
$new_paths = scandir($dir);
foreach ($new_paths as $path) {
if ($path == '.' || $path == '..') {
continue;
}
if ($rel === null) {
$path_with_rel = $path;
} else {
$path_with_rel = $rel . DIRECTORY_SEPARATOR . $path;
}
$full_path = $dir . DIRECTORY_SEPARATOR . $path;
$all_paths[] = $path_with_rel;
if (is_dir($full_path)) {
$all_paths = array_merge(
$all_paths, scan_dir_recursive($full_path, $path_with_rel)
);
}
}
return $all_paths;
}
Then you can compute their difference with array_diff.
$diff_paths = array_diff(
scan_dir_recursive('/foo/bar/mirror'),
scan_dir_recursive('/qux/baz/source')
);
Iterating over this array, you will be able to start deleting files. Directories are a bit trickier because they must be empty first.
// warning: test this code yourself before using on real data!
$dirs_to_delete = array();
foreach ($diff_paths as $path) {
if (is_dir($path)) {
$dirs_to_delete[] = $path;
} else {
unlink($path);
}
}
while ($dir = array_pop($dirs_to_delete)) {
rmdir($dir);
}
I've tested things and it should be working well now. Of course, don't take my word for it. Make sure to setup your own safe test before deleting real data.
For recursive directories please use:
$modified_directory = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('path/to/modified'), true
);
$modified_files = array();
foreach ($modified_directory as $file)
{
$modified_files []= $file->getPathname();
}
You can do other things like $file->isDot(), or $file->isFile(). For more file commands with SPLFileInfo visit http://www.php.net/manual/en/class.splfileinfo.php
Thanks all for the precious time given to my work, Special Thanks to erisco for his dedication for my problem, Below Code is the perfect code to acomplish the task I was supposed to do, with a little change in the erisco's last edited reply...
$source = '/var/www/html/copy1';
$mirror = '/var/www/html/copy2';
function scan_dir_recursive($dir, $rel = null) {
$all_paths = array();
$new_paths = scandir($dir);
foreach ($new_paths as $path) {
if ($path == '.' || $path == '..') {
continue;
}
if ($rel === null) {
$path_with_rel = $path;
} else {
$path_with_rel = $rel . DIRECTORY_SEPARATOR . $path;
}
$full_path = $dir . DIRECTORY_SEPARATOR . $path;
$all_paths[] = $path_with_rel;
if (is_dir($full_path)) {
$all_paths = array_merge(
$all_paths, scan_dir_recursive($full_path, $path_with_rel)
);
}
}
return $all_paths;
}
$diff_paths = array_diff(
scan_dir_recursive($mirror),
scan_dir_recursive($source)
);
echo "<pre>Difference ";print_r($diff_paths);
$dirs_to_delete = array();
foreach ($diff_paths as $path) {
$path = $mirror."/".$path;//added code to unlink.
if (is_dir($path)) {
$dirs_to_delete[] = $path;
} else {
if(unlink($path))
{
echo "File ".$path. "Deleted.";
}
}
}
while ($dir = array_pop($dirs_to_delete)) {
rmdir($dir);
}
First do a scandir() of the original folder, then do a scandir on mirror folder. start traversing the mirror folder array and check if that file is present in the scandir() of original folder. something like this
$original = scandir('path/to/original/folder');
$mirror = scandir('path/to/mirror/folder');
foreach($mirror as $mirr)
{
if($mirr != '.' && $mirr != '..')
{
if(in_array($mirr, $original))
{
// do not delete the file
}
else
{
// delete the file
unlink($mirr);
}
}
}
this should solve your problem. you will need to modify the above code accordingly (include some recursion in the above code to check if the folder that you are trying to delete is empty or not, if it is not empty then you will first need to delete all the file/folders in it and then delete the parent folder).

Categories