跳轉到

進階表單處理

PHP Compatibility

This example uses PHP 8.5 syntax. If your environment runs PHP 8.1 or 7.4, use NextPDF Backport for a backward-compatible build.

PDF 表單生態系包含三種主要格式:AcroForm(PDF 1.2 引入,最普遍)、XFA(XML Forms Architecture,Adobe 專有,靜態與動態兩種)、XFDF(XML Forms Data Format,XFA 的資料交換格式)。NextPDF Pro 提供對三種格式的完整讀寫支援。


格式支援矩陣

功能 AcroForm XFA 靜態 XFA 動態 XFDF
欄位讀取
欄位填充 部分
資料匯出
批次填充
欄位驗證規則 N/A
計算欄位 N/A
數位簽章欄位 部分 N/A

XFA 動態表單的部分功能(如動態新增重複列)需要 JavaScript 執行環境,Pro 套件提供靜態解析,不執行動態邏輯。


FormDataBinder — 表單資料綁定

FormDataBinder 負責將外部資料來源(陣列、PHP 物件、JSON)注入 PDF 表單欄位:

基礎用法

use NextPDF\Pro\Forms\FormDataBinder;
use NextPDF\Pro\Forms\FormFillResult;

$templatePdf = file_get_contents('/templates/employee-onboarding.pdf');

$binder = new FormDataBinder();

/** @var FormFillResult $result */
$result = $binder->fill(
    pdfBytes: $templatePdf,
    data: [
        'employee_name'   => '王大明',
        'employee_id'     => 'EMP-001234',
        'department'      => '工程部',
        'start_date'      => '2026-03-15',
        'contract_type'   => 'full_time',  // 對應單選按鈕值
        'benefits'        => ['health', 'dental', 'vision'], // 多選核取方塊
        'signature_date'  => date('Y-m-d'),
    ],
    flatten: false, // true = 鎖定欄位為不可編輯;false = 保留可編輯性
);

file_put_contents('/output/onboarding-filled.pdf', $result->getPdf());

// 取得填充報告
$report = $result->getReport();
echo 'Fields filled: ' . $report->getFilledCount();
echo 'Fields not found: ' . implode(', ', $report->getUnmatchedKeys());
echo 'Fields skipped (read-only): ' . $report->getSkippedCount();

強型別資料物件

use NextPDF\Pro\Forms\FormDataBinder;
use NextPDF\Pro\Forms\Attribute\FormField;

// 使用 PHP 8.5 readonly + 屬性標注,實現強型別表單資料物件
final readonly class EmployeeFormData
{
    public function __construct(
        #[FormField('employee_name')]
        public string $name,

        #[FormField('employee_id')]
        public string $employeeId,

        #[FormField('department')]
        public string $department,

        #[FormField('start_date', format: 'Y-m-d')]
        public \DateTimeImmutable $startDate,

        /** @var list<string> */
        #[FormField('benefits', type: 'checkbox_group')]
        public array $benefits,
    ) {}
}

$data = new EmployeeFormData(
    name: '王大明',
    employeeId: 'EMP-001234',
    department: '工程部',
    startDate: new \DateTimeImmutable('2026-03-15'),
    benefits: ['health', 'dental'],
);

$binder = new FormDataBinder();
$result = $binder->fillFromObject($templatePdf, $data);

XFA 解析器

use NextPDF\Pro\Forms\XFA\XfaParser;
use NextPDF\Pro\Forms\XFA\XfaDocument;
use NextPDF\Pro\Forms\XFA\XfaField;

$pdf = file_get_contents('/forms/xfa-invoice.pdf');

$parser = new XfaParser();

/** @var XfaDocument $xfaDoc */
$xfaDoc = $parser->parse($pdf);

echo 'XFA Type: ' . $xfaDoc->getType()->value;   // 'static' | 'dynamic'
echo 'XFA Version: ' . $xfaDoc->getVersion();

// 遍歷所有欄位
foreach ($xfaDoc->getFields() as $field) {
    /** @var XfaField $field */
    printf(
        "Field: %-30s | Type: %-15s | Value: %s\n",
        $field->getName(),
        $field->getFieldType()->value,
        $field->getValue() ?? '(empty)',
    );
}

// 填充 XFA 表單資料
$filledPdf = $xfaDoc->fill([
    'invoice.header.invoiceNumber' => 'INV-2026-001',
    'invoice.header.date'          => '2026-03-15',
    'invoice.customer.name'        => 'Acme Corporation',
    'invoice.items.0.description'  => 'PDF Processing License',
    'invoice.items.0.quantity'     => 1,
    'invoice.items.0.unitPrice'    => 9900,
])->toPdf();

XFDF 格式支援

XFDF(XML Forms Data Format)是 ISO 19444-1 定義的表單資料交換格式,常用於與其他系統的資料交換:

use NextPDF\Pro\Forms\XFDF\XfdfParser;
use NextPDF\Pro\Forms\XFDF\XfdfBuilder;

