PHP 匯出 Excel

_zzh發表於2019-08-06

由於業務需要,需要在後臺新增匯出Excel的功能。所以很自然的想到了PHPExcel,於是就開始動手了。
由於之前沒有做過,所以基本都是借鑑網上大佬的。

  1. 檢查是否有PHPExcel包,如果沒有,請在網上下好。
    2.開始寫程式碼了。需要注意得是,一般匯出資料,資料量都會較大。所以需要設定記憶體使用量和,超時時間。並且需要注意匯出的資料會被Excel改寫,如身份證號,手機號,時間戳等長數字會被顯示為科學計數法,時間格式也顯示為Excel的預設顯示格式。脾氣是大得很吶....惹不起惹不起。所以經過查資料尋得PHPExcel中有個方法(setCellValueExplicit)是可以設定匯出單元格格式為文字。還有種方法是獲取資料時做處理,後面會講到。

    /**\
    * 匯出Excel檔案 速度慢
    * @param $fileName 匯出的檔名
    * @param $headArr 資料頭
    * @param $data 匯出資料
    */
    function getExcel($fileName,$headArr,$data){
      //設定PHP最大單執行緒的獨立記憶體使用量
      ini_set('memory_limit','1024M');
      //程式超時設定設為不限時
      ini_set('max_execution_time ','0');
    
      //匯入PHPExcel類庫,因為PHPExcel沒有用名稱空間,所以使用vendor匯入
      vendor("PHPExcel.PHPExcel.IOFactory");
      vendor("Excel.PHPExcel");
      vendor("Excel.PHPExcel.Writer.Excel5");
      vendor("Excel.PHPExcel.IOFactory.php");
    
      //對資料進行檢驗
      if(empty($data) || !is_array($data)){
          die("data must be a array");
      }
      //檢查檔名
      if(empty($fileName)){
          exit;
      }
      $date = date("Y_m_d",time());
      $fileName .= "_{$date}.xls";
      //建立PHPExcel物件
      $objPHPExcel = new \PHPExcel();
    
      //設定表頭
      $key = ord("A");
      foreach($headArr as $hkey => $v){
          $colum = chr($key);
          $objPHPExcel->setActiveSheetIndex(0) ->setCellValue($colum.'1', $v);
          $key += 1;
          unset($headArr[$hkey]);
      }
      $column = 2;
      $objActSheet = $objPHPExcel->getActiveSheet();
      foreach($data as $key => $rows){ //行寫入
          $span = ord("A");
          foreach($rows as $keyName=>$value){// 列寫入
              $j = chr($span);
              //設定匯出單元格格式為文字,避免身份證號的資料被Excel改寫
              $objActSheet->setCellValueExplicit($j.$column, $value);
              $span++;
              unset($rows[$keyName]);
          }
          $column++;
          unset($data[$key]);
      }
      $fileName = iconv("utf-8", "gb2312", $fileName);
      //重新命名錶
      // $objPHPExcel->getActiveSheet()->setTitle('test');
      //設定活動單指數到第一個表,所以Excel開啟這是第一個表
      $objPHPExcel->setActiveSheetIndex(0);
      ob_end_clean();
      ob_start();
      header('Content-Type: application/vnd.ms-excel');//定義輸出的檔案型別為excel檔案
      header("Content-Disposition: attachment;filename=\"$fileName\"");//定義輸出的檔名
      header('Cache-Control: max-age=0');//強制每次請求直接傳送給源伺服器,而不經過本地快取版本的校驗。
      $objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
      $objWriter->save('php://output'); //檔案通過瀏覽器下載
      exit;
    
    }

    3.呼叫實現,此處用的是thinkPHP。如框架不同,請自行修改語法。

    public function exportexcel(){
        $sqlWhere = session('H5OPERATIONLOG_TIME_WHERE');
        $data = M(self::T_TABLE)->where('create_time'.$sqlWhere)->select();//匯出資料
        $filename="XXXXX";//匯出檔名
        $headArr=array("XXX","XXX1","XXX2");//資料頭
        getExcel($filename,$headArr,$data);
    }
    
    <td><a href="你的exportexcel訪問路徑"><input type="button" value="匯出Excel"></input></a></td>

    以上借鑑於:https://www.cnblogs.com/HoverM/p/6210178.h...

