Unix like smart recursive file/folder copy function for PHP

Blog
PHP

PHP's file system manipulation functions are not as complete as they should be specially when it comes to manging recursive folders. For addressing part of this issue i decided to write a smart function which can handle recursive file/folder copy easily.

As you may have noticed there are feature that i didn't have time to implement , so if you have time to extend this function please notify me as well :). It's also shared here

Update : Thiago has written a different version of this function

Update 2010/05/14 : Function has updated. The new version is compatible with Windows and also can copy a folder to itself! like C:\test -> C:\test\new

Several sites considered this function the best solutions for copying files!

 

/**
* Create a new directory, and the whole path.
*
* If  the  parent  directory  does  not exists, we will create it,
* etc.
* @todo
*     - PHP5 mkdir functoin supports recursive, it should be used
* @author baldurien at club-internet dot fr 
* @param string the directory to create
* @param int the mode to apply on the directory
* @return bool return true on success, false else
* @previousNames mkdirs
*/

function makeAll($dir, $mode = 0777, $recursive = true) {
    if( is_null($dir) || $dir === "" ){
        return FALSE;
    }
    
    if( is_dir($dir) || $dir === "/" ){
        return TRUE;
    }
    if( makeAll(dirname($dir), $mode, $recursive) ){
        return mkdir($dir, $mode);
    }
    return FALSE;
}

/**
 * Copies file or folder from source to destination, it can also do
 * recursive copy by recursively creating the dest file or directory path if it wasn't exist
 * Use cases:
 * - Src:/home/test/file.txt ,Dst:/home/test/b ,Result:/home/test/b -> If source was file copy file.txt name with b as name to destination
 * - Src:/home/test/file.txt ,Dst:/home/test/b/ ,Result:/home/test/b/file.txt -> If source was file Creates b directory if does not exsits and copy file.txt into it
 * - Src:/home/test ,Dst:/home/ ,Result:/home/test/** -> If source was directory copy test directory and all of its content into dest      
 * - Src:/home/test/ ,Dst:/home/ ,Result:/home/**-> if source was direcotry copy its content to dest
 * - Src:/home/test ,Dst:/home/test2 ,Result:/home/test2/** -> if source was directoy copy it and its content to dest with test2 as name
 * - Src:/home/test/ ,Dst:/home/test2 ,Result:->/home/test2/** if source was directoy copy it and its content to dest with test2 as name
 * @author Sina Salek (http://sina.salek.ws/node/1289)
 * @todo
 *  - Should have rollback so it can undo the copy when it wasn't completely successful
 *  - It should be possible to turn off auto path creation feature f
 *  - Supporting callback function
 *  - May prevent some issues on shared enviroments : http://us3.php.net/umask
 * @param $source //file or folder
 * @param $dest ///file or folder
 * @param $options //folderPermission,filePermission
 * @return boolean
 */
function smartCopy($source, $dest, $options=array('folderPermission'=>0755,'filePermission'=>0755))
{
	$result=false;
	
	//For Cross Platform Compatibility
	if (!isset($options['noTheFirstRun'])) {
		$source=str_replace('\\','/',$source);
		$dest=str_replace('\\','/',$dest);
		$options['noTheFirstRun']=true;
	}
	
	if (is_file($source)) {
		if ($dest[strlen($dest)-1]=='/') {
			if (!file_exists($dest)) {
				makeAll($dest,$options['folderPermission'],true);
			}
			$__dest=$dest."/".basename($source);
		} else {
			$__dest=$dest;
		}
		if (!file_exists($__dest)) {
			$result=copy($source, $__dest);
			chmod($__dest,$options['filePermission']);
		}
	} elseif(is_dir($source)) {
		if ($dest[strlen($dest)-1]=='/') {
			if ($source[strlen($source)-1]=='/') {
				//Copy only contents
			} else {
				//Change parent itself and its contents
				$dest=$dest.basename($source);
				@mkdir($dest);
				chmod($dest,$options['filePermission']);
			}
		} else {
			if ($source[strlen($source)-1]=='/') {
				//Copy parent directory with new name and all its content
				@mkdir($dest,$options['folderPermission']);
				chmod($dest,$options['filePermission']);
			} else {
				//Copy parent directory with new name and all its content
				@mkdir($dest,$options['folderPermission']);
				chmod($dest,$options['filePermission']);
			}
		}

		$dirHandle=opendir($source);
		while($file=readdir($dirHandle))
		{
			if($file!="." && $file!="..")
			{
				$__dest=$dest."/".$file;
				$__source=$source."/".$file;
				//echo "$__source ||| $__dest<br />";
				if ($__source!=$dest) {
					$result=smartCopy($__source, $__dest, $options);
				}
			}
		}
		closedir($dirHandle);
		
	} else {
		$result=false;
	}
	return $result;
}

Your rating: None Average: 2 (29 votes)

Comments

dywany's picture

Very good post, thanks a lot.

Very good post, thanks a lot.

justin liebregts's picture

Hello, I was trying to use

Hello,

I was trying to use your smartCopy function that I found on php.net
(http://ca.php.net/copy). I noticed that when trying to run the function
it fails because the variable $dirsource has not been defined ($dirsource
is used in the last while loop of your function). Would you be able to
help me out?

Thanks again for the great function :)

- Justin

admin's picture

Hi justin , Sorry about that

Hi justin ,
Sorry about that i renamed $dirsource parameter to $source but i missed that one :),
so It should be fixed by replacing it with $source.

justin liebregts's picture

ah perfect thank you!! I

ah perfect thank you!! I thought that might work but I was thinking if the $source was pointing to a file, then it might break because it's not a "directory". I probably should have just tried it out to see what would happen. Thanks a lot! :D

