Recursively copying directories in PHP
Posted on April 5th, 2004 in Code Repository | 38 Comments »
This function allows you to copy an entire directory tree recursively. Symlinks on both operating systems are also accommodated for.
/**
* Copy a file, or recursively copy a folder and its contents
*
* @author Aidan Lister <aidan@php.net>
* @version 1.0.1
* @link http://aidanlister.com/2004/04/recursively-copying-directories-in-php/
* @param string $source Source path
* @param string $dest Destination path
* @return bool Returns TRUE on success, FALSE on failure
*/
function copyr($source, $dest)
{
// Check for symlinks
if (is_link($source)) {
return symlink(readlink($source), $dest);
}
// Simple copy for a file
if (is_file($source)) {
return copy($source, $dest);
}
// Make destination directory
if (!is_dir($dest)) {
mkdir($dest);
}
// Loop through the folder
$dir = dir($source);
while (false !== $entry = $dir->read()) {
// Skip pointers
if ($entry == '.' || $entry == '..') {
continue;
}
// Deep copy directories
copyr("$source/$entry", "$dest/$entry");
}
// Clean up
$dir->close();
return true;
}
Just to make sure this function works perfectly, we can do some testing:
// Create a directory and file tree
mkdir('testcopy');
mkdir('testcopy/one-a');
touch('testcopy/one-a/testfile');
mkdir('testcopy/one-b');
// Add some hidden files for good measure
touch('testcopy/one-b/.hiddenfile');
mkdir('testcopy/one-c');
touch('testcopy/one-c/.hiddenfile');
// Add some more depth
mkdir('testcopy/one-c/two-a');
touch('testcopy/one-c/two-a/testfile');
mkdir('testcopy/one-d/');
// Test that symlinks are created properly
mkdir('testlink');
touch('testlink/testfile');
symlink(getcwd() . '/testlink/testfile', 'testcopy/one-d/my-symlink');
symlink(getcwd() . '/testlink', 'testcopy/one-d/my-symlink-dir');
symlink('../', 'testcopy/one-d/my-symlink-relative');
$status = copyr('testcopy', 'testcopy-copy');
if (file_exists('testcopy-copy')
&& file_exists('testcopy-copy/one-b/.hiddenfile')
&& file_exists('testcopy-copy/one-c/two-a/testfile')
&& is_link('testcopy-copy/one-d/my-symlink-relative')
&& (readlink('testcopy-copy/one-d/my-symlink-relative') == '../')
&& is_link('testcopy-copy/one-d/my-symlink')) {
echo "TEST PASSED";
} else {
echo "TEST FAILED";
}
If everything works as expected, you should see a “TEST PASSED”.
38 Responses
Thanx for the perfect COPYR func
simple, but nice
THX
Awesome =)
thanks for the function! saved me some brainwaves during a hard programming evening…
Hello,
First Thanks for the nice code. I have something to say on your algorithm.
Here you can do simply this:
if ($dest !== “$source/$entry”) { copyr(“$source/$entry”, “$dest/$entry”); }
If it is a file you do test it in the beginning of your function. If it is the destination you just have to continue.
Hope I am useful!
Regards,
Dimitar Peev
[Editor's note: Good pick up, thanks]
thanks a squillion for this function! I hope you can enjoy the music from my site as a way thanks: futureboogiedotcom
thanks alot for this one. was too lazy to get my brain thinking about a recursive function for it. needed this one quite often. great that someone wrote in down
regards,
chill
Thanks dude! You write beautiful recursive code!
hehello;
good script ! thanks
very nice!
Excellent script, I hate thinking recursively.
This way may allow to copy the permissions over from the source file to the destination:
<?php
// get the current permissions from the source file or directory
$perms = substr(sprintf(‘%o’, fileperms(“$source/$entry”)), -4);
?>
Then apply the permissions in $perms to the destination file after its been copied:
<?php
// Change the permissions on the destination file or folder
chmod (“$dest/$entry”, $perms);
?>
I’ve had numerous problems changing permissions in my php scripts. If you’re running PHP on a linux box then try editing the httpd.conf file and changing the user and group apache runs as.
Then make that user a member of the group the files that you want to chmod belongs to. Then restart apache.
Hope this helps.
Thanks for useful example
Good little script Aidan. Strange that there is no native PHP function.
thanks… i was so certain i’d break this thing in a matther of minutes but it seems to work pretty nicely!
Hi and thanx
thanks for the great function. I’ve addapted it to copy only files from certain types, passing an optional array as a third parameter. The result is here, and I hope it will be useful for others:
http://smalltalking.com/wiki/php:copyr
thank’s
Very nice! Thank’s
This is a function that does the same but uses a stack array to make it work faster.
Using recursion is (nearly?) always slower since it takes up extra resources for every instance of the function and all the set variables in the function instances.
Comments are always welcome
http://pastebin.com/775560
The stack array is damned cool. That’s a technique I’ve not seen before.
I spent hours trying to write something similar. thanks.
Thanx,simple but powerful algorithm
Should $source/$entry be $source . DIRECTORY_SEPARATOR . $entry to handle windows paths?
[Editor's Note: No, PHP automatically handles the conversion on Windows.]
thanks
really great. Thank you for all you guys!
Excellent script it works perfectly.
If you are looking to try to make the exec (cp) work, this is easier for real
Wow, This is my FIRST work script !
Thanks very much. I was looking for such a function today and this one is perfect
I have been commissioned to provide a univeral installation system for a company that has several php-based content/database management systems. I have used this copy function (copied asis into it’s own file) without a hitch. Very nice work, and is there somewhere I can send a donation?
Hi Nightmoon,
Glad you liked the function! Your feedback is donation enough, good luck with the project.
Thanks for the awesome script, I have pretty much got it working as I want. BUT in running the initial testing that you provide above, there are now two folders on my web server that I cannot delete. I can move them around, but they just don’t seem to delete.
Any ideas?
very good coding. It’s very useful..
Thanks…
Saved me a lot of time. Thanks a lot.
Hi Aidan,
Thanks for sharing a great function. Just one remark. It seems to me like your recursive calling of copyr() at line 37 should take into consideration the return value, and if ever it is false, return false at the end of the function. Otherwise your function will always return true, as long as the first level of recursion returns no errors. Errors that happen in the deeper levels of your recursion are never reported back to the original caller of the function, no? Let me know if I’m missing something here.
Cheers,
Chris
Hi again,
I added a few lines of code for the following reasons:
- I added a $return variable for the recursive function to prevent a mistaken TRUE return value.
- I added a chmod for folders, so that I can delete them off the server via ftp.
- (I also added an array to the parameters for folders who’s content I don’t want to copy)
http://www.dickinson.ch/public_code/php_dirs_copy.txt
I also created a second function to delete folders and files recursively, which I needed after having run your test-program on the original code (deleting the folders and files over ftp was impossible, and when I used filezilla, it went into an infinite loop – supposedly because of the symbolic links).
http://www.dickinson.ch/public_code/php_dirs_delete.txt
worked for me!
Hi Aidan,
Thanks for that lovely function – very well written and standing the test of time with all the dignity of Dr Quinn Medicine woman.
Beautiful work!
thank you so much!