Slim PHP Print HTML to PDF By Chrome

最近剛好遇到了需要提供下載PDF的案子,之前的做法是透過TCPDF 這個套件處理,
有機會再分享實作的方法,而這次要分享的,則是使用chrome 的語法將寫好的html 內容轉成PDF。
會想要使用chrome 來執行,是因為大多數的電腦都有裝chrome,在server上要裝chrome 也很容易,
相信許多MIS都不會介意在自己家主機裝chrome,所以就嘗試了這個方法,也將遇到的雷點記錄下來,希望大家可以避免。


寫在前面

開發使用php slim framework,透過php dev server 執行專案(php -S 0.0.0.0:port<-雷點在此,稍後說明)。

正文

要使用chrome 來執行將html 轉成 pdf,先提供幾個關鍵字:

  • chrome cmd
  • chrome headless command
  • chrome print-to-pdf
  • php exec()

大致上來說,我們希望透過chrome.exe 來執行一段chrome 的指令,將已經寫好的html 轉存成pdf,
再透過給定的 http response header 來產出的pdf 以附件的方式回傳到瀏覽器,瀏覽器就會執行下載了。

首先我們要先知道chrome.exe 的絕對路徑,或者是將其設定環境變數,為了Demo 方便,就直接給定絕對路徑,請依實際情況修改:

1
$chrome_path = "C:\Program Files\Google\Chrome\Application\chrome.exe";

再來須指定chrome 匯出pdf 後要存到哪裡:

1
2
3
4
5
6
7
8
9
10
11
// 我先設定好一個常數,指定了pdf 存的路徑
// 為避免資料夾不存在而出現錯誤
if (!is_dir(PDF_OUTPUT_PATH)) {
mkdir(PDF_OUTPUT_PATH);
}
// 以日期作為小資料夾,方便區分
if (!is_dir(PDF_OUTPUT_PATH . '/' . $date)) {
mkdir(PDF_OUTPUT_PATH . '/' . $date);
}

$output_file = PDF_OUTPUT_PATH . "/{$date}/{$fileName}";

接著來看到chrome 的指令

1
2
$command = "\"{$chrome_path}\" --headless --disable-gpu --print-to-pdf=\"{$outputFile}\" https://google.com 2>&1"
// |-- chrome.exe ---|--無頭模式 -|- 禁用硬體加速-|--關鍵:轉存pdf-|-pdf 存放路徑-|--- 目標網頁(html)--|-不顯示結果-|

簡單來看,我們不希望真的開起瀏覽器來做這件事情,而是希望在背景執行,並且將目標網頁的html 轉成pdf,放到我們指定的地方。

接著就是要執行這段我們已經打好的command了

1
2
3
4
$output = null;
$result = null;

exec($command, $output, $result);

exec() 這個方法有三個參數,第一個為必要,後面兩個為選填,為了Debug 方便所以這邊三個參數都有用到。
執行完後會將輸出及結果分別放到這兩個指定參數中,這樣就可以知道哪裡出錯了。
執行到這邊,應該就可以看到我們指定的路徑已經有pdf 的檔案了。最後的步驟就是將檔案回傳到瀏覽器供下載,
這邊我使用的是Slim Psr7 中的Factory,將串流回傳出去。

1
2
3
4
5
6
$stream = (new StreamFactory())->createStreamFromFile($output_file);
return $response
->withHeader("Content-Type", "text/html")
->withHeader("Content-Transfer-Encoding", 'binary')
->withHeader('Content-Disposition', "attachment; filename=Chrome.pdf")
->withBody($stream);

就算你使用的不是slim 框架,應該也能找到相對應的方法,將pdf 檔案傳出去。

接著我們透過瀏覽器,開啟這個頁面,馬上就可以下載一份名為Chrome.pdf 的檔案,內容如圖
PDF檔案

這樣就完成了這次的 html 轉 pdf 的功能了!


雷點

上面有稍微提示雷點是透過php dev server 來執行我的專案,
一開始我是透過此方法來執行專案,會發現執行我們寫好的command,網頁就會進入pending狀態,
就算把chrome 關掉,把監聽專案的php dev server中斷都沒有用,
只能透過工作管理員,將卡住的chrome 結束工作。

我試著將 –headless 拿掉,這時會開啟預設瀏覽器,並跳轉到我們要輸出的url (https://google.com),
但是完全沒有產出pdf 檔案,也找不到問題點。

後來先將專案上傳到server,是透過Apache 來執行專案,突然發現功能一切都正常,pdf 也正常產出,
瀏覽器也可以正確下載到這個檔案,搞了半天找不出答案的問題突然就解開了,我目前還沒有找到原因,
若你有任何相關的資訊,歡迎與我分享!