Skip to content

Instantly share code, notes, and snippets.

@UziTech
Last active October 20, 2024 10:13
Show Gist options
  • Save UziTech/3b65b2543cee57cd6d2ecfcccf846f20 to your computer and use it in GitHub Desktop.
Save UziTech/3b65b2543cee57cd6d2ecfcccf846f20 to your computer and use it in GitHub Desktop.
Recursive glob search in php
/*
* License: DWTFYW
*/
/**
* Search recusively for files in a base directory matching a glob pattern.
* The `GLOB_NOCHECK` flag has no effect.
*
* @param string $base Directory to search
* @param string $pattern Glob pattern to match files
* @param int $flags Glob flags from https://www.php.net/manual/function.glob.php
* @return string[] Array of files matching the pattern
*/
function glob_recursive($base, $pattern, $flags = 0) {
$flags = $flags & ~GLOB_NOCHECK;
if (substr($base, -1) !== DIRECTORY_SEPARATOR) {
$base .= DIRECTORY_SEPARATOR;
}
$files = glob($base.$pattern, $flags);
if (!is_array($files)) {
$files = [];
}
$dirs = glob($base.'*', GLOB_ONLYDIR|GLOB_NOSORT|GLOB_MARK);
if (!is_array($dirs)) {
return $files;
}
foreach ($dirs as $dir) {
$dirFiles = glob_recursive($dir, $pattern, $flags);
$files = array_merge($files, $dirFiles);
}
return $files;
}
@UziTech
Copy link
Author

UziTech commented Mar 26, 2021

Also GLOB_NOCHECK still returns an array with the pattern as a string not just the string pattern.

glob("*.txt", GLOB_NOCHECK)
// returns ["*.txt"] if no files found not "*.txt"

@UziTech
Copy link
Author

UziTech commented Mar 26, 2021

Here is a version that works with GLOB_NOCHECK

function glob_recursive($base, $pattern, $flags = 0) {
	$glob_nocheck = $flags & GLOB_NOCHECK;
	$flags = $flags & ~GLOB_NOCHECK;

	function check_folder($base, $pattern, $flags) {
		if (substr($base, -1) !== DIRECTORY_SEPARATOR) {
			$base .= DIRECTORY_SEPARATOR;
		}

		$files = glob($base.$pattern, $flags);
		if (!is_array($files)) {
			$files = [];
		}

		$dirs = glob($base.'*', GLOB_ONLYDIR|GLOB_NOSORT|GLOB_MARK);
		if (!is_array($dirs)) {
			return $files;
		}

		foreach ($dirs as $dir) {
			$dirFiles = check_folder($dir, $pattern, $flags);
			$files = array_merge($files, $dirFiles);
		}
		
		return $files;
	}
	$files = check_folder($base, $pattern, $flags);

	if ($glob_nocheck && count($files) === 0) {
		return [$pattern];
	}
	
	return $files;
}

@Alys-dev
Copy link

Hi there, I used your marvelous second function glob_recursive, but, I need to say there is an issue when we want using the function glob_recursive twice or more per php run execution.

that one

The issue lead to a FATAL ERROR: Fatal error: Cannot redeclare check_folder() (previously declared in ....

SO... I found where is the issue:
you used a declaration of a sub-function in your second version of glob_recursive(), then unlike class methods, functions (or sub-functions are exported to a global namespace (OR) custom local and of course you certainly know function are not class).
sub-function (I call them like that but it's just a function that will be declared once the main parent function will be called).. cause an issue because once we call ONE time glob_recursive() the function is registered in the PHP execution... (meant : declared!)
finally if we call a second time glob_recursive() it lead to a force re-declare check_folder() function as it is a part of glob_recursive().

The fix is easy, (and I think you just forgot it, as your function is amazing):

Before line:
function check_folder($base, $pattern, $flags) {

Add just:
if (!function_exists('check_folder')) {

And we now must close the newly added "if" statement, so ...

Before line:
$files = check_folder($base, $pattern, $flags);

Add just an another :
}

And now, the glob_recursive() function can be called infinite amount of attempts !

By the way, thanks for the function !!

@zkelo
Copy link

zkelo commented Oct 20, 2024

Thanks for this function! Do you have a package with it for composer?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment