CakePHPでHTMLをPDFに一発変換しちゃうゾ〜

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
HTMLが一瞬でPDFに!

この記事を三行にまとめると

さすが、世界は広いですね
HTMLをPDFに変換してくれるものです
CakePHP2系での話になります
前々から、ウェブページを……ようはHTMLをPDFに変換するお手軽で良い方法ないかな〜って、思ってたんですよ。

PHPでPDFを作成するライブラリなら、MBFPDF(だったかな?)とかを、ちょっとだけ使ったことがあります。でもそういうんじゃなくて、もっと単純に、ウェブ画面上に表示されるものがそのままPDFになったら良いのにな〜って、ずっと思ってました。

正直、そういうのはないかと思ってたんですけど、さすが、世界は広いですね。ちゃんとありました。

wkhtmltopdf

まさしく、HTMLをPDFに変換してくれるものです。

そしてさらに! このwkhtmltopdfを、CakePHPで使えるようにしてくれている賢者もいました。

CakePdf

何でも探せばあるもんですな〜。タイムマシンとかも、血眼になって探せばどっかに転がってんじゃねーの?

というわけで、今日はこれらのプラグインを使ってみたいと思います。

ちなみに、今回はCakePHP2系で動いているサイトでCakePdfを使ったので、CakePHP2系での話になります。3系でやる場合についての情報は、すまぬがここには載ってない。



wkhtmltopdf

まずはwkhtmltopdfを使って、HTMLをPDFにするとこまでやってみましょう。SSHでサーバーに接続して、wkhtmltopdfコマンドを実行してみたいと思います。

ってことで、まずはサーバーに接続。ターミナルとかTeraTermとか使って接続してくらはい。

僕はAWSのEC2サーバーを使っているので、僕と同じ環境の場合は、以下のような手順でwkhtmlpdfをインストールできます。

# wget http://download.gna.org/wkhtmltopdf/0.12/0.12.2.1/wkhtmltox-0.12.2.1_linux-centos6-amd64.rpm
# rpm -ivh wkhtmltox-0.12.2.1_linux-centos6-amd64.rpm

サーバーのどっか適当なところにrpmファイルをダウンロードしてきて、rpmコマンドを使ってインストールをしています。EC2の場合はこれでオーケーです。他の場合、例えばWindowsにインストールする場合などは……すまん、分からん。

今回はwgetコマンドを使ってダウンロードしましたが、上記のwkhtmltopdfのサイトにダウンロードページがあるので、そこから直接落として来て自分でサーバーに上げても良いです。

もしかしたら、rpmコマンドを実行したときに、エラーが出るかもしれません。僕の場合は以下のようなエラーが出ました。

xorg-x11-fonts-75dpi は wkhtmltox-1:0.12.2.1-1.x86_64 に必要とされています

ようは「必要なフォントがない」みたいなことですかね。僕はこれ一つしかエラーが出ませんでしたが、他にもエラーが出る可能性はあると思います。でもおそらく、これとほとんど同じ内容のエラーだと思います。wkhtmltoxさんが「テレビもラジオも電話もギターも映画ものぞきもディスコもあるけど、必要なフォントがねえなんて、オラこんなサーバー嫌だ〜」っておっしゃってるわけですな。

そんときゃエラーに従って、足りないフォントをやむってください。

# yum install xorg-x11-fonts-75dpi

インストールが完了したら、wkhtmltopdfコマンドが使えるようになっているので、実際にコマンドを使って適当なページをPDFにしてみます。

# wkhtmltopdf http://norm-nois.com norm-nois.pdf

これで、あかつきのお宿のトップページが「norm-nois.pdf」というファイル名で保存されます。

もし実行したときに、↓みたいなエラーが出た場合。

-bash: /usr/bin/wkhtmltopdf: No such file or directory

「そのようなファイルやディレクトリはありません」っていうエラーですね。rpmコマンドでインストールすると、wkhtmltopdfの実行ファイルは「/usr/bin」ではなく「/usr/local/bin」の方にインストールされています。なので、以下のようにパスを指定して実行すれば大丈夫です。

# /usr/local/bin/wkhtmltopdf http://norm-nois.com norm-nois.pdf



日本語のフォントについて

もし作成したPDFで日本語が文字化けしていたら、日本語用のフォントをサーバーに入れる必要があります。

IPAフォントってのが日本語用フォントなので、それをやむります。

