1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【開発環境】Laravel + BrowsershotでPDFを生成 → プレビュー表示 → 自動印刷まで一括完了する方法

Last updated at Posted at 2025-07-09

✅ やりたいこと

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 に以下を追加

.env
# Browsershot(PDF、印刷)
CHROME_PATH="/Applications/Chromium.app/Contents/MacOS/Chromium"


設定ファイル config/browsershot.php を作成

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生成&中継ビュー表示処理を追加

/Http/Controllers/ReceiptController.php
<?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 に以下を追加

.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 を作成

config/browsershot.php
<?php

return [
    'chrome_path' => env('CHROME_PATH'),
    'node_path' => env('NODE_PATH'),
    'include_path' => env('INCLUDE_PATH'),
];


コントローラーで反映

/Http/Controllers/ReceiptController.php
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:ルーティング

routes/web.php
// 印刷
// 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 を追加

/Http/Controllers/ReceiptController.php
public function showPrintView($filename)
{
    $pdfUrl = asset("storage/tmp/{$filename}");
    return view('pdf.print_redirect', compact('pdfUrl'));
}

✅ 9:1時間以上経過したPDFは自動削除

🔹 9-1:tmp フォルダを .gitignore に追加(Git管理対象外に)

.gitignore
/storage/app/public/tmp/

🔹 9-2:Artisanコマンドで「古いPDFファイルを削除するスクリプト」を作成

php artisan make:command DeleteOldPdfs


作成後、以下のように編集:

app/Console/Commands/DeleteOldPdfs.php
<?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:スケジュールに登録する

app/Console/Kernel.php
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

以下に修正:

.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 設定にして保存日数を制限
config/logging.php
'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

⭐️ 続き(本番環境)

⭐️ 関連

一括印刷

PDF

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?