Jump to content

Checking for '.' and '..' as a Condition for Invoking Recursion


iwato

Recommended Posts

Question ONE: Are the following statement's true?

  1. '.' refers to the file or folder currently being read.
  2. '..' refers to the folder in which the curent file or folder is located.

If not, then what do the mean?Question TWO: Why is it necessary to check the following two conditions in the code below? Under what circumstances could it occur that they even need to be checked? Is there not another way to achieve the same task? For example, does not the readdir() function return boolean false when it finds nothing more to read, does it not?

  1. $element != '.'
  2. $element != '..'

	$path = 'foo';	$delim = strstr(PHP_OS, 'Darwin') ? '/' : '\\';	function retrieveTree($path)  {		global $delim;		if ($dir=@opendir($path)) {			while (($element=readdir($dir))!== false) {				if (is_dir($path.$delim.$element) && $element!= '.' && $element!= '..') {					$array[$element] = retrieveTree($path.$delim.$element);				} elseif ($element!= '.' && $element!= '..') {					$array[] = $element;				}			}			closedir($dir);		}		return (isset($array) ? $array : false);	}

Roddy

Link to comment
Share on other sites

Yes, . refers to the current folder and .. refers to the parent folder.The functions that list files in a directory always have "." and ".." even though they're not real files, so you have to make sure to test if the file is not one of those if you want to check all the files in a directory.

Link to comment
Share on other sites

In the case of recursion, the reason why you ignore them is because or else you will get infinite recursion, as the logic selects the same folder over and over again, or goes up, and down, and up, and down, forever.

Link to comment
Share on other sites

A Unix directory cannot be removed until it is empty. To remove a directory, then, you need a utility that recursively unlinks all the files and removes all the subdirectories that it contains. Guess what happens when you pass ".." to such a function if it does screen for such a circumstance? :) I did such a thing. Once.I use glob a lot now. :)

Link to comment
Share on other sites

Here you go, can also be used to delete files only and not directories (empty the directory):

function delete_directory($directory, $empty=false){  if(substr($directory,-1) == DIRECTORY_SEPARATOR)  {	$directory = substr($directory,0,-1);  }  if(!file_exists($directory) || !is_dir($directory))  {	return false;  }  elseif(!is_readable($directory))  {	return false;  }  else  {	$handle = opendir($directory);	while (false !== ($item = readdir($handle)))	{	  if($item != '.' && $item != '..')	  {		$path = $directory.DIRECTORY_SEPARATOR.$item;		if(is_dir($path))		{		  delete_directory($path);		}		else		{		  unlink($path);		}	  }	}	closedir($handle);	if($empty == false)	{	  if(!rmdir($directory))	  {		return false;	  }	}	return true;  }}

Link to comment
Share on other sites

I hope you didn't just write that. I've had a working model for a while now. I wrote the original in Perl, in fact, since that's where I had "the accident."Still, it never hurts to post working code, and maybe Roddy can pick up something. Nice illustration of DIRECTORY_SEPARATOR, too.

Link to comment
Share on other sites

function delete_directory($directory, $empty=false)

This is a very useful function, but I prefer to think of it in this way:
function empty_directory($path, $empty=true)

This makes it clearly distinctive and more Apple oriented.For some reason, I remember a method in an extension somewhere that does what your function does with the $empty's default value set to false.Roddy

Link to comment
Share on other sites

I didn't really figure you had. :)
I would like to thank everyone, who contributed to this thread including Ingolme, Synook, JSG, and DD. There was not an idea in it that I did not find useful. I surely hope that others have benefited from it in the same way that I have.RoddyDrinking one 12oz. can of Coca Cola is like eating 10 cola-flavored sugar cubes.
Link to comment
Share on other sites

Here you go, can also be used to delete files only and not directories (empty the directory):
 if(!file_exists($directory) || !is_dir($directory))