4.原本以為這樣完事了。(事實上這樣也算完事了)但是後來覺得,貌似匯出速度有點慢。就找了時間再深入研究了一下。於是找到了另一種比較好的方法。-------匯出CSV--------

5.CSV概念

CSV是一種通用的、相對簡單的檔案格式,被使用者、商業和科學廣泛應用。最廣泛的應用是在程式之間轉移表格資料,而這些程式本身是在不相容的格式上進行操作的(往往是私有的和/或無規範的格式)。因為大量程式都支援某種CSV變體,至少是作為一種可選擇的輸入/輸出格式。

6.因為CSV通常也是使用Excel檢視。所以不可避免的,還是需要注意一些Excel的限制。

表資料限制:

Excel 2003及以下的版本。一張表最大支援65536行資料,256列。

Excel 2007-2010版本。一張表最大支援1048576行,16384列。

7.PHPExcel於CSV需要注意的地方

PHPexcel記憶體溢位:
既然資料限制在104W,那麼資料分割就資料分割唄,於是你嘗試50W一次匯入表,然而PHPexcel內部有函式報記憶體溢位錯誤,然後你就不斷的調小資料量,直到5W一次匯入你都會發現有記憶體溢位錯誤。這是為什麼呢,雖然你分割資料來匯入多個資料表,但是最後PHPexcel內部還是一次性把所有表資料放進一個變數中來建立檔案……額,這幾百萬資料一個變數儲存,你想記憶體不溢位,還真有點困難。
(後來看了一些文章發現PHPExcel也有解決方案,PHPExcel_Settings::setCacheStorageMethod方法更改緩衝方式來減小記憶體的使用)

CSV輸出buffer過多:
當你用PHP原生函式putcsv()其實就使用到了輸出快取buffer,如果你把幾百萬的資料一直用這個函式輸出,會導致輸出快取太大而報錯的,因此我們每隔一定量的時候,必須進行將輸出快取中的內容取出來,設定為等待輸出狀態。具體操作是:
ob_flush();
flush();

然後需要解決的就是,當資料量達到百萬以上是。不可避免的就需要面對Excel的顯示資料問題以及大量資料匯出的問題。這個問題也是我在目前為止並沒有解決的。參考文章中有說道一個方案就是使用yield生成器以及分段生成CSV檔案然後再統一打包下載。如果有大神有這方面經驗,還請不吝賜教。

文章出處:https://blog.csdn.net/qq_16142851/article/...

