开发者

PHP Case Insensitive Version of file_exists()

开发者 https://www.devze.com 2023-01-21 10:00 出处:网络
I\'m trying to think of the fastest way to implement a case insensitive file_exists function in PHP. Is my best bet to enumerate the file in the directory and do a strtolower() t开发者_如何学Goo strto

I'm trying to think of the fastest way to implement a case insensitive file_exists function in PHP. Is my best bet to enumerate the file in the directory and do a strtolower() t开发者_如何学Goo strtolower() comparison until a match is found?


I used the source from the comments to create this function. Returns the full path file if found, FALSE if not.

Does not work case-insensitively on directory names in the filename.

function fileExists($fileName, $caseSensitive = true) {

    if(file_exists($fileName)) {
        return $fileName;
    }
    if($caseSensitive) return false;

    // Handle case insensitive requests            
    $directoryName = dirname($fileName);
    $fileArray = glob($directoryName . '/*', GLOB_NOSORT);
    $fileNameLowerCase = strtolower($fileName);
    foreach($fileArray as $file) {
        if(strtolower($file) == $fileNameLowerCase) {
            return $file;
        }
    }
    return false;
}


This question is a few years old but it is linked to several as duplicates, so here is a simple method.

Returns false if the $filename in any case is not found in the $path or the actual filename of the first file returned by glob() if it was found in any case:

$result = current(preg_grep("/^".preg_quote($filename)."$/i", glob("$path/*")));
  • Get all files in the path glob
  • Grep for the $filename in any case i is case-insensitive
  • current returns the first filename from the array

Remove the current() to return all matching files. This is important on case-sensitive filesystems as IMAGE.jpg and image.JPG can both exist.


In Unix file names are case sensitive, so you won't be able to do a case insensitive existence check without listing the contents of the directory.


Your approach works.
Alternatively you can use glob to get the list of all files and directories in the present working directory in an array, use array_map to apply strtolower to each element and then use in_array to check if your file(after applying strtolower) exists in the array.


I ran into the same issue when we migrated from IIS to apache. Below is the piece I whipped up. It returns either the correct path as a string or false.

function resolve_path($path)
{
    $is_absolute_path = substr($path, 0, 1) == '/';
    $resolved_path = $is_absolute_path ? '/' : './';
    $path_parts = explode('/', strtolower($path));

    foreach ($path_parts as $part)
    {
        if (!empty($part))
        {
            $files = scandir($resolved_path);

            $match_found = FALSE;

            foreach ($files as $file)
            {
                if (strtolower($file) == $part)
                {
                    $match_found = TRUE;

                    $resolved_path .= $file . '/';
                }
            }

            if (!$match_found)
            {
                return FALSE;
            }
        }
    }

    if (!is_dir($resolved_path) && !is_file($resolved_path))
    {
        $resolved_path = substr($resolved_path, 0, strlen($resolved_path) - 1);
    }

    $resolved_path = $is_absolute_path ? $resolved_path : substr($resolved_path, 2, strlen($resolved_path));

    return $resolved_path;
}

$relative_path = substr($_SERVER['REQUEST_URI'], 1, strlen($_SERVER['REQUEST_URI']));
$resolved_path = resolve_path($relative_path);

if ($resolved_path)
{
    header('Location: http://' . $_SERVER['SERVER_NAME'] . '/' . $resolved_path);
    die();
}


AbraCadaver's answer with +7 rating is incorrect, I do not have enough reputation to comment under it, so here is correct solution, based on his answer:

$result = count(preg_grep('/\/'.preg_quote($filename)."$/i", glob("$path/*")));

AbraCadaver's answer is incorrect, because it returns true if you test against file foo.jpg and files like anytext_foo.jpg exist.


I tuned the function a lil bit more. guess this better for use

function fileExists( $fileName, $fullpath = false, $caseInsensitive = false ) 
{
    // Presets
    $status         = false;
    $directoryName  = dirname( $fileName );
    $fileArray      = glob( $directoryName . '/*', GLOB_NOSORT );
    $i              = ( $caseInsensitive ) ? "i" : "";

    // Stringcheck
    if ( preg_match( "/\\\|\//", $fileName) ) // Check if \ is in the string
    {
        $array    = preg_split("/\\\|\//", $fileName);
        $fileName = $array[ count( $array ) -1 ];
    }

    // Compare String
    foreach ( $fileArray  AS $file )
    {
        if(preg_match("/{$fileName}/{$i}", $file))
        {
            $output = "{$directoryName}/{$fileName}";
            $status = true;
            break;
        }
    }

    // Show full path
    if( $fullpath && $status )
        $status = $output;

    // Return the result [true/false/fullpath (only if result isn't false)]
    return $status;
}


For a pure PHP implementation, yes. There's an example in the comments for the file_exists function.

The other option would be to run your script on a case insensitive file system.


I have improved John Himmelman's function and come up with this:
suppose that i have a catch system \iMVC\kernel\caching\fileCache

function resolve_path($path)
{
    # check if string is valid
    if(!strlen($path)) return FALSE;
    # a primary check
    if(file_exists($path)) return $path;
    # create a cache signiture
    $cache_sig = __METHOD__."@$path";
    # open the cache file
    $fc = new \iMVC\kernel\caching\fileCache(__CLASS__);
    # check cache file and validate it
    if($fc->isCached($cache_sig) && file_exists($fc->retrieve($cache_sig)))
    {
        # it was a HIT!
        return $fc->retrieve($cache_sig);
    }
    # if it is ab
    $is_absolute_path = ($path[0] == DIRECTORY_SEPARATOR);
    # depart the path
    $path_parts = array_filter(explode(DIRECTORY_SEPARATOR, strtolower($path)));
    # normalizing array's parts
    $path_parts = count($path_parts)? array_chunk($path_parts, count($path_parts)) : array();
    $path_parts = count($path_parts[0])?$path_parts[0]:array();
    # UNIX fs style
    $resolved_path = $is_absolute_path ? DIRECTORY_SEPARATOR : ".";
    # WINNT fs style
    if(string::Contains($path_parts[0], ":"))
    {
        $is_absolute_path = 1;
        $resolved_path = $is_absolute_path ? "" : ".".DIRECTORY_SEPARATOR;
    }
    # do a BFS in subdirz
    foreach ($path_parts as $part)
    {
        if (!empty($part))
        {
            $target_path = $resolved_path.DIRECTORY_SEPARATOR.$part;
            if(file_exists($target_path))
            {
                $resolved_path = $target_path;
                continue;
            }
            $files = scandir($resolved_path);

            $match_found = FALSE;

            foreach ($files as $file)
            {   
                if (strtolower($file) == $part)
                {
                    $match_found = TRUE;
                    $resolved_path = $resolved_path.DIRECTORY_SEPARATOR.$file;
                    break;
                }
            }
            if (!$match_found)
            {
                return FALSE;
            }
        }
    }
    # cache the result
    $fc->store($target_path, $resolved_path);
    # retrun the resolved path
    return $resolved_path;
}


Having found this page from a quick google I used Kirk's solution, however it's slow if you call it multiple times on the same directory, or on a directory that has many files in. This is due to it looping over all the files each time, so I optimised it a little:

function fileExists($fileName) {
    static $dirList = [];
    if(file_exists($fileName)) {
        return true;
    }
    $directoryName = dirname($fileName);
    if (!isset($dirList[$directoryName])) {
        $fileArray = glob($directoryName . '/*', GLOB_NOSORT);
        $dirListEntry = [];
        foreach ($fileArray as $file) {
            $dirListEntry[strtolower($file)] = true;
        }
        $dirList[$directoryName] = $dirListEntry;
    }
    return isset($dirList[$directoryName][strtolower($fileName)]);
}

I dropped the flag to check for case insensitivity as I assume you'd just use file_exists if you didn't need this behaviour, so the flag seemed redundant. I also expect that if you're doing anything beyond a trivial script you'd want to turn this into a class to get more control over the directory list caching, e.g. resetting it, but that's beyond the scope of what I needed and it should be trivial to do if you need it.


My tuned solution, OS independent, case-insensitive realpath() alternative, covering whole path, named realpathi():

/**
 * Case-insensitive realpath()
 * @param string $path
 * @return string|false
 */
function realpathi($path)
{
    $me = __METHOD__;

    $path = rtrim(preg_replace('#[/\\\\]+#', DIRECTORY_SEPARATOR, $path), DIRECTORY_SEPARATOR);
    $realPath = realpath($path);
    if ($realPath !== false) {
        return $realPath;
    }

    $dir = dirname($path);
    if ($dir === $path) {
        return false;
    }
    $dir = $me($dir);
    if ($dir === false) {
        return false;
    }

    $search = strtolower(basename($path));
    $pattern = '';
    for ($pos = 0; $pos < strlen($search); $pos++) {
        $pattern .= sprintf('[%s%s]', $search[$pos], strtoupper($search[$pos]));
    }
    return current(glob($dir . DIRECTORY_SEPARATOR . $pattern));
}

search filename with glob [nN][aA][mM][eE] pattern seems to be the faster solution


//will resolve & print the real filename
$path = "CaseInsensitiveFiLENAME.eXt";
$dir  = "nameOfDirectory";

if ($handle = opendir($dir)) {
 while (false !== ($entry = readdir($handle))) {
     if (strtolower($path) == strtolower($entry)){
       echo $entry ;
    }}
    closedir($handle);
}


Just ran across this today, but didn't like any of the answers here, so I thought I would add my solution ( using SPL and the regex iterator )

function _file_exists( $pathname ){
    if(file_exists($pathname)) return $pathname;

    try{
        $path = dirname( $pathname );
        $file = basename( $pathname );

        $Dir = new \FilesystemIterator( $path, \FilesystemIterator::UNIX_PATHS );
        $regX = new \RegexIterator($Dir, '/(.+\/'.preg_quote( $file ).')$/i', \RegexIterator::MATCH);

        foreach ( $regX as $p ) return $p->getPathname();

    }catch (\UnexpectedValueException $e ){
        //invalid path
    }
    return false;
}

The way I am using it is like so:

 $filepath = 'path/to/file.php';

 if( false !== ( $filepath = _file_exists( $filepath ))){
      //do something with $filepath
 }

This way it will use the built in one first, if that fails it will use the insensitive one, and assign the proper casing to the $filepath variable.


The other answers might be very resource intensive on large file systems (large number of files to search through). It might be useful to create a temporary table of all the filenames (full path if necessary). Then do a like condition search of that table to get whatever the actual case is.

SELECT actual_file_name
FROM TABLE_NAME
WHERE actual_file_name LIKE 'filename_i_want'
0

精彩评论

暂无评论...
验证码 换一张
取 消