Created
August 3, 2022 14:22
-
-
Save timint/bd69982da4bc8756ff8c4eea92640b23 to your computer and use it in GitHub Desktop.
PHP glob() reinvented with support for StreamWrappers and dual globstars
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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