8.具體實現程式碼如下,需要注意的是。匯出CSV的方法並不像PHPExcel一樣提供了轉文字格式的方法。所以這裡需要在傳入資料是做資料處理。具體方法為:在需要處理的資料後面拼接製表符:"/t"。一定要雙引號。

     /**
   * @param array $head 資料頭
   * @param string $sql  物件
   * @param string $mark csv檔名
   * @param string $fileName 壓縮檔名
   * @param array $dateCountArr
   */
  function putCsv($head, $sql, $mark, $fileName, $dateCountArr)
  {
      //處理匯出Csv需要轉換為文字格式的資料
      $textArr = array(
          //mem_loan_excel鍵名 = $fileName
          "mem_loan_excel" => array(
              //對應欄位
              'phone',
              'id',
              'time',
          ),
      );
      $Model = new \Think\Model();
      //設定PHP最大單執行緒的獨立記憶體使用量
      ini_set('memory_limit','128M');
      set_time_limit(0);

//    輸出Excel檔案頭
      header('Content-Type: application/vnd.ms-excel;charset=utf-8');
      header('Content-Disposition: attachment;filename="' . $fileName . '.zip"');
      header('Cache-Control: max-age=0');

      // 每隔$limit行,重新整理一下輸出buffer
      $limit = 2000;
      // buffer計數器
      $sqlLimitCount = 0;
      $cnt = 0;
      $fileNameArr = array();
      // 逐行取出資料,不浪費記憶體
      foreach ($dateCountArr as $key => $value){
          $fp = fopen($mark . '_' . $value['date'] . '.csv', 'w'); //生成臨時檔案
          fwrite($fp, chr(0xEF).chr(0xBB).chr(0xBF));//轉碼 防止亂碼(比如微信暱稱(亂七八糟的))
          $fileNameArr[] = $mark . '_' .  $value['date'] . '.csv';
//        將資料通過fputcsv寫到檔案控制程式碼
          fputcsv($fp, $head);

          $sqlLimitCount += $value['count'];

          $sqlLimit = $sqlLimitCount - $value['count'];//每次計數開始的位置,所有資料相加 - 當前資料

          $dataArr = $Model->table($sql . ' a')->limit($sqlLimit,$value['count'])->select();
          foreach ($dataArr as $a) {
              //加"\t"形成文字格式,原樣輸出
              if($textArr[$fileName]){
                  foreach ($a as $key => $val){
                      if(in_array($key,$textArr[$fileName])){
                          $a[$key] = $val."\t";
                      }
                  }
              }

              $cnt++;
              if ($limit == $cnt) {
                  //重新整理一下輸出buffer,防止由於資料過多造成問題
                  ob_flush();
                  flush();
                  $cnt = 0;
              }

              fputcsv($fp, $a);
          }
          fclose($fp);  //每生成一個檔案關閉
      }
      //進行多個檔案壓縮
      $zip = new ZipArchive();
      $ZipFilename = $fileName . ".zip";
      $openFile = $zip->open($ZipFilename, ZipArchive::CREATE);   //開啟壓縮包
      if($openFile!=true || empty($fileNameArr)){
          exit("file Error");
      }
      foreach ($fileNameArr as $file) {
          $zip->addFile($file, basename($file));   //向壓縮包中新增檔案
      }
      $zip->close();  //關閉壓縮包
      foreach ($fileNameArr as $file) {
          unlink($file); //刪除csv臨時檔案
      }
      //輸出壓縮檔案提供下載
      header("Cache-Control: public");
      header("Content-Description: File Transfer");
      header('Content-disposition: attachment; filename='.basename($ZipFilename)); //檔名
      header("Content-Type: application/zip"); //zip格式的
      header("Content-Transfer-Encoding: binary"); //告訴瀏覽器,這是二進位制檔案
      header('Content-Length: '. filesize($ZipFilename)); //告訴瀏覽器,檔案大小

      ob_clean();
      flush();

      @readfile($ZipFilename);
      unlink($ZipFilename);
      exit;
  }

    /**
     ** 匯出資訊
      **/
    public function exportexcel(){
      $sqlWhere = session('H5OPERATIONLOG_TIME_WHERE');//這裡是我這裡的條件,需要修改
      $sql = M(self::T_TABLE)->where('create_time'.$sqlWhere)->buildSql();//獲取到查詢資料的sql
      $Model = new \Think\Model();
      //得到每一天的資料總數
      $dateCountArr = $Model->table($sql . ' a')
     ->field('count(*) as count, DATE_FORMAT(a.create_time,"%Y-%m-%d") as date')
     ->group('DATE_FORMAT(a.create_time,"%Y-%m-%d")')
     ->select();
     $filename="h5_operation_log_excel".time();
      $mark="h5_operation_log_info";
      $headArr=array("id","使用者編號","資料唯一編號","資料名稱","操作","操作者IP","操作批次","操作時間");
     putCsv($headArr,$sql,$mark,$filename,$dateCountArr);
    }

    <td><a href="你的exportexcel訪問路徑"><input type="button" value="匯出Excel"></input></a></td>

    //此地方的出處找不到了,希望原作看到不要揍我。狗頭保命ing......

9.CSV在Excel中展示會有點小問題

PHP 匯出 Excel

解決方案:SYLK 檔案時一個文字檔案,開頭的為“ID”或“ID_XXXX”(其中XXXX是文字字串)。
SYLK 檔案的第一個的記錄是在 ID_Number 記錄的。
Excel 將識別該文字在文字檔案開頭時, 它會將該檔案解釋為 SYLK 檔案。
Excel 將嘗試從該 SYLK 格式轉換該檔案,但不能這樣做,因為"ID"字元後不有任何有效的 SYLK 程式碼。
因為 Excel 不能轉換該檔案,您收到錯誤訊息。

當您開啟一個文字檔案、 CSV 檔案和檔案的前兩個字元是大寫字母"I","D"時,會發生此問題。
例如文字檔案可能包含以下文字:
ID, STATUS 123, open 456, closed
如果前兩個字母小寫"i"和"d"不會發生此問題的 。

參考:https://www.cnblogs.com/A2008A/archive/201...

至此也就完成了。原本匯出1W+條資料,PHPExcel需要8秒。現在換成CSV方法只需要兩秒,也算能滿足日常的運營需求了。當然,可以優化的地方還有很多。歡迎指正修改。

相關文章