✅ やりたいこと
Laravelアプリ上で、
・PDFを綺麗に出力し
・ブラウザで自動表示し
・印刷ダイアログまで自動で出す
という一連の流れを実現したい!
✅ 技術スタック
・Laravel 9
・spatie/browsershot
・Node.js + Puppeteer
・JavaScript (iframe.onload → print())
・Tailwind CSS(PDFでは非対応。CSSはstyle属性で対応)
✅ 完成イメージ
ボタンをクリックすると…
✅ サーバーでPDF生成
✅ 自動的に新しいタブで表示
✅ 即印刷ダイアログが開く!
↓ サンプル動画
✅ 1:Node.js & Puppeteer インストール手順(Apple Silicon対応)
✅ 2:Laravel側に Browsershot 導入
✅ 3:MacでのChromium設定(Google Chromeの代わり)
Puppeteer が使う Chromium を Mac 用に別途インストールします
brew install --cask chromium
インストール後にパス確認:
ls /Applications/Chromium.app/Contents/MacOS/Chromium
↓
.env に以下を追加
# Browsershot(PDF、印刷)
CHROME_PATH="/Applications/Chromium.app/Contents/MacOS/Chromium"
↓
設定ファイル config/browsershot.php を作成
<?php
return [
'chrome_path' => env('CHROME_PATH'),
];
↓
コントローラーで反映
Browsershot::html($html)
->setNodeBinary('/usr/local/bin/node')
->setIncludePath('/usr/local/bin')
->setChromePath(config('browsershot.chrome_path'))
->format('A4')
->showBackground()
->save($pdfPath);
↓
🔹 3-1:ControllerにPDF生成&中継ビュー表示処理を追加
<?php
namespace App\Http\Controllers;
// ⭐️印刷
public function generateAndPrint($id)
{
// ✅ 情報の取得
/** @var \App\Models\User $user */
$user = Auth::user();
$receipt = $user
->receipts()
->with(['paymentMethod', 'bentoDetails'])
->findOrFail($id);
// ✅ PDF作成
$html = view('pdf.receipt', compact('receipt'))->render();
$customerName = preg_replace('/[^\w\-]/u', '_', $receipt->customer_name);
$filename = "receipt_{$customerName}_{$id}.pdf";
$pdfPath = storage_path("app/public/tmp/{$filename}");
Browsershot::html($html)
->setNodeBinary('/usr/local/bin/node')
->setIncludePath('/usr/local/bin')
->setChromePath(config('browsershot.chrome_path'))
->format('A4')
->showBackground()
->save($pdfPath);
// ✅ PDF作成完了後、中継ビューへリダイレクト
return redirect()->route('receipts.print.show', ['filename' => $filename]);
}
}
🔹 3-2:setNodeBinary と setIncludePath も .env 切り替えを行う
setNodeBinary と setIncludePath も .env で柔軟に切り替えられるようにしておくことで、本番・開発・Apple Silicon など環境ごとの切り替えが容易になります。
.env に以下を追加
# Browsershot(PDF、印刷)
CHROME_PATH="/Applications/Chromium.app/Contents/MacOS/Chromium"
NODE_PATH="/usr/local/bin/node"
INCLUDE_PATH="/usr/local/bin"
↓
設定ファイル config/browsershot.php を作成
<?php
return [
'chrome_path' => env('CHROME_PATH'),
'node_path' => env('NODE_PATH'),
'include_path' => env('INCLUDE_PATH'),
];
↓
コントローラーで反映
Browsershot::html($html)
->setNodeBinary('/usr/local/bin/node')
->setIncludePath('/usr/local/bin')
->setChromePath(config('browsershot.chrome_path'))
->format('A4')
->showBackground()
->save($pdfPath);
✅ 4:印刷ボタン
// /views/receipts/show.blade.php
{{-- 印刷 --}}
<form action="{{ route('receipts.generate_and_print', [ 'id' => $receipt->id ]) }}"
target="_blank">
<button type="submit"
class="text-white bg-indigo-500 border-0 py-2 px-8 focus:outline-none hover:bg-indigo-600 rounded text-lg">
印刷する
</button>
</form>
✅ 5:ルーティング
// 印刷
// PDF生成 & 中継ページへ
Route::get('/receipts/pdf/print/{id}', [ReceiptController::class, 'generateAndPrint'])->name('receipts.generate_and_print');
// 中継ビュー(iframe + 自動印刷)
Route::get('/receipts/print/show/{filename}', [ReceiptController::class, 'showPrintView'])->name('receipts.print.show');
✅ 6:tmpディレクトリ(storage/app/public/tmp)作成
mkdir -p storage/app/public/tmp
chmod -R 775 storage/app/public/tmp
↓
public/storage にシンボリックリンクを作成
php artisan storage:link
✅ 7:中継ビューでPDF表示&自動印刷
// /views/pdf/print_redirect.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>印刷中...</title>
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
}
iframe {
width: 100%;
height: 100%;
border: none;
}
</style>
</head>
<body>
<iframe id="pdfFrame" src="{{ $pdfUrl }}"></iframe>
<script>
const iframe = document.getElementById('pdfFrame');
iframe.onload = () => {
iframe.contentWindow.focus();
iframe.contentWindow.print();
};
</script>
</body>
</html>
✅ 8:中継ビュー用 Controller を追加
public function showPrintView($filename)
{
$pdfUrl = asset("storage/tmp/{$filename}");
return view('pdf.print_redirect', compact('pdfUrl'));
}
✅ 9:1時間以上経過したPDFは自動削除
🔹 9-1:tmp フォルダを .gitignore に追加(Git管理対象外に)
/storage/app/public/tmp/
🔹 9-2:Artisanコマンドで「古いPDFファイルを削除するスクリプト」を作成
php artisan make:command DeleteOldPdfs
↓
作成後、以下のように編集:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Carbon\Carbon;
class DeleteOldPdfs extends Command
{
protected $signature = 'pdf:cleanup'; // コマンド名
protected $description = '1時間以上前に作られたPDFを削除します';
public function handle()
{
$dir = storage_path('app/public/tmp');
$deleted = 0;
foreach(File::files($dir) as $file) {
// ファイルの作成から1時間以上経ってたら削除
if(now()->diffInMinutes(Carbon::createFromTimestamp(filemtime($file))) > 60) {
File::delete($file);
$deleted++;
}
}
$this->info("🗑️ {$deleted}個の古いPDFを削除しました!");
Log::info("🗑️ [pdf:cleanup] 削除数: {$deleted}"); // 本番で使用するログ
}
}
🔹 9-3:スケジュールに登録する
protected function schedule(Schedule $schedule)
{
// 毎時このコマンドを実行(1時間以上前のPDF削除)
$schedule->command('pdf:cleanup')->hourly();
}
🔹 9-4:本番サーバでcron登録(ローカルなら不要)
🔸 9-4-1:PHP のフルパス確認
which php
# 例:/usr/bin/php
🔸 9-4-2:cron を登録するユーザーを選択
Laravel アプリを Web 経由で実行する場合は、Web サーバーの実行ユーザー(例:apache, www-data) に登録するのが推奨です。
Linuxサーバでターミナルから:
# 推奨:Webサーバーの実行ユーザー(例:apacheやwww-data)のcronを設定
sudo crontab -u apache -e
# rootの場合
crontab -e
真っ白画面が出てくるのでそれを編集
↓
🔸 9-4-3:以下を追記(Laravelのschedulerを毎分呼び出す):
Laravelアプリのプロジェクトルート(/var/www/your-app)
+
さっきのフルパス(/usr/bin/php)
で記載
※ cd のパスは artisan があるプロジェクトルートにしてください。
# ログを残したい場合
* * * * * cd /var/www/your-app && /usr/bin/php artisan schedule:run >> /var/www/cron.log 2>&1
# ログ残さない場合
* * * * * cd /var/www/your-app && /usr/bin/php artisan schedule:run > /dev/null 2>&1
# 例)conohaVPSの場合
* * * * * cd /var/www/your-app && /usr/bin/php artisan schedule:run >> /var/www/cron.log 2>&1
保存後、確認:
# 選んだ方
sudo crontab -u apache -l
crontab -l
🔸 9-4-4:.env でログ設定を修正
sudo -u apache vi .env
以下に修正:
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=info
🔸 9-4-5:ログ確認コマンド
# cleanupログのみ確認
tail -f storage/logs/laravel.log | grep cleanup
これで、次のような行が出ればOK:
[2025-07-17 21:00:00] local.INFO: 🗑️ [pdf:cleanup] 削除数: 1
🔸 9-4-6:ログのstarage保管日数を制限
容量確認コマンド
# 蓄積容量
du -sh storage/logs
# 残り容量
df -h storage/logs
ログのstarage保管日数を制限
config/logging.php の daily 設定にして保存日数を制限
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
],
補足:古いログを手動削除
find storage/logs -type f -name "*.log" -mtime +30 -delete
⭐️ 続き(本番環境)
⭐️ 関連
一括印刷