mPDFでPDFが開けない・出力できないときの確認方法:BOM・空白・警告を切り分ける
作成日:2026.11.07
mPDFで出力済みエラーが出たり、PDFが開けなかったりするときに、生成処理とHTTPでの返却を分けて原因を調べる方法を紹介します。BOM・空白・デバッグ出力・PHP警告の混入を比較し、出力バッファーやdebug設定による違い、ログで出力元を特定する手順を解説します。
目次
mPDFでPDFを返そうとしたら、出力済みというエラーになったり、ダウンロードしたファイルを開けなかったりすることがあります。PDFのレイアウトを調べる前に、PHPの警告やデバッグ出力が混ざっていないか確認してみましょう。
今回は、PDFの生成とブラウザーへの返却を分けてから、BOM・空白・デバッグ出力・PHP警告を混ぜた場合の違いを確認します。インストールはmPDFの入門記事、日本語フォントや改ページは日本語PDFの設定記事を参照してください。
コードと比較結果は、Docker内のDebian 12(linux/amd64)、PHP 8.3.36、mPDF 8.2.6、Composer 2.10.3で確認しました。HTTP側はPHP組み込みWebサーバーを使っています。
PDFの生成とHTTPでの返却を分ける
mPDFは、HTMLから作ったPDFをファイルへ保存することも、HTTPレスポンスとして返すこともできます。ファイル保存は成功するのにブラウザーへ返せないなら、先に返してしまった出力やヘッダーを調べる手掛かりになります。
まず、ComposerでmPDFを導入済みのプロジェクトで、次をmake-mpdf.phpとして保存します。PHPファイルはUTF-8のBOMなしで保存し、末尾の?>は書かない形にしておきます。
<?php
require_once __DIR__ . '/vendor/autoload.php';
function makeMpdf(): \Mpdf\Mpdf
{
$mpdf = new \Mpdf\Mpdf([
'tempDir' => __DIR__ . '/work/mpdf',
]);
$mpdf->WriteHTML('<h1>PDF output test</h1><p>Hello, PDF!</p>');
return $mpdf;
}
work/mpdfと、後で保存先に使うworkは、PHPの実行ユーザーが書き込める専用ディレクトリとして先に用意してください。生成したファイルを公開しないよう、Webサーバー側でこのディレクトリへのアクセスも防ぎます。フォントの問題と分けるため、ここでは英字だけの最小HTMLにしています。
まずファイル保存を確認する
次をsave.phpに保存し、検証環境のコマンドラインからphp save.phpで実行します。
<?php
require __DIR__ . '/make-mpdf.php';
$mpdf = makeMpdf();
$mpdf->OutputFile(__DIR__ . '/work/check.pdf');
check.pdfを開き、見出しと本文が表示されるか確認します。保存自体に失敗するなら、例外やログを確認し、一時ディレクトリ・保存先の権限などから調べます。保存に成功しても、CLIとWeb側のPHP・設定・実行ユーザーが異なる場合は、Web側でも確認が必要です。
次にブラウザーへ返す
同じディレクトリに、次のinline.phpを置きます。これはフレームワークを使わない検証用のPHPです。
<?php
try {
require __DIR__ . '/make-mpdf.php';
$mpdf = makeMpdf();
$mpdf->OutputHttpInline();
} catch (\Throwable $e) {
error_log(get_class($e) . ': ' . $e->getMessage());
if (!headers_sent()) {
http_response_code(500);
header('Content-Type: text/plain; charset=UTF-8');
echo 'PDFを出力できませんでした。ログを確認してください。';
}
}
exit;
OutputHttpInline()はPDFをブラウザー内で表示するための出力、OutputHttpDownload('check.pdf')はダウンロード用の出力です。これらのメソッドはmPDF 8.1.2以降で使えます。出力後に別のHTMLやデバッグ文字列を続けないようにします。詳しくはmPDF公式の出力先の説明を参照してください。
エラーの詳細はPDFの本文へechoせず、ログへ出しています。ただし、先に送ったデータは、この例外処理で取り消せません。出力バッファーに残ったデータをこのコードが消してくれるわけでもないので、混入元の修正が必要です。
BOM・空白・警告を一つずつ混ぜて確認する
検証では、正常な出力に対して次の条件を一つずつ追加します。実際の帳票でまとめて試すより、どの条件で変わるか分かりやすくなります。
- 読み込み先のPHPファイルをUTF-8のBOM付きにする
- 読み込み先の
<?phpより前に空白を置く - PDF出力前に
echo 'DEBUG';を実行する - 画面に表示されるPHP警告を発生させる
UTF-8のBOMは、ファイル先頭のEF BB BFという3バイトです。PHPタグの外にあるため、読み込んだだけで出力へ混ざる原因になります。エディターでBOMなしにしても、require先にBOMが残っていないか確認してください。
PHP警告の説明用には、検証環境だけで次を実行できます。trigger_error()によるユーザー警告で、実際の業務処理の不具合を再現したものではありません。
trigger_error('PDF output test warning', E_USER_WARNING);
この警告がレスポンスへ出るかは、error_reportingやdisplay_errors、独自のエラーハンドラーにも左右されます。警告の原因を直すことと、利用者向けレスポンスへ詳細を出さないことは、分けて対応します。
出力バッファーによって結果が変わる
PHPは、echoなどの出力をすぐ送らず、バッファーへためることがあります。output_bufferingやob_start()が関係するため、「文字を出したのにエラーにならない」場合もあります。
今回の最小例では、output_buffering=0と4096を切り替え、display_errors=On、log_errors=On、html_errors=Off、error_reporting=E_ALLで比較しました。圧縮は無効にし、mPDFのallow_output_bufferingは既定のfalseです。表の「バッファーあり」は、PDF出力前の短い出力が4096バイトのバッファーに残っている条件を指します。
| 追加した出力 | バッファーなし・debug=false | バッファーあり・debug=false | バッファーあり・debug=true |
|---|---|---|---|
| なし | PDFを返せた | PDFを返せた | PDFを返せた |
| BOM | 送信済みのため例外 | PDFの前に3バイト混入 | バッファー内の出力を検出して例外 |
| 空白1文字 | 送信済みのため例外 | PDFの前に1バイト混入 | バッファー内の出力を検出して例外 |
| echo 'DEBUG' | 送信済みのため例外 | PDFの前に5バイト混入 | バッファー内の出力を検出して例外 |
| ユーザー警告 | 送信済みのため例外 | PDFの前に警告文が混入 | 直前のPHPエラーを検出して例外 |
この表はOutputHttpInline()の結果です。バッファーがあると、エラーが出ずに混ざる場合がある点に注意してください。今回、先頭に出力が混ざったデータでも、検証用のPDF解析ツールで本文を取得できたものがありました。混入しているから必ず同じ表示エラーになる、とは言えません。各ブラウザーでの表示可否とは分けて扱います。
ファイル保存も比較したところ、debug=falseでは、4種類の出力があっても保存したPDF本体には混ざりませんでした。ただし、同じ処理の標準出力やHTTP本文には不要な出力が残ります。また、debug=trueで警告を出した場合はファイル保存も停止したため、「保存ならどの条件でも成功する」という意味ではありません。
ダウンロードとdebug設定の違いも確認する
mPDF 8.2.6のOutputHttpDownload()では、バッファー内のBOM・空白・デバッグ文字列は、debug=trueでも同じようには検出されませんでした。先に送信済みなら例外になりますが、バッファーに残っている場合はPDFの前に混ざります。ユーザー警告は、debug=trueだと別のエラー検出で停止しました。
debug=trueとallow_output_buffering=trueを組み合わせたインライン出力も確認しました。バッファー内のBOMなどを理由にした停止は避けられても、混入したデータは消えません。警告による停止や、送信済みのヘッダーも、この設定だけでは解決しません。設定名だけを見て変更せず、出力先とログの内容を確認しましょう。
さらに、output_buffering=0でも、明示的にob_start()すると出力をためられます。検証ではその条件も確認しました。出力サイズや明示的なフラッシュ、ほかのバッファー処理によって送信タイミングが変わるため、設定値だけで決めつけないようにします。
出力元は画面ではなくログで調べる
mPDFを呼び出す直前に、次を一時的に入れると、ヘッダーの送信状態と出力バッファーの状態を確認できます。var_dump()で画面へ出すと、その診断結果まで混ざるのでログを使います。
$outputFile = '';
$outputLine = 0;
$sent = headers_sent($outputFile, $outputLine);
error_log(json_encode([
'headers_sent' => $sent,
'output_file' => $outputFile,
'output_line' => $outputLine,
'buffer_level' => ob_get_level(),
'buffer_bytes' => ob_get_length(),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
headers_sent()がtrueなら、出力が始まったファイルと行番号が手掛かりになります。起動時のエラーなどではファイル名が空になる場合もあります。falseでも、出力がバッファーに残っている可能性があるため、「何も出力されていない」とは判断できません。ob_get_length()は現在の最上位バッファーの長さで、バッファーがなければfalseです。入れ子の全バッファーを合計した値ではありません。
設定ファイルや共通処理を読み込んでいる場合は、読み込み前後で同じ診断を入れ、どこから変わるかを絞ります。本文そのものをログへ記録すると個人情報まで残ることがあるため、まずは出力の有無やバイト数から確認するのがよさそうです。
HTTPステータスと本文も別々に見る
ブラウザーの開発者ツールやcurlで、HTTPステータスとContent-Type、返却本文を確認します。WindowsのPowerShellなら、例えば次のようにヘッダーと本文を別ファイルへ保存できます。ローカル検証用のURLへ置き換えて実行してください。
curl.exe --silent --show-error --dump-header headers.txt --output response.bin http://127.0.0.1:8080/inline.php
Get-Content -LiteralPath .\headers.txt
Format-Hex -LiteralPath .\response.bin | Select-Object -First 4
ステータスが200でも、本文がPDFとは限りません。Content-Type: application/pdfも、中身が正常である保証にはなりません。先頭にDEBUGや警告、BOMがあれば混入を疑います。一方、先頭が%PDF-でも、それだけでPDF全体が正常とは判断できません。
バッファーを消す前に、混入元を直す
BOMを除去し、PHPだけのファイルでは閉じタグを省略し、echoやvar_dump()を外します。警告がある場合はログから原因を修正します。本番環境ではdisplay_errors=Off、log_errors=Onを基本に、ログの保存先と閲覧権限も確認しましょう。PDFを返すWeb側の実効設定で確認する必要があります。
対処としてob_end_clean()やallow_output_bufferingの変更を見かけることもありますが、まず混ざっている内容を調べたいところです。無条件にバッファーを消すと必要な出力まで失う可能性があり、すでに送信した内容も戻せません。自分でob_start()してHTMLを組み立てる処理なら、そのバッファーからHTMLを取得して終了し、取得した文字列をWriteHTML()へ渡すなど、用途を明確にします。
ファイル保存とHTTP返却を分け、次に出力の発生箇所とバッファーの状態を確認すると、フォントやレイアウトの問題と混同せずに調べられるかと思います。
参考資料
奈良市を拠点に、27年以上の経験を持つフリーランスWebエンジニア、阿部辰也です。
これまで、ECサイトのバックエンド開発や業務効率化システム、公共施設の予約システムなど、多彩なプロジェクトを手がけ、企業様や制作会社様のパートナーとして信頼を築いてまいりました。
【制作会社・企業様向けサポート】
Webシステムの開発やサイト改善でお困りの際は、どうぞお気軽にご相談ください。小さな疑問から大規模プロジェクトまで、最適なご提案を心を込めてさせていただきます。
ぜひ、プロフィールやWeb制作会社様向け業務案内、一般企業様向け業務案内もご覧くださいね。
mPDFで日本語PDFを生成する:フォント登録・改ページ・レイアウト崩れの確認ポイント
2026.09.23
PHPからmPDFで日本語を含むPDFや帳票を生成する際に、Noto Sans JPなどのフォントをプロジェクト側で登録する方法、fontDir・fontdata・tempDirの設定、HTML/CSSの制約、改ページや長い文字列の扱いを解説します。Linuxや本番環境で起きる文字化け・レイアウト崩れを、PHP拡張、フォント、権限、入力HTMLの観点から切り分ける手順も紹介します。
PHPでmPDFを使ってHTMLをPDFに変換する方法
2025.02.07
PHPのライブラリmPDFを使用して、HTMLドキュメントをPDFとして出力する方法を詳しく解説します。インストールから日本語対応まで、具体的なコード例を交えて説明します。
PhpSpreadsheetでExcelの日付・日時を読み込む:数値・文字列・空欄の扱い方
2026.11.06
PhpSpreadsheetでExcelの日付・日時を読み込み、取り込み先の形式へ変換する方法を解説します。セルの値・型・表示形式の違いを整理し、数値と文字列の混在、空欄、不正な日付、時刻部分を扱うPHPコードを紹介。1900・1904日付システムとタイムゾーンの注意点も確認します。
php -rの使い方:PowerShellでPHPを手軽に試す
2026.11.04
PHPファイルを作らずにコードを実行できる「php -r」の基本的な使い方を紹介します。PowerShellで変数や関数を試す例と、引用符による変数展開でエラーになる原因・対処法を説明し、Bashとの違いやPHPファイルへ切り出す目安も補足します。