跳轉到

CodeIgniter 快速開始

本指南說明如何在 CodeIgniter 4.6 專案中整合 nextpdf/codeigniter,包含 Services 配置、便利 Helper 函數,以及非同步 Queue Job 的使用方式。

前置條件: - CodeIgniter 4.6.x 專案 - PHP 8.5+ - 已完成 安裝指南 中的基本設定


Backport 相容性說明

PHP 8.5 語法需求nextpdf/codeigniter 使用 PHP 8.5 語法特性。

若你的 CodeIgniter 應用程式執行於 PHP 8.1,請改用 nextpdf/backport 套件, 並參閱 PHP 相容性說明


步驟一:安裝套件

composer require nextpdf/codeigniter

nextpdf/codeigniter 透過 CodeIgniter 4 的 Composer 自動探索機制, 自動在框架啟動時初始化 Helper 與 Services。


步驟二:設定 Services

在你的 app/Config/Services.php 中,nextpdf/codeigniter 已自動透過 Composer Psr-4 autoloading 將自身的 Service 定義注入 CodeIgniter 的服務容器。

若需要自訂設定,可建立 app/Config/NextPdf.php

<?php

declare(strict_types=1);

namespace Config;

use CodeIgniter\Config\BaseConfig;

final class NextPdf extends BaseConfig
{
    /**
     * Path to custom font directory.
     *
     * @var non-empty-string
     */
    public string $fontPath = WRITEPATH . 'nextpdf/fonts';

    /**
     * Font cache directory.
     *
     * @var non-empty-string
     */
    public string $fontCache = WRITEPATH . 'cache/nextpdf/fonts';

    /**
     * Enable Spectrum accelerator sidecar.
     */
    public bool $spectrumEnabled = false;

    /**
     * Spectrum socket DSN.
     *
     * @var non-empty-string
     */
    public string $spectrumSocket = 'tcp://localhost:9000';

    /**
     * Default storage base path for saved PDFs.
     *
     * @var non-empty-string
     */
    public string $storagePath = WRITEPATH . 'pdfs';
}

步驟三:使用 Services

透過 CodeIgniter 的 Services 靜態方法取得 PdfFactory

<?php

declare(strict_types=1);

namespace App\Controllers;

use CodeIgniter\HTTP\ResponseInterface;
use NextPDF\CodeIgniter\Services;

final class InvoiceController extends BaseController
{
    public function download(int $invoiceId): ResponseInterface
    {
        $factory  = Services::pdfFactory();
        $document = $factory->create();

        $document->addPage()
            ->setFont(family: 'NotoSans', size: 12)
            ->text("發票編號:INV-{$invoiceId}", x: 20, y: 30)
            ->text('金額:NT$ 1,000', x: 20, y: 45)
            ->text('日期:' . date('Y-m-d'), x: 20, y: 60);

        // 取得 PDF 二進位內容
        $content = $document->output();

        return $this->response
            ->setHeader('Content-Type', 'application/pdf')
            ->setHeader('Content-Disposition', "inline; filename=\"invoice-{$invoiceId}.pdf\"")
            ->setBody($content);
    }
}

步驟四:使用 Helper 函數

載入 NextPDF Helper 後,可使用 pdf()pdf_document() 快速函數:

<?php

declare(strict_types=1);

namespace App\Controllers;

use CodeIgniter\HTTP\ResponseInterface;

final class ReportController extends BaseController
{
    public function __construct()
    {
        // 載入 NextPDF Helper
        helper('nextpdf');
    }

    public function generate(): ResponseInterface
    {
        // pdf() — 建立並直接輸出 PDF Response
        return pdf(function (\NextPDF\Core\Document $doc): void {
            $doc->addPage()
                ->setFont(family: 'NotoSans', size: 16)
                ->text('月度銷售報表', x: 20, y: 30)
                ->setFont(family: 'NotoSans', size: 12)
                ->text('期間:' . date('Y-m'), x: 20, y: 50);

            // <!-- PLACEHOLDER:CONTENT:report-body — Report table, charts, summary -->
        }, filename: 'report-' . date('Ym') . '.pdf');
    }

    public function saveToFile(): ResponseInterface
    {
        // pdf_document() — 建立 Document 實例,由呼叫者控制儲存方式
        $document = pdf_document(function (\NextPDF\Core\Document $doc): void {
            $doc->addPage()
                ->text('存檔範例', x: 20, y: 30);
        });

        $path = WRITEPATH . 'pdfs/report-' . date('Ymd') . '.pdf';
        $document->save($path);

        return $this->response->setJSON(['saved' => true, 'path' => $path]);
    }
}

Helper 函數簽章

/**
 * Create a PDF and return a CodeIgniter ResponseInterface.
 *
 * @param callable(\NextPDF\Core\Document): void $builder
 * @param non-empty-string                       $filename
 * @param 'inline'|'attachment'                  $disposition
 */
function pdf(
    callable $builder,
    string $filename = 'document.pdf',
    string $disposition = 'inline',
): \CodeIgniter\HTTP\ResponseInterface {}

/**
 * Create and return a NextPDF Document instance.
 *
 * @param callable(\NextPDF\Core\Document): void $builder
 */
