Tested in a number of configurations on a number of file and symlink combinations. Written for PHP 5.3+, though should work in earlier versions if you change "$home = $_ENV['HOME'] ?: '.';" to "$home = $_ENV['HOME'] ? $_ENV['HOME'] : '.';". Designed for Linux/Unix, though probably works on Windows if you specify true for $semiColon - not tested though.
No licence. Free to use or change without credit. If you find this useful, it would be nice to hear from you in this thread.
PHP Code:
/**
* Searches a path list for the requested filename, with the requested permissions. Returns the first match.
*
* @param string $name
* Filename to find
*
* @param string $path
* Colon separated path list. If omitted or null, $_ENV['path'] is used. This will use realpath() to translate
* individual paths, and translate ~/ at the start of a path to $_ENV['home']. If no portions of this are
* valid paths, an E_USER_WARNING is triggered.
*
* @param bool $readable
* If true, only files that are readable will match
*
* @param bool $writable
* If true, only files that are writable will match
*
* @param bool $executable
* If true, only files that are executable will match
*
* @param bool $semiColon
* If true, uses the Windows path separator instead
*
* @return bool|string
* Either the full, real path without a trailing "/" or false if it couldn't be found
*/
function find_file($name, $path = null, $readable = true, $writable = false, $executable = false, $semiColon = false) {
// Handle default path
if (is_null($path)) {
$path = $_ENV['PATH'];
}
// Get a value for home, defaulting to cwd
$home = $_ENV['HOME'] ?: '.';
// Split the path
$paths = explode(($semiColon ? ';' : ':'), $path);
// Setup to detect fully invalid $path for a E_USER_WARNING
$hasValid = false;
foreach($paths as $pathItem) {
// realpath() doesn't support ~, so compensate for that
if (($pathItem === '~') || (substr($pathItem, 0, 2) === '~/')) {
$pathItem = $home . '/' . substr($pathItem, 2);
}
// Check this is a valid path
if (($pathItem === '') || !is_dir($pathItem)) {
continue;
}
// We got here, it's a valid path
$hasValid = true;
// Setup fullName and pathItem with the real, full paths
$fullName = ($pathItem = realpath($pathItem)) . '/' . $name;
// Check criteria and return first match
if (file_exists($fullName) && !is_dir($fullName)
&& (!$readable || is_readable($fullName))
&& (!$writable || is_writable($fullName))
&& (!$executable || is_executable($fullName))
) {
return $pathItem;
}
}
// If $path had no valid paths, trigger a warning
if (!$hasValid) {
trigger_error('No valid paths given', E_USER_WARNING);
}
return false;
}
Examples of use:
Find a readable my.cnf in some likely locations:
PHP Code:
find_file('my.cnf', '/etc:/etc/mysql:/etc/MySQL:~:~/mysql');
Find an executable convert in the system provided path:
PHP Code:
find_file('convert', null, true, false, true);