为什么Windows需要使用`utf8_decode`函数来解码文件名以使`file_get_contents`函数正常工作?

7
如果$filename包含德语元音字母(ä, ö, ü),在我的Windows操作系统中file_get_contents($filename)无法正常工作。通过试错,我发现需要使用file_get_contents(utf8_decode($filename))才能让它正常工作。
然而,当我将其部署到服务器上(猜测是某种Linux),它再次返回错误,所以我删除了utf8_decode,突然间它完美地工作了。
为了解决这个问题(使我不需要每次更改代码时手动更改此部分代码),我已经尝试过:
(mb_detect_encoding($filename, 'UTF-8', true)) ? utf8_decode$filename) : $filename;

由于同样的问题已经通过utf8_encode得到解决,但是无论在哪个(服务器)环境中,$filename都是UTF8编码,所以这种解决方法行不通,因为它总是正确的。

是否有任何想法可以使这两个系统都能正常工作?(请不要“只是为了PHP开发而迁移到Linux” - 我有Linux,但是出于许多原因,我目前正在使用Windows)


编辑:使用fopen也会出现此问题,接受的解决方法也有效。


1
你的最后几行非常吸引人 :) - swapnesh
不要在文件名中使用非ASCII字符...这总会引起问题。 - Mark Baker
我担心作为一个讲德语的人,我习惯于在标题中使用umlauts(这些umlauts又用作文件名)。 说真的,难道没有解决方案吗?否则,这个“项目”将变得非常复杂(而且我只是在写一个超级简单的博客)。 - Peter
问题在于平台的操作系统上,没有一个干净的答案可以跨平台工作,这就是为什么我永远不会推荐它。 - Mark Baker
2个回答

3
最好的方法是检测您是否正在使用Windows服务器。从那里,您可以应用正确的命令。
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
    echo 'This is a server using Windows!';
} else {
    echo 'This is a server not using Windows!';
}

3

问题出在Windows文件系统不支持UTF-8的文件名或目录名,但Linux在php 6中支持UTF-8,因此这个问题得到了解决。为了解决当前版本的php中的这个问题,我们可以进行一些工作,例如:

1)Windows支持UTF-16,Linux对这种Unicode没有任何问题,我们可以将所有文件名和目录名保存为这种Unicode格式。

2)使用urlencode编码UTF-8,这是另一种解决此问题的方法,我建议使用这种方法。

