Skip to content

Instantly share code, notes, and snippets.

@timint
Created August 3, 2022 14:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timint/bd69982da4bc8756ff8c4eea92640b23 to your computer and use it in GitHub Desktop.
Save timint/bd69982da4bc8756ff8c4eea92640b23 to your computer and use it in GitHub Desktop.
PHP glob() reinvented with support for StreamWrappers and dual globstars
<?php
// PHP glob() does not support stream wrappers, so let's create our own glob.
// And while we are at it, let's throw in support for double globstars **. :)
function file_search($glob, $flags=0) {
// Unixify paths
$glob = str_replace('\\', '/', $glob);
// Set basedir and remains
$basedir = '';
$remains = $glob;
for ($i=0; $i<strlen($glob); $i++) {
if (in_array($glob[$i], ['*', '[', ']', '{', '}'])) break;
if ($glob[$i] == '/') {
@list($basedir, $remains) = str_split($glob, $i+1);
}
}
// Halt if basedir does not exist
if ($basedir && !is_dir($basedir)) {
return [];
}
// If there are no pattern remains, return base directory if valid
if (!$remains) {
if (is_dir($basedir)) {
return [$basedir];
} else {
return [];
}
}
// Extract pattern for current directory
if (($pos = strpos($remains, '/')) !== false) {
list($pattern, $remains) = [substr($remains, 0, $pos+1), substr($remains, $pos+1)];
} else {
list($pattern, $remains) = [$remains, ''];
}
// fnmatch() doesn't support GLOB_BRACE. Let's create a regex pattern instead.
$regex = strtr($pattern, [
'[!' => '[^',
'\\' => '\\\\',
'.' => '\\.',
'(' => '\\(',
')' => '\\)',
'|' => '\\|',
'+' => '\\+',
'^' => '\\^',
'$' => '\\$',
'*' => '[^/]*',
'**' => '.*',
'?' => '.',
]);
if ($flags & GLOB_BRACE) {
$regex = preg_replace_callback('#\{[^\}]+\}#', function($matches) {
return strtr($matches[0], ['{' => '(', '}' => ')', ',' => '|']);
}, $regex);
} else {
$regex = strtr($regex, ['{' => '\\{', '}' => '\\}']);
}
$regex = '#^'.$regex.'$#';
$results = [];
$files = [];
// Open directory
$dh = opendir($basedir ? $basedir : './');
// Step through each file in directory
while ($file = readdir($dh)) {
if (in_array($file, ['.', '..'])) continue;
// Prepend path
$file = $basedir . $file;
$filetype = filetype($file);
if ($filetype == 'dir') {
// Resolve double globstars
if (strpos($pattern, '**') !== false) {
$results = array_merge($results, file_search($file .'/'. $pattern . $remains, $flags));
}
// Collect a matching folder
if (preg_match($regex, basename($file)) || preg_match($regex, basename($file).'/')) {
if ($remains) {
$results = array_merge($results, file_search($file .'/'. $remains, $flags));
} else {
$results[] = $file .'/';
}
}
} else if ($filetype == 'file') {
// Skip if not a directory during GLOB_ONLYDIR
if ($flags & GLOB_ONLYDIR) continue;
// Collect a matching file
if (preg_match($regex, basename($file))) {
$files[] = $file;
}
}
}
// Merge folders and files into one and same result
$results = array_merge($results, $files);
return $results;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment