前幾天我們公司伺服器出現了一個離奇的問題,伺服器與本地檔案程式碼完全一致,本地執行正常,到了測試環境伺服器之後,各種問題一個又一個浮現,先是後臺驗證碼不顯示,以為是session寫入失敗,又是懷疑gd庫,又是覺得伺服器gd路徑錯誤,又排查目錄許可權,各種方法試之無效,百度必應各種搜尋,整個公司一半以上PHP排查問題,我們之前的程式碼如下
public function createImage()
{
$word = $this -> randomCode();
// 記錄字串
$_SESSION[$this -> _space]['code'] = base64_encode($this -> encryptsCode($word));
$this -> image = ImageCreate($this -> _width, $this -> _height);
ImageColorAllocate($this -> image, 220, 220, 220);
// 在圖片上新增擾亂元素
$this -> disturbPixel();
// 在圖片上新增字串
$this -> drawCode($word);
ob_end_clean(); ------------------------------------伺服器檔案上解決問題僅僅加了這麼一行函式
ob_clean(); //關鍵程式碼,防止出現'影象因其本身有錯無法顯示'的問題。
header("Content-type:text/html;charset=utf-8"); // 設定頁面的編碼風格
header("Content-type: image/PNG");
ImagePng($this -> image);
ImageDestroy($this -> image);
}
言歸正傳,雖然這個問題解決了,但是購物車好好地失效了,原因是我們前端發現很多檔案都帶了BOM頭上去,經過百度各種帖子,找到了檢查BOM檔案的方法
本地檔案上傳到伺服器上,某些檔案頭部總是出現一條空白,無論怎麼修改檔案都無法去除空白,用firebug檢視header部分同樣有一片空白,刪除後空白消失,但是在檔案裡卻無法找到那個空白的部分
BOM頭
BOM: Byte Order Mark
UTF-8 BOM又叫UTF-8 簽名,其實UTF-8 的BOM對UFT-8沒有作用,是為了支援UTF-16,UTF-32才加上的BOM,BOM簽名的意思就是告訴編輯器當前檔案採用何種編碼,方便編輯器識別,但是BOM雖然在編輯器中不顯示,但是會產生輸出,就像多了一個空行
這些大部分是編輯器的問題,PHP檔案採用UTF-8編碼,PHP開發大部分使用的文字編輯軟體如:Zend studio、editplus、eclipse等等都可以顯示並編輯UTF-8編碼的檔案,但是也有一些軟體不能滿足這個要求.
類似如windows的記事本,在儲存一個以UTF-8編碼的檔案時,會在檔案開始的地方插入三個不可見的字元(0xEF 0xBB 0xBF,即BOM).它是一串隱藏的字元,用於讓記事本等編輯器識別這個檔案是否以UTF-8編碼.對於一般的檔案,這樣並不會產生什麼麻煩.但對於 PHP來說,BOM是個大麻煩.
對於BOM,PHP並不會忽略,在讀取、包含或者引用這些檔案時,PHP會把BOM作為檔案開頭正文的一部分,根據嵌入式語言的特點,這串字元將被直接執行(顯示)出來.這就導致了一些頁面的頭部總是有一條白條,儘管樣式padding、margin等各方面都設定好也無法讓整個網頁緊貼瀏覽器頂部,這頭部白條就是這3個不可見的字元(0xEF 0xBB 0xBF,即BOM);
另外還有的問題就是,受COOKIE送出機制的限制,在這些檔案開頭已經有BOM的檔案中,COOKIE無法送出(因為在COOKIE送出前PHP已經送出了檔案頭),所以登入和登出功能失效.一切依賴COOKIE、SESSION實現的功能全部無效.
所以,在編輯、修改任何文字檔案的時候,請使用不會亂加BOM的編輯器.Linux下的編輯器應該都沒有這個問題.WINDOWS下,請勿使用記事本等編輯器.推薦使用Editplus,Zend studio、eclipse等編輯器.
其他的對於已經新增了BOM的檔案,要取消的話,可以用不會亂加BOM的編輯器另存一次.當然也可以使用以下方法去除該目錄下所有檔案的頭部BOM:
檢查哪些檔案存在BOM
<?php
/*檢測並清除BOM*/
$basedir = ROOT_PATH;
$auto = 1;
checkdir($basedir);
function checkdir($basedir){
if($dh = opendir($basedir)){
while(($file = readdir($dh)) !== false){
if($file != '.' && $file != '..'){
if(!is_dir($basedir."/".$file)){
echo "filename: $basedir/$file ".checkBOM("$basedir/$file")."
";
}else{
$dirname = $basedir."/".$file;
checkdir($dirname);
}
}
}//end while
closedir($dh);
}//end if($dh
}//end function
function checkBOM($filename){
global $auto;
$contents = file_get_contents($filename);
$charset[1] = substr($contents, 0, 1);
$charset[2] = substr($contents, 1, 1);
$charset[3] = substr($contents, 2, 1);
if(ord($charset[1]) == 239 && ord($charset[2]) == 187 && ord($charset[3]) == 191){
if($auto == 1){
$rest = substr($contents, 3);
rewrite ($filename, $rest);
return "";
}else{
return ("");
}
}
else return ("BOM Not Found.");
}//end function
function rewrite($filename, $data){
$filenum = fopen($filename, "w");
flock($filenum, LOCK_EX);
fwrite($filenum, $data);
fclose($filenum);
}//end function
然後百度一些工具,有專門清除BOM頭的,其次可以使用Notepad++來清除
補充:以上PHP程式碼可能會有遺漏,在用以上方法測試完成可以用一下一下方法
function printDir($d){
$dir=dir($d);
while(false != $row = $dir->read()){
if($row=='.' || $row=='..') continue;
if(is_dir($d.$row)){
printDir($d.$row.'/');
}else{
$f=fopen($d.$row,"r");
if($f){
$str=fgets($f,102);
if (ord($str{0}) == 239 && ord($str{1}) == 187 && ord($str{2}) == 191) {
echo $d.$row.'
';
}
}
fclose($f);
}
}
}
想要花哨一點兒,也可以這樣玩,不同的伺服器要區分路徑的格式問題
<?php
/**
* copyright (c) crossphp.cn
* author aray
* created 2009-07-25
*
**/
error_reporting(E_ALL & ~E_NOTICE);
$exts = array(
'.x',
'.html',
'.dwt',
'.lbi',
'.tpl',
'.php',
'.js',
'.css',
'.xml',
'.txt',
);
$start = false;
if ($_POST && !empty($_POST['path']) )
{
$start = true;
$PATH = $_POST['path'];
$PATH = addslashes($PATH);
$EXTS = $_POST['exts'];
}
function ReadDirs($path, $ext)
{
global $BOM;
echo $path;
$dir = opendir($path );
echo '<ul>';
while ( ($file = readdir($dir )))
{
if ($file == '.' || $file == '..') continue;
$f = $path . '/' . $file;
if (is_dir($f))
{
echo '<li class="folder"><span class="symbol">1</span>' . $file;
echo '<ul>' . ReadDirs($f, $ext) . '</ul></li>';
}
else
{
$flag = false;
if ( is_array($ext) )
{
if (! in_array(getExt($file), $ext) )
{
continue;
}
else
{
$flag = true;
}
}
else
{
$flag = true;
}
if ($flag)
{
$cssClass = 'file';
if (checkBOM($f))
{
$cssClass = 'bom';
$BOM[] = str_replace('//','/',str_replace('\\','/',$f));
}
echo '<li class="'.$cssClass.'"><span class="symbol">2</span>' . $file . $isBom . "</li>";
}
}
}
echo '</ul>';
}
function getExt($filename )
{
$ext = strrchr($filename,'.');
// 根本沒有副檔名
if ( empty($ext) )
{
return null;
}
return $ext;
}
function checkBOM($filename )
{
$contents = file_get_contents($filename);
$char[1] = substr($contents, 0, 1); // 1
$char[2] = substr($contents, 1, 1); // 2
$char[3] = substr($contents, 2, 1); // 3
// EF BB BF
if ( ord($char[1]) == 239 && ord($char[2]) == 187 && ord($char[3]) == 191 )
{
return true;
}
return false;
}
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>BOM檢測工具</title>
<style type="text/css">
body, td, th {
font-size: 14px;
}
body {
margin-left: 15%;
margin-top: 2px;
margin-right: 15%;
margin-bottom: 2px;
}
form {
margin: 0px;
padding: 0px;
}
ul {
margin: 0px 0px 0px auto;
padding: 0px;
}
.symbol {
font-family: Wingdings;
font-size: 20px;
padding-right: 10px;
}
.path {
color: #0033CC;
}
li {
color: #333333;
list-style: none;
}
.bom {
color: #ff00ff;
}
.folder {
color: #0000ff;
}
.file {
color: #333333;
}
</style>
</head>
<body>
<br/>
<br/>
<table width="100%" border="0" align="center" cellpadding="0" cellspacing="1" bgcolor="#8DAFDA">
<tr>
<td height="70" align="center" valign="middle" bgcolor="#CBDBEE"><form id="form1" name="form1" method="post" action="">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td height="30" align="left" valign="middle"> 資料夾:</td>
<td align="left" valign="middle"><select name="path" >
<?php $dir = opendir(dirname(dirname(__FILE__))); while ( ($f = readdir($dir)) )
{ if ($f == '..' || is_file('./' . $f) ) continue; ?>
<option value="../<?=$f?>" <?php if($_POST['path'] == '.\\'.$f) echo 'selected';?> >
<?=$f?>
</option>
<?php }?>
</select></td>
<td align="left" valign="middle"><input type="submit" name="button" id="button" value="提交" /></td>
</tr>
</table>
<?php foreach($exts as $ext){ ?>
<label>
<input type="checkbox" name="exts[]" value="<?=$ext?>"
<?php if(is_array($_POST['exts']) && in_array($ext, $_POST['exts'])) echo 'checked'; ?> />
<?=$ext?>
</label>
<?php }?>
</form></td>
</tr>
</table>
<div id="result"><br/>
<br/>
<?php if($start){?>
<?php echo '搜尋路徑:
<span class="path">' . str_replace('\\\\','\\',$PATH) . '</span> ,
實際路徑: <span class="path">' . realpath($PATH) . '</span><br/>'; echo '檔案列表: ';
ReadDirs( $PATH, $EXTS);?>
<br/>
<br/>
<?php if ($BOM) { ?>
發現BOM檔案列表:<br/>
<ul>
<?php foreach( $BOM as $f){?>
<li class="bom">
<?=$f?>
</li>
<?php }?>
</ul>
<?php }?>
<?php }?>
</div>
</body>
</html>