我编写了这个脚本来解决这个问题,请测试它。

 function GetExt($sFileName)//ffilter
 {
     $sExt="";
     $sTmp=$sFileName;
     while($sTmp!="")
     {
        $sTmp=strstr($sTmp,".");
        if($sTmp!="")
        {
            $sTmp=substr($sTmp,1);
            $sExt=$sTmp;
        }
     }
     return ($sExt);
 }
  function LocatePath($Path='/',$Mode="rb",$Root="",$Open=true,$IsFile=false){//make real Path and create new Directory
     switch(strtolower($Mode)){
          case 'r':
            $Read=true;
            $Write=false;
            $Create=false;
          break;
          case 'rb'://Open for reading only; place the file pointer at the beginning of the file. 
            $Read=true;
            $Write=false;
            $Create=false;
          break;
          case 'r+'://Open for reading and writing; place the file pointer at the beginning of the file.
            $Read=true;
            $Write=true;
            $Create=false;
          break;
          case 'x'://Create and open for writing only; place the file pointer at the beginning of the file. If the file already exists, the open() call will fail by returning FALSE
            $Read=false;
            $Write=true;
            $Create=false;
          break;
          case 'wb':
            $Read=false;
            $Write=true;
            $Create=true;
          break;
          case 'ab':
            $Read=false;
            $Write=true;
            $Create=true;
          break;
          case 'w'://Open for writing only; place the file pointer at the beginning of the file and truncate the file to zero length. If the file does not exist, attempt to create it. 
            $Read=false;
            $Write=true;
            $Create=true;
          break;
          case 'w+'://Open for reading and writing; place the file pointer at the beginning of the file and truncate the file to zero length. If the file does not exist, attempt to create it. 
            $Read=true;
            $Write=true;
            $Create=true;
          break;
          case 'a'://Open for writing only; place the file pointer at the end of the file. If the file does not exist, attempt to create it. 
            $Read=false;
            $Write=true;
            $Create=true;
          break;
          case 'a+'://Open for reading and writing; place the file pointer at the end of the file. If the file does not exist, attempt to create it. 
            $Read=true;
            $Write=true;
            $Create=true;
          break;
          case 'x+'://Create and open for reading and writing; otherwise it has the same behavior as 'x'. 
            $Read=true;
            $Write=true;
            $Create=true;
          break;
          case 'c'://Open the file for writing only. If the file does not exist, it is created. If it exists, it is neither truncated (as opposed to 'w'), nor the call to this function fails (as is the case with 'x'). The file pointer is positioned on the beginning of the file.
            $Read=false;
            $Write=true;
            $Create=true;
          break;
          case 'c+'://Open the file for reading and writing; otherwise it has the same behavior as 'c'. 
            $Read=true;
            $Write=true;
            $Create=true;            
     }
     $Path=str_replace("./",'/',trim($Path));
     $Path=str_replace("../",'/',$Path);
     $Path=str_replace(".../",'/',$Path);
     $Path=ltrim($Path,'/');
     if($Path==NULL or $Path=="")$Path="/";
     $DOCUMENT_ROOT=$_SERVER['DOCUMENT_ROOT'];
     $FileRoot=$DOCUMENT_ROOT."/".$Root;
     $FileRoot=str_replace("\\",'/',$FileRoot);
     $FileRoot=rtrim(str_replace('//','/',$FileRoot),'/');
     $TMkDir=0;
     $ISDir=true;
     $RelativePath=$Path;
     $Type=GetExt($FileRoot."/".$Path);
     if($Type!=""){
         $FileName=basename($FileRoot."/".$Path);
     }
     $ParentDir=dirname($Path);
     $T=urlencode($FileRoot."/".$Path);
     $T=str_replace("%3A",':',$T);
     $T=str_replace("%2F",'/',$T);
     if(is_file($T)){
         if($IsFile){
             return array("ISDir"=>false,"Handle"=>false,"Path"=>$T,'FileName'=>$FileName,"Type"=>$Type,"RelativePath"=>$RelativePath,'ParentDir'=>$ParentDir,'Read'=>$Read,'Write'=>$Write);
         }
         $Handle=fopen($T,$Mode);
         return array("ISDir"=>false,"Handle"=>$Handle,"Path"=>$T,'FileName'=>$FileName,"Type"=>$Type,"RelativePath"=>$RelativePath,'ParentDir'=>$ParentDir,'Read'=>$Read,'Write'=>$Write);
     }
     if(is_dir($T)){
         return array("ISDir"=>true,"Handle"=>NULL,"Path"=>$T,"RelativePath"=>$RelativePath,'ParentDir'=>$ParentDir,'Read'=>$Read,'Write'=>$Write);
     }
     $PathSplit=explode("/",$Path);
     if($Create==true){
         try{
            foreach($PathSplit as $PartDir){
               $TPartDir=$PartDir;
               $PartDir=urlencode($PartDir);
               $Temp=$FileRoot."/".$PartDir;
               if(!is_file($Temp) and $TPartDir!=$FileName){
                  if(!is_dir($Temp)){
                     mkdir($Temp);
                  }
               }else{
                   $ISDir=false;
                   $Temp=$FileRoot."/".$FileName;
                   $Handle=fopen($Temp,$Mode);
                   $FileRoot=$FileRoot."/".$PartDir;    
                   break;
               }
               $FileRoot=$FileRoot."/".$PartDir;    

            }
         }catch(Extension $e){
             echo ($e);
             return false;
         }
     }else{
         try{
            foreach($PathSplit as $PartDir){
               $TPartDir=$PartDir;
               $PartDir=urlencode($PartDir);
               $Temp=$FileRoot."/".$PartDir;
               if(!is_file($Temp) and $TPartDir!=$FileName){
                  if(!is_dir($Temp)){
                     return false;
                  }
               }else{
                   $ISDir=false;
                   $Handle=fopen($Temp,$Mode);
                   $FileRoot=$FileRoot."/".$PartDir;    
                   break;
               }
               $FileRoot=$FileRoot."/".$PartDir;    
            }
         }catch(Extension $e){
             echo ($e);
             return false;
         } 
     }
     if($Open!=true){//keep open Handle for User
        fclose($Handle); 
     }
     return array("ISDir"=>$ISDir,"Handle"=>$Handle,"Path"=>$FileRoot,'FileName'=>$FileName,"Type"=>$Type,"Create"=>(!$ISDir)?true:false,"RelativePath"=>$RelativePath,'ParentDir'=>$ParentDir,'Read'=>$Read,'Write'=>$Write);
 }

样例:

打开文件进行读写,如果目录或文件不存在则创建。 1)

 print_r(LocatePath('/er/ert/aیgfسبd/ی.af',"w+")); 

返回类似于fopen句柄的一些信息

输出

Array
(
[ISDir] => 
[Handle] => Resource id #3
[Path] => F:/xampp/htdocs/er/ert/a%DB%8Cgf%D8%B3%D8%A8d/%DB%8C.af
[FileName] => ی.af
[Type] => af
[Create] => 1
[RelativePath] => er/ert/aیgfسبd/ی.af
[ParentDir] => er/ert/aیgfسبd
[Read] => 1
[Write] => 1
)

2)如果文件不存在,则打开读取文件并返回false。

 print_r(LocatePath('/er/ert/aیgfسبd/ی.af',"rb")); 

还有一种与fopen相同的模式


非常感谢您提供如此详尽的答案,但我想这对于我的小博客来说可能有些过头了。 - Peter
此外,我不确定 LocatePath() 的目的是什么。它确实会给你一堆信息,但它不是以我所知道的任何格式传递给其他函数。 - user149341

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接