Sebastien's picture

Hello again, I did some more

Hello again,

I did some more testing and realized that the problem with the missing
cmfcDirectory class has nothing to do with trying to copy accross two
domains. It's just plain missing because it seems to be a custom class that
you built and omitted to include on php.net.

Is there any chance you could email me the class please? Your smartcopy
function is not very useful without this important missing piece. It works
fine if the destination folder already exists but if not then it calls on
the class and fails right there...

Please correct me if I'm wrong... and I do apoligize if I am.. but after a
bit of googling it seems like cmfcDirectory is a custom user class and I'd
really like to have it. Thanks for your time!

admin's picture

Hi, You guessed correctly,

Hi,
You guessed correctly, this function is part of my own framework. i simply missed that function :).
I attached the most recent version, hope it helps

Sebastien's picture

Sweet! Thanks a lot man you

Sweet! Thanks a lot man you rock!

caparuni's picture

this functions rocks... luv

this functions rocks... luv it... thank you work your contribution ;)

btw it seems that gonna cause endless loop of copying folder if we copy some folder to itself. for exsample smartCopy('some/source', some/source/source')

admin's picture

Glad to hear you liked it

Glad to hear you liked it ;)
Have a look at the new version, it's also compatible with Windows

caparuni's picture

ty for the fix.. im gnna try

ty for the fix.. im gnna try it ;)

Dom's picture

Absolute Lifesaver!! Been

5

Absolute Lifesaver!! Been hunting for a decent recursive copying functions and this is awesome!
Quick question, is there a way to ignore/exclude cetrain directories (and in turn subdirectories) from copying? I'm wanting to use it on the root of a drive but the (backup) destination is a subdirectory of this root and obviously don't want to backup that into itself.

admin's picture

Try the latest version, it

Try the latest version, it automatically excludes the destination

Sandra's picture

So I wanted to thank you for

So I wanted to thank you for your post of the copy function on php.net. I
have spent hours trying to use the copy with permissions issues, and in an
instant,with your function, the problem is resolved. Thank you, greetings,
Sandra

Khalid's picture

hello Sina Salek thank you

hello Sina Salek

thank you for your function . it's fully supporting file and directory copy .

i sea your function here
http://php.net/manual/en/function.copy.php

Question !

is your function is working under server is working savemode ?

admin's picture

Haven't tried that, but i

Haven't tried that, but i don't see any reason why it shouldn't.
You might also want to try the latest version here. couple of bugs are fixed + new features

jayson's picture

i would like to ask if this

5

i would like to ask if this script allow copying of files accross network...copying file from a shared folder....tnx..

please guide me..

admin's picture

If PHP can, this function is

If PHP can, this function is also capable of doing it. and as long as i know PHP file system functions support copying files over network.
If for any reason you couldn't get it to work you can always map share folder as network drives in Windows.

Gege089's picture

I am not an English spoker.

I am not an English spoker. So I am sorry for this bad English...
I was looking for a simple function in php to copy also folders and files.
When I read the php notice of copy
[http://php.net/manual/fr/function.copy.php].
I wrote a more "data-driven" function with less lines that you ! :

function copier($directory, $destination){
    $dir = scandir($directory);
    $i=0;
    while(!empty($dir[$i])){
        if (($dir[$i]!=".") and ($dir[$i]!="..") and (is_dir($dir[$i])==true)){
            $destinationback = $destination;
            $directoryback = $directory;
            $destination = $destination. "/". $dir[$i];
            $directory = $directory. "/". $dir[$i];
            mkdir($destination);
            copier($directory, $destination);
            $destination = $destinationback;
            $directory = $directoryback;
        }
        elseif(($dir[$i]!=".") and ($dir[$i]!="..") and (is_dir($dir[$i])==false))
{
            copy($directory. "/". $dir[$i], $destination. "/". $dir[$i]);
        }
        $i++;
    }
} 

Stefan Wimmer's picture

Hey Sina, maybe I didn't

5

Hey Sina,

maybe I didn't quite get the intention of this function, but it somehow didn't work for me. I wanted to do something like this:
/my/source/path/file.txt => /my/otherpath/different/file.txt
Where the second path doesn't exist.

I thought your function could do this out of the box but after a closer look it was not possible. I changed the first part of the function and now it does what I want. As I said, I am not sure whether I got the whole idea wrong...

Here's what I did:

***************
....
if (is_file($source)) {
$info = pathinfo($dest);
$dir = $info['dirname'];
if (!is_dir($dir)) {
makeAll($dir,$options['folderPermission'],true);

}
$result=copy($source, $dest);
chmod($dest,$options['filePermission']);
}
*************************

Cheers, Stefan

Stephen M's picture

Awesome - exactly what I was

Awesome - exactly what I was looking for, thank you!

I am using this to do a recursive copy from the current directory I place the PHP file in to a subfolder in that same directory, so I call the function like this:

$src = getcwd();
$dst = $src . "/backup";

smartCopy($src,$dst);

I have one small suggestion for an improvement I made - on some servers with a low PHP max_execution_time, where the following doesn't work:
@set_time_limit(0);

You may need to run the script multiple times to complete copying a lot of files. This will speed it up a lot on the subsequent runs - just don't copy a file if it already exists. Replace line 69 and 70 with:

if (!file_exists($dest)) {
result=copy($source, $__dest);
chmod($__dest,$options['filePermission']);
}

Thanks again - very much appreciated!

admin's picture

Thanks for the suggestion, i

Thanks for the suggestion, i included in the code :), also if possible it's always better to run very long through command line, PHP is a great scripting language.

johnres's picture

Well, you can use Long Path

Well, you can use Long Path Tool as well, it works good for such problems.