CakePHP5入門【WebAPI編⑱】PDFファイル化
A子
B美
A子
いちいち結果を紙にメモっておくのが、なにげに面倒なんだよね
C菜
A子
B美
てか、メモるほうの紙はもったいなくないの?
A子
そんなことより、Webアプリで出力した結果を何らかのファイルにして、保存しておくことってできないの?
C菜
A子
HTMLファイルでしか保存できないみたいなんだよね
さらにその場合、CDNでアクセスしているBulma(CSSフレームワーク)も無効化されちゃうし…
B美
だったら、PDFファイルにしてみる?
案外、簡単よ
C菜
それとも何らかの拡張機能をオプションとしてインストールするとか~?
B美
正解は、(CakePHPのプログラムとして)PDFファイル化するコードを組むの
A子
ああいうやつ?
B美
何かを印刷したい場合って、いったんPDFファイル化するのは(Webの世界では)よくあることよ
C菜
「案外、簡単」ということは、PHPのライブラリがあるってことでしょうか~?
B美
いくつかの選択肢があるわよ
私は昔「TCPDF」をよく使ってたんだけど、今は「mPDF」ばかり使ってるわ
(好みの問題なんだけど…)
A子
その「mPDF」ってやつ?
B美
それじゃ、「MATE端末」を開いてから、composerでライブラリをインストールしてみましょう
|
cd html/numapp[Enter]
composer require mpdf/mpdf[Enter] |
あと、テンポラリ領域を設定して、フォントのダウンロードとその配置も必要だけどね
C菜
B美
まぁ、やってみましょう
A子
てか、それってOSに内蔵されてるんじゃないの?
B美
その場合、OS付属のフォントではライセンスの問題が発生するからね
(要するに、著作権的な問題…)
C菜
B美
なにしろ「独立行政法人 情報処理推進機構(IPA)」が公開しているフォントだからね
(通称「IPAフォント」…正確には「IPAexフォント」と呼ばれます)
A子
IPAって、「ITパスポート」試験や「基本情報技術者」試験の主催じゃなかったっけ?
B美
それらの試験が『国家試験』であることからも、IPA自体が国の機関であることが分かるでしょう?
さて、それじゃブラウザ(Linux上のFirefox)を使ってダウンロードしてみましょう
(「IPAフォント」というキーワードでググってね)
ダウンロードしたものが以下の「IPAexfont00401.zip」というファイルよ
C菜
↓
B美
それがフォントファイルよ
(「ipaexg.ttf」がゴシック体で、「ipaexm.ttf」が明朝体ね)
A子
B美
んで、フォントの置き場所って、(本来は)「numapp/vendor/mpdf/mpdf/ttfonts」なんだけど、「vendor」以下をいじくるのはお勧めしないわ
A子
B美
あ、ファイルのパーミッションを(「700」から)「644」に変更しないといけないからね
それを忘れないように!
B美
「numapp/tmp」の下に「mpdf」ディレクトリを作って、そのオーナーをWebサーバ(www-data)にするの
あと、ディレクトリ自体のパーミッションを「755」に変更ね
C菜
A子
B美
ちょっと指が滑っただけよ
C菜
B美
あ、コントローラについては、専用のものを作っておきましょうか
A子
B美
|
use Mpdf\Mpdf;
use Mpdf\HTMLParserMode; use Mpdf\Config\ConfigVariables; use Mpdf\Config\FontVariables; |
の四つを記述してね
あと、全体の流れを先に言っておくと…
|
1.フォーム送信された値で再計算
2.Mpdfクラスをインスタンス化 3.CSSを定義(変数に格納) 4.HTMLによるコンテンツを定義(変数に格納) 5.WriteHTMLメソッドの引数として3と4(の変数)を渡したあと、PDFデータの書き出しを行う 6.レスポンスを返す |
…って感じかな
A子
CSSとHTMLってことは、このPDFファイルってWebページみたいに作るってこと?
B美
この特徴こそが、私が「mPDF」というライブラリを推す理由なのよね
(「TCPDF」のほうって、座標指定が必要だからめっちゃ大変なの)
C菜
作りやすそうです~
B美
前回作った「TopController」の中の「combinationResult」メソッドだけど、中核部分を別メソッドに切り出しておきましょう
なぜなら「PdfController」の中でも全く同じ処理をするからね
C菜
その切り出すメソッドって、親クラスである「AppController」の中に書いたほうが良いですね~
(「TopController」と「PdfController」の両方から呼び出せるように…)
A子
その場合、アクセス修飾子は「private」ではなく「protected」でないとダメなんだっけ?
B美
よく覚えてたわね
A子
んじゃ、まずは「TopController」の「combinationResult」メソッドを書き換えるよ
|
//指定された数字を組み合わせて「ボックス」用の値を作成(結果画面)
public function combinationResult() { if ($this->request->is('post')) { $box = []; //ボックス値の作成 $digits = array_filter($this->request->getData('digits'), 'strlen'); //空文字列は削除(ただし、ゼロは除外) $box_set = $this->combination($digits, 3); //ボックスの組み合わせを作成 foreach ($box_set as $row) { sort($row, SORT_NUMERIC); $candidate = ($row[0] * 100) + ($row[1] * 10) + ($row[2] * 1); array_push($box, sprintf("%03d", $candidate)); //ゼロパディングした3けたの文字列 } $result = $this->doCombination($box); $this->set(compact('box', 'digits')); $this->set(compact('result')); } } |
「AppController」内にある「doCombination」メソッドを呼び出して結果を得るつもりだよ
あと、ビューにも$box及び$digitsを渡すことで、PDF作成のためのパラメータとして使う予定…
C菜
「templates/Top/combination_result.php」については私のほうで追加しておきますね~
|
<div style="width: 100%;text-align: center;">
<?= $this->Form->create(null, ['url' => ['controller' => 'pdf', 'action' => 'makepdf'], 'target' => '_blank']) ?> <?php foreach ($box as $idx => $val) { ?> <?= $this->Form->hidden("box.$idx", ['value' => $val]) ?> <?php } ?> <?php foreach ($digits as $idx => $val) { ?> <?= $this->Form->hidden("digits.$idx", ['value' => $val]) ?> <?php } ?> <?= $this->Form->button(__('PDFファイルを作成'), ['class' => 'button is-link']) ?> <?= $this->Form->end() ?> </div> |
ポイントとしては、フォーム作成時のオプションとして「'target' => '_blank'」を入れたことですね~
(別画面に表示したいので~)
あと、「"box.$idx"」と「"digits.$idx"」の部分は、勝手に「box[0]」や「digits[1]」って感じに書き換わりますよ~
(シングルクォートではなく、ダブルクォートで囲むのがポイントです~)
B美
シングルクォートだと「box0」や「digits1」になっちゃうのよね(苦笑)
A子
(というか、元々の「combinationResult」メソッドから切り出しただけ)
|
//各ボックス値についてDB検索及びソートを実行
protected function doCombination($box) { $result = []; //各ボックス値の詳細情報を取得 //直近の出現時期とその当選番号、過去の出現頻度等 $connection = ConnectionManager::get('default'); foreach ($box as $value) { //各桁の分解 $digit = str_split($value); //データベース検索 $sql =<<<EOF select lottery_time,lottery_date_dt,lottery_date_str, num3_str from numbers where least(num3_place100, num3_place10, num3_place1)={$digit[0]} and (num3_place100+num3_place10+num3_place1)-least(num3_place100, num3_place10, num3_place1)-greatest(num3_place100, num3_place10, num3_place1)={$digit[1]} and greatest(num3_place100, num3_place10, num3_place1)={$digit[2]} order by lottery_date_dt desc; EOF; $search_result = $connection->execute($sql)->fetchAll('assoc'); //結果を格納 if (count($search_result) > 0) { $recentry = $search_result[0]['lottery_date_str']; //直近の出現時期 $lottery_time = $search_result[0]['lottery_time']; //直近の実施回 $num3 = $search_result[0]['num3_str']; //直近のストレート数字 $frequentry = count($search_result); //出現頻度(回数) array_push($result, [$value, $recentry, $lottery_time, $num3, $frequentry]); } else { array_push($result, [$value, '-', '-', '-', 0]); } } //直近の出現時期が古いものから順に並べ替え(ソートには「実施回」を使用) array_multisort(array_column($result, 2), SORT_ASC, $result); return $result; } |
C菜
| use Cake\Datasource\ConnectionManager; |
A子
B美
ここからは「src/Controller/PdfController.php」に「makepdf」メソッドを書いていきましょう
まずは1番よ
| 1.フォーム送信された値で再計算 |
|
//「ボックス」の予想をPDF化
public function makepdf() { $this->autoRender = false; if ($this->request->is('post')) { //ボックス値を取得し、再計算 $box = $this->request->getData('box'); //ボックス値 $digits = $this->request->getData('digits'); //候補数字 sort($digits); $digit_cnt = count($digits); $result = $this->doCombination($box); $today = date('Y年n月j日 H:i:s'); ・・・ } } |
「autoRender」をfalseにしている理由は、ビューファイルを使わないからよ
A子
C菜
B美
では、次は2番ね
(これが一番の問題なのよね)
| 2.Mpdfクラスをインスタンス化 |
A子
普通にnewするだけじゃないの?
B美
フォントディレクトリの取得やデフォルトフォントの取得なんかが必要になるからね
|
//フォントディレクトリ(デフォルト)
$defaultConfig = (new ConfigVariables())->getDefaults(); $fontDirs = $defaultConfig['fontDir']; //フォント(デフォルト) $defaultFontConfig = (new FontVariables())->getDefaults(); $fontData = $defaultFontConfig['fontdata']; //mPDFのインスタンス化 $mpdf = new Mpdf([ 'tempDir' => TMP.'mpdf', 'fontDir' => array_merge($fontDirs, [ROOT.DS.'fonts']), 'format' => 'A4', 'orientation' => 'P', 'fontdata' => $fontData + [ 'ipaexg' => [ 'R' => 'ipaexg.ttf' ], 'ipaexm' => [ 'R' => 'ipaexm.ttf' ], ], 'default_font' => 'ipaexg', ]); |
かなり面倒っぽいけど、定型的な記述だと思っておけば良いから
(深く考えないように…)
C菜
既存(デフォルト)のフォント情報(ディレクトリやフォント自体)に追加する形で、IPAフォントを設定するんですね~?
A子
IPAフォントだけを設定すれば良いじゃん
B美
さっき見た「numapp/vendor/mpdf/mpdf/ttfonts」の中って、めっちゃたくさんのフォントが入っていたでしょ?
あれらが内部的に(こっそり)使われてたりするとヤバいかもしれない…ってことで「追加」が推奨されているのよ
A子
そういうことか
んじゃ、次は3番だね
| 3.CSSを定義(変数に格納) |
C菜
B美
変数$cssにヒアドキュメントで記述してみなさい
C菜
あ、ついでに4番もやっちゃいますね~
($contentという変数に格納するです~)
| 4.HTMLによるコンテンツを定義(変数に格納) |
|
//ヘッダー
$css =<<<EOF table { border-collapse:collapse; width:80%; margin: 0 auto; font-size:12px; } table th{ border:1px solid black; padding:10px 5px 10px 5px; text-align: center; background-color: #afffaf; } table td{ border:1px solid black; padding:10px 5px 10px 5px; text-align: center; } EOF; //ボディ $content = ''; $content .=<<<EOF <div style="width: 100%;text-align: center;"> <h2>ボックス値(予想)一覧</h2> 【{$today}に作成】 <br /><br /> <table> <tbody> <tr><th colspan="{$digit_cnt}">候補数字</th></tr> <tr> EOF; foreach ($digits as $val) { $content .= "<td>{$val}</td>"; } $content .=<<<EOF </tr> </tbody> </table> <br /><br /> <table> <tbody> <tr> <th>ボックス値</th><th>直近の出現時期</th><th>直近の当選番号<br />(ストレート)</th><th>このボックス値<br />の出現回数</th> </tr> EOF; foreach ($result as $val) { $content .= "<tr><td>{$val[0]}</td><td>{$val[1]}(第{$val[2]}回)</td><td>{$val[3]}</td><td>{$val[4]}回</td></tr>"; } $content .=<<<EOF </tbody> </table> </div> EOF; |
こんな感じでいかがでしょう~?
A子
B美
C菜
| 5.WriteHTMLメソッドの引数として3と4(の変数)を渡したあと、PDFデータの書き出しを行う |
B美
|
//PDFへの書き出し
$mpdf->WriteHTML($css, HTMLParserMode::HEADER_CSS); $mpdf->WriteHTML($content, HTMLParserMode::HTML_BODY); $pdf = $mpdf->Output('', 'S'); |
これも(難しく考えず)定型的な記述だと思って良いわ
A子
| 6.レスポンスを返す |
てか、さっきの$pdfをreturnするだけじゃないの?
B美
まずはファイル保存する際のデフォルトのファイル名を決めましょう
(「numbers3box_年月日_時分秒.pdf」で良いかな?)
| $fileName = 'numbers3box_'.date('Ymd_His').'.pdf'; |
で、レスポンス自体はこう書くの
|
return $this->response
->withType('pdf') ->withHeader( 'Content-Disposition', 'inline; filename="' . $fileName . '"' ) ->withStringBody($pdf); |
ちなみに、ヘッダーで「inline」指定しているのは(ダウンロードではなく)ブラウザ表示させたいからよ
C菜
候補数字は適当に「5」「2」「6」「7」としてみました~
(特に意味はありませんけど~)
↓
↓
A子
ちなみに「保存」アイコンを押したときの(デフォルト)ファイル名もバッチリだったよ
(「numbers3box_20260613_091713.pdf」というファイル名だった)
C菜
(もちろん、「mPDF」というPHPライブラリとB美部長の存在あってのことですけど~)