JSG, I have been reviewing your code, and would like to know the reason for the second argument in the above if-condition. Since the file_exists() function checks for both folders and files, what is the purpose of the is_dir() that checks only for folders? Is it not redundant and inefficient? Or, am I am missing something? Certainly, when I run the code without the is_dir() argument, I can detect no difference.Roddy
Link to comment
Share on other sites

elseif(!is_readable($directory))[/code]
Also, JSG, since the return statement yields the same for both existence and readability, why not use only the is_readable() function and do away with the following statement completely?
if(!file_exists($directory) || !is_dir($directory)) {return false;}

Roddy

Link to comment
Share on other sites

Here you go, can also be used to delete files only and not directories (empty the directory)
Wow! I have discovered still another problem with this code. When I entered a file that was not a folder as the $folder parameter, an infinite-loop resulted. Obviously, a much better set of control checks is required, before the function is run recursively.Roddy
Link to comment
Share on other sites

Here you go, can also be used to delete files only and not directories (empty the directory)
OK. I have fixed all of the previously mentioned problems, but have now run into a new one that baffles me. If I enter '../baz/', it fails and returns null. If I enter '../baz', it succeeds.Should this problem not have been corrected with the first if-statement.
function empty_folder($folder, $empty=false) {	if(substr($folder,-1) == DIRECTORY_SEPARATOR) {		echo $folder = substr($folder,0,-1);	} elseif(!is_readable($folder) || !is_dir($folder)) {			return false;	} else {		$handle = opendir($folder);		while (false !== ($item = readdir($handle))) {			if($item != '.' && $item != '..') {				$path = $folder.DIRECTORY_SEPARATOR.$item;				if(is_dir($path)) {					empty_folder($path);				} else {					unlink($path);				}			}		}		closedir($handle);		if($empty == false) {			if(!rmdir($folder)) {				return false;			}		}		return true;	}}

Roddy

Link to comment
Share on other sites

Unless you also want to be able to handle FTP, the following is likely to work better (i.e. be able to handle a deeper level of folder removal) thanks to the use of RecursiveDirectoryItrator instead of plain recursion on the PHP level. I've also added some additional error handling in the form of exceptions:

/** * Removes a folder with all of its contents. * * @author boen_robot * @param string $path The directory to remove * @param bool $continueOnError Whether to keep deleting items even after a * failure on a child item. * @return bool TRUE on success, FALSE on failure. */function rrmdir($path, $continueOnError = false) {	if (!is_dir($path)) {		throw new Exception("Directory '{$path}' not found", 1);	}	if (!is_writable($path)) {		throw new Exception("Permission denied for '{realpath($path)}'", 2);	}	$iterator =		new RecursiveIteratorIterator(			new RecursiveDirectoryIterator(					$path,					FilesystemIterator::CURRENT_AS_FILEINFO			),			RecursiveIteratorIterator::CHILD_FIRST		);	foreach($iterator as $current) {		$realpath = $current->getRealPath();		if (!is_writable($realpath)) {			if ($continueOnError) continue;			throw new Exception("Permission denied for '{$realpath}'", 3);		}		if (is_file($realpath) || is_link($realpath)) {			if (!unlink($realpath)) {				if ($continueOnError) continue;				throw new Exception("Unlinking a child file or link '{$realpath}' failed", 4);			}		}elseif(is_dir($realpath)) {			if (!rmdir($realpath)) {				if ($continueOnError) continue;				throw new Exception("Removing a child directory '{$realpath}' failed", 5);			}		}else {			if ($continueOnError) continue;			throw new Exception("Unable to remove '{$realpath}'", 6);		}	}	if (!rmdir($path)) {		if ($continueOnError) return false;		throw new Exception("Removing the directory '{realpath($path)}' failed", 7);	}	return true;}

Link to comment
Share on other sites

i use this$bad_units=array('','.','..','~'); if(isset($_POST['directory'])) {foreach(explode('/',$_POST['directory']) as $u) {if(!in_array($u,$bad_units)&&file_exists($dir_files.$ask.$u)&&is_dir($dir_files.$ask.$u)) {$ask.=$u.'/';}else{break;}}}