function pdf_document(
    callable $builder,
): \NextPDF\Core\Document {}

步驟五:在 View 中使用

<?php

declare(strict_types=1);

namespace App\Controllers;

use CodeIgniter\HTTP\ResponseInterface;
use NextPDF\CodeIgniter\Services;

final class ContractController extends BaseController
{
    public function preview(int $contractId): ResponseInterface
    {
        $factory = Services::pdfFactory();

        // 先用 CodeIgniter View 渲染 HTML
        $html = view('pdfs/contract', [
            'contract_id' => $contractId,
            'date'        => date('Y-m-d'),
            'client'      => 'Acme Corp.',
        ]);

        // 從 HTML 建立 PDF(需 nextpdf/artisan)
        $document = $factory->createFromHtml($html);
        $content  = $document->output();

        return $this->response
            ->setHeader('Content-Type', 'application/pdf')
            ->setHeader('Content-Disposition', "inline; filename=\"contract-{$contractId}.pdf\"")
            ->setBody($content);
    }
}

對應的 View 檔案 app/Views/pdfs/contract.php

<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="UTF-8">
    <style>
        body { font-family: 'Noto Sans TC', sans-serif; margin: 40px; }
        h1   { color: #1E3A8A; }
    </style>
</head>
<body>
    <h1>合約編號:<?= esc($contract_id) ?></h1>
    <p>日期:<?= esc($date) ?></p>
    <p>乙方:<?= esc($client) ?></p>
    <!-- PLACEHOLDER:CONTENT:contract-template-body — Full contract body -->
</body>
</html>

步驟六:Queue Job 非同步生成

CodeIgniter 4 的 Tasks 或第三方 Queue 套件可用於非同步 PDF 生成:

使用 codeigniter4/tasks(排程)

<?php

declare(strict_types=1);

namespace App\Tasks;

use CodeIgniter\Tasks\Task;
use NextPDF\CodeIgniter\Services;
use Psr\Log\LoggerInterface;

final class MonthlyReportTask extends Task
{
    public function run(): void
    {
        $factory  = Services::pdfFactory();
        $document = $factory->create();

        $month = date('Y-m', strtotime('first day of last month'));

        $document->addPage()
            ->setFont(family: 'NotoSans', size: 20)
            ->text("月度報表 {$month}", x: 20, y: 30);

        // <!-- PLACEHOLDER:CONTENT:monthly-report-content — Monthly report pages -->

        $outputPath = WRITEPATH . "pdfs/monthly-report-{$month}.pdf";
        $document->save($outputPath);

        log_message('info', "月度報表已生成:{$outputPath}");
    }
}

Queue Job(使用 codeigniter4-queue)

<?php

declare(strict_types=1);

namespace App\Jobs;

use CodeIgniter\Queue\BaseJob;
use CodeIgniter\Queue\Interfaces\JobInterface;
use NextPDF\CodeIgniter\Services;

final class GeneratePdfJob extends BaseJob implements JobInterface
{
    /**
     * @param array{user_id: int, report_month: non-empty-string} $data
     */
    public function process(): bool
    {
        $userId      = $this->job->payload['user_id'];
        $reportMonth = $this->job->payload['report_month'];

        $factory  = Services::pdfFactory();
        $document = $factory->create();

        $document->addPage()
            ->setFont(family: 'NotoSans', size: 20)
            ->text("報表 {$reportMonth}(使用者 {$userId})", x: 20, y: 30);

        // <!-- PLACEHOLDER:CONTENT:job-pdf-content — PDF content builder -->

        $path = WRITEPATH . "pdfs/user-{$userId}/{$reportMonth}.pdf";
        @mkdir(dirname($path), 0755, recursive: true);
        $document->save($path);

        return true;
    }
}

分派 Job:

// 在 Controller 中分派
service('queue')
    ->push('default', 'GeneratePdf', [
        'user_id'      => $this->request->getPost('user_id'),
        'report_month' => date('Y-m'),
    ]);

路由設定

// app/Config/Routes.php
$routes->group('api', ['namespace' => 'App\Controllers'], function ($routes) {
    $routes->get('invoices/(:num)/pdf', 'InvoiceController::download/$1');
    $routes->post('reports/generate', 'ReportController::generate');
    $routes->get('contracts/(:num)/preview', 'ContractController::preview/$1');
});

測試

<?php

declare(strict_types=1);

namespace App\Tests\Controllers;

use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FeatureTestTrait;
use NextPDF\CodeIgniter\Testing\PdfFactoryFake;
use NextPDF\CodeIgniter\Services;

final class InvoiceControllerTest extends CIUnitTestCase
{
    use FeatureTestTrait;

    protected function setUp(): void
    {
        parent::setUp();

        // 注入 Fake PdfFactory
        Services::injectMock('pdfFactory', new PdfFactoryFake());
    }

    public function test_invoice_pdf_download_returns_pdf_response(): void
    {
        $result = $this->get('api/invoices/1/pdf');

        $result->assertOK();
        $result->assertHeader('Content-Type', 'application/pdf');
    }
}

下一步