// 解析 XFDF 資料並注入 PDF
$xfdfContent = file_get_contents('/data/form-submission.xfdf');
$parser = new XfdfParser();
$formData = $parser->parse($xfdfContent);

$binder = new FormDataBinder();
$result = $binder->fillFromXfdf($templatePdf, $formData);

// 從已填充的 PDF 匯出 XFDF 資料
$extractor = new FormDataExtractor();
$extractedData = $extractor->extract($result->getPdf());

$builder = new XfdfBuilder();
$xfdfOutput = $builder->build(
    formData: $extractedData,
    pdfPath: 'https://forms.example.com/templates/onboarding.pdf',
);

file_put_contents('/exports/submission.xfdf', $xfdfOutput);

FormDataExtractor — 表單資料萃取

use NextPDF\Pro\Forms\FormDataExtractor;
use NextPDF\Pro\Forms\ExtractedFormData;
use NextPDF\Pro\Forms\FormFieldValue;

$filledPdf = file_get_contents('/submissions/completed-form.pdf');
$extractor = new FormDataExtractor();

/** @var ExtractedFormData $formData */
$formData = $extractor->extract($filledPdf);

// 取得所有欄位值
foreach ($formData->getFields() as $field) {
    /** @var FormFieldValue $field */
    printf(
        "%-30s = %s\n",
        $field->getName(),
        $field->getDisplayValue(),
    );
}

// 轉換為陣列(適合存入資料庫)
$dataArray = $formData->toArray();

// 轉換為 JSON(適合 API 回應)
$json = $formData->toJson(pretty: true);

// 轉換為 CSV(適合批次匯入)
$formData->toCsv(path: '/exports/form-data.csv');

批次表單填充

use NextPDF\Pro\Forms\FormDataBinder;
use NextPDF\Pro\Forms\BatchFormFiller;
use NextPDF\Pro\Forms\BatchFillResult;

$template = file_get_contents('/templates/certificate.pdf');

$records = [
    ['recipient_name' => '王大明', 'course' => 'PDF Engineering', 'date' => '2026-03-01', 'score' => '92'],
    ['recipient_name' => '李小華', 'course' => 'PDF Engineering', 'date' => '2026-03-01', 'score' => '88'],
    ['recipient_name' => '張美玲', 'course' => 'PDF Engineering', 'date' => '2026-03-01', 'score' => '95'],
];

$filler = new BatchFormFiller(
    binder: new FormDataBinder(),
    template: $template,
);

/** @var BatchFillResult $result */
$result = $filler->fill(
    records: $records,
    outputDirectory: '/output/certificates/',
    filenameTemplate: 'certificate-{recipient_name}.pdf',
    flatten: true, // 填充後鎖定
);

printf(
    "Filled: %d / %d | Failed: %d\n",
    $result->getSuccessCount(),
    count($records),
    $result->getFailureCount(),
);

欄位結構探索

在填充前,可先探索 PDF 表單的欄位結構:

use NextPDF\Pro\Forms\FormInspector;
use NextPDF\Pro\Forms\FormField;

$inspector = new FormInspector();
$fields = $inspector->inspect($templatePdf);

foreach ($fields as $field) {
    /** @var FormField $field */
    printf(
        "%-30s | %-15s | Required: %s | Default: %s\n",
        $field->getName(),
        $field->getType()->value,   // text/checkbox/radio/dropdown/signature/date
        $field->isRequired() ? 'Yes' : 'No',
        $field->getDefaultValue() ?? '(none)',
    );

    // 下拉選單的選項清單
    if ($field->hasOptions()) {
        foreach ($field->getOptions() as $option) {
            printf("  Option: %s => %s\n", $option->getValue(), $option->getDisplayText());
        }
    }
}

Bates 編號

Bates 編號是法律文件管理的標準做法,在每頁特定位置印上唯一序號:

use NextPDF\Pro\Bates\BatesNumberer;
use NextPDF\Pro\Bates\BatesOptions;
use NextPDF\Pro\Bates\BatesPosition;

$batesNumberer = new BatesNumberer(
    BatesOptions::create(
        prefix: 'ACME-LEGAL-',
        startNumber: 1,
        padLength: 6,          // ACME-LEGAL-000001
        position: BatesPosition::BottomRight,
        fontSize: 8.0,
        fontColor: '#374151',
        margin: 10.0,
    )
);

$documents = [
    file_get_contents('/docs/contract.pdf'),
    file_get_contents('/docs/exhibits.pdf'),
    file_get_contents('/docs/correspondence.pdf'),
];

$numberedDocs = $batesNumberer->numberBatch($documents);
// 三份文件的頁碼連續:ACME-LEGAL-000001 起始,跨文件累計

foreach ($numberedDocs as $index => $doc) {
    file_put_contents(sprintf('/output/bates-%02d.pdf', $index + 1), $doc);
}

相關資源