PHPで出力されるWebページをサーバーサイドでPDFにしてダウンロードさせる

# 要件

- とあるシステムの管理ダッシュボードでレポートをダウンロードしたい。
- レポートは画面を印刷したものと同じでPDFにする。
- 一覧のページに設置されたボタンクリックでその詳細ページをダウンロードする。

絵に書くとこんな感じ。

![PNGイメージ-7763F9E0CA93-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159511/9d6aa271-5f8f-93a9-a839-96a2f3a26bb4.png)

# 何をつかってやるか

[ヘッドレスChrome](https://developers.google.com/web/updates/2017/04/headless-chrome?hl=ja)を使う。ヘッドレスChromeはコマンドからChromeブラウザを操作することが出来るので、GUI操作をプログラムから実行することができる。

#### PDFを作成するには 

```bash
google-chrome  --headless --no-sandbox --disable-gpu --print-to-pdf 'https://example.com/'
```

でよい。

# いくつかの問題

-  印刷対象のURLへのアクセス
-  出力先
-  フォント

ヘッドレスChromeを使ってやるのは良いが実装する上でいくつかの問題が発生した。
印刷対象がログインしたあとのページのため、サーバーサイドから詳細ページにアクセスするにはログイン処理を通る必要があり、直接URLにアクセスできない。また出力先の指定方法がわからなかったりフォントがなかったりした。

## 1. 印刷対象へのアクセス

サーバーサイドのヘッドレスChromeから自身のページにHTTP経由でアクセスしようとしても、ログイン処理を通過していないため直接アクセスできない。

![PNGイメージ-0708E4D92841-1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159511/d90a238d-c83b-ade9-041f-6975dc1700e4.jpeg)


サーバーサイドから自分自身にHTTP経由でアクセスすることは難しいけど、出力予定のページのHTMLは作成出来る。ということで、ファイルにHTMLを出力してそれをヘッドレスChromeに読ませることで解決した。
HTMLをファイルに出力しているので以下のようにPDFにすることができる。

```bash
google-chrome  --headless --no-sandbox --disable-gpu --print-to-pdf 'file:///path/to/page.html'
```

## 2. 出力先の指定方法

先のコマンド例で実行した場合、PDFファイルはコマンドを実行したディレクトリに `output.pdf` という名前でPDFファイルが保存される。出力先を指定したくても `google-chrome --help` でヘルプがみれない。ということでソースコードを漁る。

https://cs.chromium.org/chromium/src/headless/app/headless_shell.cc?type=cs&q=print-to-pdf&g=0&l=69-70

```c
// Default file name for pdf. Can be overriden by "--print-to-pdf" switch.
const char kDefaultPDFFileName[] = "output.pdf";
```

`--print-to-pdf` で上書き出来るということなので `--print-to-pdf=origin-file-name.pdf` で出力先を指定できることがわかる。


## 3. フォントのインストールが必要

HTMLファイルをPDF化しても日本語フォントがないと文字化けするのでインストールする必要がある。日本語フォントは[IPAフォント](https://ipafont.ipa.go.jp/)からダウンロードできる。


# どうやってやるか

## 1. ページの出力HTMLをサーバーに保存する

PHPでは[出力制御関数](https://www.php.net/manual/ja/ref.outcontrol.php)を使って出力を操作できる。今回は、サーバーで作成したHTMLをクライアントに返さずにサーバーで取得したいのでこの関数を使う。

```php
ob_start();
/*
ページを表示するための
いろいろな処理
 */
$html = ob_get_clean();
```

これで `$html` 変数に出力予定のHTMLが保持されるのであとはこれをサーバーに保存すればよい。
MVCモデルのフレームワークを使っている場合はこんな感じになる。

```php
Class Awesome extends Base_Controller {
    public function detail($id) {
        // 詳細ページの処理
    }

    public function pdf($id) {
        // 詳細ページの内容をPDFにしてダウンロードさせたい
        ob_start();
        $this->detail($id);
        $html = ob_get_clean();
        
        $html_filepath = "/tmp/" .  md5($html) . ".html";
        file_put_contents($html_filepath, $html);
    }
}
```


## 2. 保存したHTMLをPDFにして保存

先の処理で保存したHTMLファイルをヘッドレスChromeを使ってPDF化する。ファイルへのアクセスは `file://` プロトコルで指定すればヘッドレスChromeに読ませることが出来る。

```bash
google-chrome  --headless --no-sandbox --disable-gpu --print-to-pdf="/tmp/output.pdf" "file:///tmp/detail_page.html"
```

PHPのコードからは `exec()` 関数を使用すれば良い。

```php5
$html = ob_get_clean();

$html_filepath = "/tmp/" .  md5($html) . ".html";
$pdf_filepath = "/tmp/" . md5($html) . ".pdf";

file_put_contents($html_filepath, $html);

exec("google-chrome  --headless --no-sandbox --disable-gpu --print-to-pdf=$pdf_filepath file://$html_filepath");
```

## 3. IPAフォントをインストールする

```bash
apt-get install -y unzip
cd /usr/local/share/fonts/
curl 'https://ipafont.ipa.go.jp/IPAfont/IPAfont00303.zip' -o IPAfont00303.zip
unzip IPAfont00303.zip
fc-cache -fv
fc-list
```



## 4. ダウンロードさせるには

```php
$pdf_filepath; // ダウンロードさせるPDFのファイルパス
$basename = basename($pdf_filepath);

header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . $basename .'"');//ファイルサイズを取得
header('Content-Length: '.filesize($pdf));
readfile($pdf);
```

# おまけ

この環境をDockerで作るにはChromeのインストールやフォントのインストールが必要になる。`Dockerfile`はこんな感じになる。

```dockerfile
FROM php:5.6-fpm


RUN cd /tmp
RUN apt install -y gnupg
RUN curl 'https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb' -o chrome.deb
RUN curl 'https://dl.google.com/linux/linux_signing_key.pub' -o chrome_key.pub
RUN apt-key add chrome_key.pub

# 依存関係でエラーになるのでそれをインストールする
RUN dpkg -i chrome.deb || apt-get install -f -y

RUN dpkg -i chrome.deb
RUN apt update
RUN apt-get install -y google-chrome-stable

# 日本語font install
RUN apt-get install -y unzip
RUN cd /usr/local/share/fonts/
RUN curl 'https://ipafont.ipa.go.jp/IPAfont/IPAfont00303.zip' -o IPAfont00303.zip
RUN unzip IPAfont00303.zip
RUN fc-cache -fv
RUN fc-list
```