Link to comment
Share on other sites

This is a very useful function, but I prefer to think of it in this way:CODEfunction empty_directory($path, $empty=true)This makes it clearly distinctive and more Apple oriented.
I don't know, I think it's a little counter-intuitive that doing this:empty_directory('/path/to/dir', false);would result in the directory being deleted instead of just emptied. You could just keep the original:function delete_directory($directory, $empty=false)Which will delete the directory if you leave out the second argument, and also define this:
function empty_directory($path){  delete_directory($path, true);}

But, whatever makes sense.

Since the file_exists() function checks for both folders and files, what is the purpose of the is_dir() that checks only for folders?
Because the function is labeled as deleting a directory, not a file. Therefore, it wants to validate that you are sending it a directory name and not a file name. If you just want to delete a file you can use unlink instead. file_exists checks that the path exists, and is_dir checks that it is a directory and not a file.
why not use only the is_readable() function and do away with the following statement completely?
It's fine to remove file_exists and use is_readable and is_dir, but I wouldn't leave out is_dir.
Wow! I have discovered still another problem with this code. When I entered a file that was not a folder as the $folder parameter, an infinite-loop resulted.
If you run the original it won't do that, for the reasons I outlined above. It sounds like in your quest to optimize my code that you removed some things that shouldn't have been removed and introduced a new bug that wasn't there originally. The reason for the redundancy with file_exists is probably that I added the is_readable check after testing the function and didn't think to replace file_exists instead. This should be the final:
function delete_directory($directory, $empty=false){  if(substr($directory,-1) == DIRECTORY_SEPARATOR)  {	$directory = substr($directory,0,-1);  }  if(!is_dir($directory) || !is_readable($directory))  {	return false;  }  else  {	$handle = opendir($directory);	while (false !== ($item = readdir($handle)))	{	  if($item != '.' && $item != '..')	  {		$path = $directory.DIRECTORY_SEPARATOR.$item;		if(is_dir($path))		{		  delete_directory($path);		}		else		{		  unlink($path);		}	  }	}	closedir($handle);	if($empty == false)	{	  if(!rmdir($directory))	  {		return false;	  }	}	return true;  }}

There may be one other change you want to make to that. You could have it send $empty when it recurses to cause it to keep a nested directory structure but delete all files, as opposed to emptying the one parent directory completely.

Link to comment
Share on other sites

There may be one other change you want to make to that. You could have it send $empty when it recurses to cause it to keep a nested directory structure but delete all files, as opposed to emptying the one parent directory completely.
While studying boen_robot's and your functions I decided to write my own with ideas taken from a variety of other sources and a little of my own invention. It appears to be very robust and does something that neither boen_robot's, nor your functions does; it set folder and file permissions and counts the number of deleted folders and files. Before developing the function further to include other features that boen_robot and you have included in your own functions I would like to hear everyone's opinion about mine.The FUNCTION
function rrmdir_override($path, $fld_count = 0, $file_cnt = 0) {	if (!is_dir($path)) {		throw new Exception('"' . basename($path) . '" is not a folder. Revise your path\'s destination.', 1);	}	$old_umask = umask();	umask(0);	chmod($path, 0755);	$members = scandir($path);	foreach ($members as $member) {		if ($member != '.' && $member != '..') {			$member_path = $path.DIRECTORY_SEPARATOR.$member;			chmod($member_path, 0755);			if (is_dir($member_path)) {				[b]rrmdir_override($member_path, &$fld_count, &$file_cnt)[/b]; 			} else {				unlink($member_path);				$file_cnt++;			}		}	}	rmdir($path);	umask($old_umask);	[b]$fld_count++;[/b]	$doc_count =  array('Folder Count' => $fld_count, 'File Count' => $file_cnt, 'Total Count' => ($fld_count + $file_cnt));	if (!empty($doc_count)) {		return $doc_count;	} else {		return false;	}}

An IMPLEMENTATION

$path = '../../somepath/';try {	if ($count_sum = rrmdir_override($path)) {		foreach($count_sum as $item => $count) {			echo "$item: $count<br />";		}	}} catch (Exception $e) {	echo 'User-Defined Exception Code: ', $e->getCode(); echo '<br />';	echo 'Error Message: ', $e->getMessage();}

Roddy

Link to comment
Share on other sites

I'm not sure I see why would you want to keep a counter on the deleted files and folders... that's why I don't have such a thing in my function. It would be trivial to add if you really want it though.And attempting to set the file permissions... I'd add that as a third setting that would let the user decide if he wants to force it. I mean, sometimes, you may decline writing/deleting privilages for a good reason.Also, keep in mind that call by reference is now deprecated in PHP (since PHP 5.3... I think). You should declare your function as accepting the arguments by reference instead.

Link to comment
Share on other sites

I'm not sure I see why would you want to keep a counter on the deleted files and folders... that's why I don't have such a thing in my function. It would be trivial to add if you really want it though.
Returning the number of deleted files and folders helps one keep track of what one has deleted, and one can always check whether the returned array is empty or not to determine success or failure.
And attempting to set the file permissions... I'd add that as a third setting that would let the user decide if he wants to force it. I mean, sometimes, you may decline writing/deleting privilages for a good reason.
I agree with this, and it is an easy parameter to add. I will call it the $mode parameter. The reason that I did not do it already, is because it took me some time to figure out what permissions would guarantee successful deletion. I can add this information as notes to the function's code.
Also, keep in mind that call by reference is now deprecated in PHP (since PHP 5.3... I think). You should declare your function as accepting the arguments by reference instead.
I do not understand the difference. Perhaps you could provide an example.I worked very hard to get my counters to work, so that no unwanted error messages would be produced, folders and files would be counted separately, and nothing was omitted. In short, everything in my scheme of things is necessary to make it work.Roddy
Link to comment
Share on other sites

When you're doing

rrmdir_override($member_path, &$fld_count, &$file_cnt)

You're having the variables $fld_count and $file_cnt passed by reference at call time. When you instead have your declaration like:

function rrmdir_override($path, &$fld_count, &$file_cnt)

you're having the variables $fld_count and $file_cnt passed by reference at the function declaration, after which you call the function like:

rrmdir_override($member_path, $fld_count, $file_cnt)

Since the function declared to take its arguments by reference, the effect of both approaches is pretty much equivalent. However, the passing by reference at call time is deprecated, because its behaviour is undefined (therefore unpredictable) for functions that were not meant to take values by reference.See the manual on passing by reference for other examples.

Link to comment
Share on other sites

I can surely agree with this.
rrmdir_override($member_path, &$fld_count, &$file_cnt)

I cannot agree with this

function rrmdir_override($path, &$fld_count, &$file_cnt)

Please look again, and you will find

function rrmdir_override($path, $fld_count=0, $file_cnt=0)

In any case I will check the manual, as you suggest. I was not aware of any deprecation in this regard, and it is the only way I figure out to make the counters work in a recursive environment.

See the manual on passing by reference for other examples.
Roddy
Link to comment
Share on other sites

I cannot agree with thisCODEfunction rrmdir_override($path, &$fld_count, &$file_cnt)
What do you mean you can't "agree" with that? It's not making any assertions, that's the way you declare a function to accept arguments as references. You should change your declaration to this:
function rrmdir_override($path, &$fld_count=0, &$file_cnt=0)

Note: There is no reference sign on a function call - only on function definitions. Function definitions alone are enough to correctly pass the argument by reference. As of PHP 5.3.0, you will get a warning saying that "call-time pass-by-reference" is deprecated when you use & in foo(&$a);.
Link to comment
Share on other sites

I think the lack of default values was the part that was not making sence... I haven't really done any reference accepting functions lately, so I didn't know if one is allowed to give default values for arguments passed by reference. I was planning to try it, but I guess I won't have to :) .

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...