# yum install ipa-gothic-fonts ipa-mincho-fonts

もしかしたらサーバーによって必要なIPAフォントが違うかもしれないですが、そんなときはyumの検索オプションを使えば大丈夫だと思います。

# yum search ipa

検索結果に「ipa-***」ってのが出てくると思うんで、それを入れればオッケーです。



CakePdf

ほんじゃあ、wkhtmltopdfコマンドが使えるようになったということで、次はそれをCakePHPで使ってみたいと思います。ここでは試しに、小説の間のトップページをPDF化するという前提で行ってみましょう。URLで言うと「http://norm-nois.com/novels/index」

上記のCakePdfサイトでは、最新のバージョンがCakePHP3用のものになってるので、CakePHP2で使うには、ちょい古いバージョンのものをもらって来る必要があります。

Releases

こっちに1.0.3っていうバージョンがあるので、それを落っことして来ます。

ダウンロードすると、フォルダ名が「CakePdf-1.0.3」ってなってると思うので、それを「CakePdf」に直して、「app/Plugin」の下にアップロードします。

続いて、「app/Config」の中にあるbootstrap.phpに、以下の項目を加える。

CakePlugin::load('CakePdf', array('bootstrap' => true, 'routes' => true));
Configure::write('CakePdf', array(
  'engine' => 'CakePdf.WkHtmlToPdf',
  'binary' => '/usr/local/bin/wkhtmltopdf',
  'options' => array(
    'print-media-type' => false,
    'outline' => true,
    'dpi' => 96,
  ),
  'margin' => array(
    'bottom' => 10,
    'left' => 10,
    'right' => 10,
    'top' => 10,
  ),
  'orientation' => 'portrait',
  'download' => false
));

marginの数字とかは任意の数値で良いです。必要なのは、engineを「CakePdf.WkHtmlToPdf」にすることと、binaryを「/usr/local/bin/wkhtmltopdf」にするところかな。デフォルトだと、binaryは「/usr/bin/wkhtmltopdf」に設定されているので、今回の手順だと、コマンドがねえって怒られる。

orientationは、PDFを縦にするか横にするかの設定です。portraitとlandscapeが設定できます。downloadは、falseにしていると、PDFがダウンロードされずに、画面上に表示されます。

この設定は、コントローラーの中で上書きすることも可能です。

//bootstrap.php
Configure::write('CakePdf', array(
  'engine' => 'CakePdf.WkHtmlToPdf',
  'binary' => '/usr/local/bin/wkhtmltopdf',
  'options' => array(
    'print-media-type' => false,
    'outline' => true,
    'dpi' => 96,
  ),
  'margin' => array(
    'bottom' => 10,
    'left' => 10,
    'right' => 10,
    'top' => 10,
  ),
  'orientation' => 'portrait',
  'download' => false
));

//NovelsController.php
class NovelsController extends AppController {
  public function index() {
    $this->pdfConfig = array(
      'orientation' => 'landscape',
      'download' => true,
    );
  }
}



次に、「View/Layouts」の下にpdfというフォルダを作成し、その中にdefault.ctpを作成します。ctpファイルの中身は、元々のdefault.ctpと同じ内容で良いです。変えたきゃ任意で。

続いて、「View/Novels」の下にも、pdfというフォルダを作成し、その中にindex.ctpを作成します。index.ctpの中身は、PDFに出力したい内容を書く。

フォルダ構成としてはこんな感じです。

//http://norm-nois.com/novels/indexの場合
/app
  ├ /Config
  │   └ bootstrap.php
  ├ /Controller
  │   └ NovelsController.php 
  ├ /Plugin
  │   └ /CakePdf
  │       └ ファイルいろいろ
  └ /View
      ├ /Layouts
      │   ├ default.ctp
      │   └ /pdf
      │       └ default.ctp
      └ /Novels
          ├ index.ctp
          └ /pdf
              └ index.ctp

PDFの場合は、ビューは基本的にpdfフォルダの下にあるものが呼ばれると思ってもらえれば大丈夫です。Layoutsにしろ、他のにしろ。

ここまでできたら、あとは出力したい内容をコントローラーやらビューに書いてくだけ。

//NovelsController.php
class NovelsController extends AppController {
  public $components = array('RequestHandler');
  public function index() {}
}

//Layouts/pdf/default.ctp
<html>
  <head>
    <?php echo $this->Html->css('style', 'stylesheet', array('fullBase' => true)) ?>
  </head>
  <body>
    <?php echo $this->fetch('content') ?>  
  </body>
</html>

//Novels/pdf/index.ctp
<p>小説の間だよ〜ん</p>

必要な内容が書けたら、実際に呼び出してみます。PDFを出力したい場合は、URLの後ろに「.pdf」をつけるだけです。

//Web画面
http://norm-nois.com/novels/index

//PDF
http://norm-nois.com/novels/index.pdf



注意点としては、コントローラーでRequestHandlerコンポーネントを読み込む必要があることと、cssやjsは、フルパスで書かないとエラーになるってことですかね。画像もか。

//エラーになる
<link rel="stylesheet" type="text/css" href="/css/style.css">

//エラーにならない
<link rel="stylesheet" type="text/css" href="http://norm-nois.com/css/style.css">

相対パスで書いてる場合、こんな感じのエラーが出ることがあります。

Exit with code 1 due to network error: ContentNotFoundError

このエラーが出たら、とりあえず相対パスのものがないか、見直してみてください。

ベタ書きの場合は自分で書けば問題ないですが、CakePHPのHTMLヘルパーを使ってcssやjsを出力している場合は、「’fullBase’ => true」でフルパスになります。

ビューはWeb画面とPDFで別々のテンプレートを用意する必要がありますが、コントローラーのアクションは分ける必要はありません。NovelsControllerのindex()アクション一つで大丈夫です。



SSLに関して

SSLのサイトでwkhtmltopdfを使うと、何やらよく分からないエラーが出ることがあるかもしれません。

何かこんな感じのやつ。

QSslSocket: cannot call unresolved function 〜

wkhtmltopdfのバージョンが古いと、出たりすることがあるっぽい。今回使ったのは0.12.2ですが、0.9.9だと出ることがあった。

その場合は、openssl-develをやむれば解決するかもしれない。

# yum install openssl-devel

あるいは、wkhtmltopdfのバージョンを上げるっていうのも、手だと思います。



QsslSocketなんちゃらってエラーは出なくなったけど、もしそれでも、SSLのページが上手くPDF化できない場合。

これは、もしかしたらそうかもっていう話になっちゃうんですが、ロードバランサーを使っていると、上手く行かないことがある気がする。

AWSのELBを使うと、ロードバランサーに直接SSLの証明書をインストールして、EC2側ではSSLの設定をしないってことができるんですよ。そういう設定にしているころでCakePdfを使ったら、上手くPDF化できませんでした。だからたぶん、それが原因なんじゃないかなぁって思うんですが……でもすみません、100%確証があるわけではないです。

これもバージョンが0.9.9のときに起こったことなので、バージョン上げれば解決すると思います。

それか、PDFを出力するときは、cssやjsをhttpsではなく、httpで読み込むようにするとかですかね。非SSLでもオッケーであれば、この方法でも一応何とかなる。



Basic認証を使ってる場合

サイトにBasic認証をかけていると、PDF化に失敗します。

そんときは、オプションでBasic認証のユーザー名とパスワードを設定すれば大丈夫です。

Configure::write('CakePdf', array(
  'engine' => 'CakePdf.WkHtmlToPdf',
  'binary' => '/usr/local/bin/wkhtmltopdf',
  'options' => array(
    'print-media-type' => false,
    'outline' => true,
    'dpi' => 96,
    'username' => 'user',//ユーザー名
    'password' => 'password',//パスワード
  ),
  'margin' => array(
    'bottom' => 10,
    'left' => 10,
    'right' => 10,
    'top' => 10,
  ),
  'orientation' => 'portrait',
  'download' => false
));

これでオッケー。ちなみにwkhtmltopdfコマンドでBasic認証のページをPDFにするなら、こう。

# wkhtmltopdf --username 'user' --password 'password' http://norm-nois.com norm-nois.pdf

どうやらCakePdfの設定のoptionsは、コマンドを打つときのオプションに対応しているみたいですね。他のオプションに関しても、同じ要領で設定できると思います。






まだバリバリ使い込んでるわけじゃないので、分からない部分も結構あります。でも、とりあえずPDFにしてみるってだけなら、これで問題ないと思います。

今回はCakePHP2系でしたが、もしかしたらその内、CakePHP3系の方でもCakePdfを使う機会があるかもしれませんので、そんときゃまた記事にしたいと思います。

ではでは〜ノシ
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください