Reports.web PHP WASM言語ラッパー プログラマーズガイド/APIリファレンス
Reports.webのWASM言語ラッパーは、PHPの業務データから、共通の印刷データPREPEJを組み立てるための小さなライブラリです。帳票の座標計算やPDF描画を各言語へ重複実装せず、ブラウザまたはサーバーの共通WASM帳票エンジンへ任せます。
この一冊では、初めて利用する方が次のプログラムを作れるようになることを目標にしています。
- PHPから帳票定義PREPDJへ業務データを流し込む
- 完成した印刷データPREPEJをJSON、ファイル、HTTPで受け渡す
- ブラウザでSVGプレビュー・印刷・PDF生成を行う
- サーバー側の共通WASMエンジンでPDFを生成・配信する
- PHPラッパーの主要APIを調べる
1. 最初に知っておくことRM001
1.1 「WASM言語ラッパー」とはRM002
本書では、各言語でPREPEJを組み立てる補助ライブラリをWASM言語ラッパーと呼びます。実装の中心はPrintDataです。ラッパー自身はWASMを直接ロードせず、SVGやPDFも描きません。出力形式を共通JSONに揃えることで、業務処理と描画エンジンを疎結合にします。
これはPure Java/Pure Python/WPF・.NETの言語別エンジンとは異なる方式です。Pure系は各言語の帳票エンジンを直接呼びます。WASM方式は7言語が同じWASMエンジンとブラウザUIを共有します。
1.2 帳票ができるまでRM003
- デザイナーで帳票定義
.prepdjを作ります。 - PHPの
PrintDataへPREPDJを設定します。 - DBやAPIから取得した値を、デザイン上のオブジェクト名へ設定します。
- 1ページずつ確定し、
.prepejを作ります。 - PREPEJをブラウザのプレビュアー、またはサーバーWASMへ渡します。
- 共通エンジンがSVGプレビュー、PDF、印刷を担当します。
1.3 二つのファイルRM004
| 拡張子 | 呼び名 | 役割 |
|---|---|---|
.prepdj |
帳票定義 | 用紙、文字、罫線、画像、バーコード、繰返しなどのデザイン |
.prepej |
印刷データ(Print Document) | 帳票定義とページごとの値・属性をまとめた、再表示・再印刷可能な文書 |
PREPEJは単なる一時レスポンスではありません。ファイル保存、再読込、メール添付、REST API、サーバー間転送、後日の再印刷に同じ文書を使えます。
2. サンプルを動かすRM005
基準環境は PHP 8.3 です。同梱のDocker構成では必要な言語環境とDBをコンテナ内に用意するため、まずPC側で準備するのはDockerです。Windows/MacではDocker Desktopを起動し、LinuxではDocker EngineとComposeプラグインが使える状態にしてください。
ZIPを全体ごと展開してください。共通RuntimeとDB初期化データも使うため、番号フォルダーだけを取り出さないでください。初回のイメージ取得とビルドにはインターネット接続が必要です。
短いコードから学ぶ場合は WebAssembly/Samples/php/01.あっという間に帳票出力/README.html から始めます。ここでは全帳票を確認できる02の起動を説明します。
- VS CodeやCursor、または使い慣れたエディターで
WebAssembly/Samples/php/02.全帳票をWebでプレビューを開きます。 - そのフォルダーでターミナルを開き、次を実行します。開発用のReports Webリポジトリをビルドする必要はありません。
docker compose up -d --build --wait
docker compose ps
- 起動が成功してから、ブラウザで
http://127.0.0.1:19201/を開きます。URLを開くだけではアプリは起動しません。 - 終了するときは、同じフォルダーで
docker compose downを実行します。このサンプルのコンテナとネットワークが削除されます。DBデータの初期化やイメージの片付けは、そのフォルダーのREADMEに手順があります。
02では、単純帳票、10の倍数、郵便番号一覧、見積書、請求書、商品一覧、名刺、デザイン機能見本を確認できます。請求書などを選び、ソースと出力結果を見比べてください。別のアプリから帳票APIを呼ぶ使い方は、続く03のREADMEに説明しています。
3. 最初の一枚を作るRM006
01のサンプルは、印刷データの形を見やすくするためにPHPの配列を直接組み立てています。業務アプリでは、同じ印刷データをPrintDataクラスで作れます。まず、動いた01の画面を使って、この違いを試しましょう。新しいWebサーバーを作る必要はありません。
- 01のREADMEに沿ってサンプルを起動します。
- ZIP内の
WebAssembly/Samples/php/02.全帳票をWebでプレビュー/src/PrintData.phpを、01のquick-start.phpと同じフォルダーへコピーします。02を起動する必要はありません。 - 元へ戻せるよう
quick-start.phpのコピーを取り、内容を次のコードに置き換えます。report.prepdj、router.php、index.htmlはそのまま使います。
01.あっという間に帳票出力/
├─ quick-start.php ← 次のコードに変更
├─ PrintData.php ← 02のsrcからコピー
├─ report.prepdj ← 同梱の定義をそのまま使う
├─ router.php
└─ index.html
<?php
declare(strict_types=1);
use Pao\Reports\Web\PrintData;
require_once __DIR__ . '/PrintData.php';
function createPrintData(): array
{
$definition = json_decode(
file_get_contents(__DIR__ . '/report.prepdj'),
true, 512, JSON_THROW_ON_ERROR
);
return (new PrintData())
->setDefinition($definition)
->pageStart()
->setValue('Message', 'PrintDataで作った最初の帳票')
->pageEnd()
->toArray();
}
保存して http://127.0.0.1:19200/ を再読み込みします。「PrintDataで作った最初の帳票」と表示されたら成功です。上部の「PDFを保存」でも同じ文字を確認できます。通常起動では、PHPの編集後にDockerを再ビルドする必要はありません。
Messageは同梱のreport.prepdjにある動的テキストのオブジェクト名です。見た目の文字列ではなく、この名前で値の入れ先を指定します。ご自分のデザインを使う場合は、定義ファイルとオブジェクト名の両方を合わせてください。
useはクラス名を短く書くための宣言で、ファイルを読み込む処理ではありません。この例ではrequire_onceで実装を読み込みます。router.phpがcreatePrintData()の戻り値をJSONにしてブラウザへ返すため、ここではheader()やechoを書きません。自作のAPIでJSONを直接返す場合は、同じクラスのtoJson()を使用します。
元の01へ戻すときは、退避したquick-start.phpに戻します。追加したPrintData.phpは元のコードから参照されません。
呼出順は 定義を設定 → ページを開始 → 値を設定 → ページを確定 → JSONを取得 です。未完了のページがある状態ではPREPEJを取得できません。この制約により、途中までの帳票を誤って配信することを防ぎます。
4. PrintData APIリファレンスRM007
配布ZIP内の実装ファイルは WebAssembly/Samples/php/02.全帳票をWebでプレビュー/src/PrintData.php です。各番号のサンプルにも必要なソースを置いています。次のAPI名は実装に合わせています。
| API | 用途 |
|---|---|
setDefinition |
帳票定義(PREPDJ)を設定します。設定値は複製され、呼び出し元の変更から守られます。 |
pageStart |
新しい印刷ページを開始します。別の定義を渡すと、表紙から明細などへ様式を切り替えられます。 |
setValue |
名前で指定した動的オブジェクトへ値を設定します。indexは0から始まる繰返し位置です。 |
setRepeatedValue |
縦横二方向に繰り返すオブジェクトへ、X・Y位置を指定して値を設定します。 |
setTypedValue |
DynamicText、DynamicImage、DynamicBarcodeなど、値の種類を明示して設定します。 |
setImage |
画像URLまたはdata URIを動的画像へ設定します。 |
setBarcode |
バーコード/二次元コードへ渡す文字列を設定します。 |
drawObject |
値を変更せず、指定したオブジェクト/繰返し位置を描画対象にします。 |
changeAttributes |
位置、幅、色、フォント、配置などをページ単位で動的に変更します。 |
changeRepeatedAttributes |
繰返し位置を含めて動的属性を変更します。 |
pageEnd |
現在のページを確定し、印刷データへ追加します。 |
toArray / toJson |
完成したPREPEJをオブジェクトまたはJSON文字列として取得します。 |
save |
完成したPREPEJを一時ファイル経由で安全に保存します。 |
4.1 値とインデックスRM008
引数と戻り値
new PrintData()は引数なしです。以下のselfは同じPrintDataインスタンスを返す意味で、->で呼び出しをつなげられます。$valueは文字列・整数・小数・真偽値・null、$valuesは属性名をキーにしたPHP配列です。
setDefinition(array $definition): self
pageStart(?array $definition = null): self
setValue(string $name, $value, int $index = 0, bool $drawing = true): self
setRepeatedValue(string $name, $value, int $indexX, int $indexY,
bool $drawing = true): self
setTypedValue(string $name, string $type, $value, int $index = 0,
bool $drawing = true): self
setImage(string $name, string $source, int $index = 0,
bool $drawing = true): self
setBarcode(string $name, string $code, int $index = 0,
bool $drawing = true): self
drawObject(string $name, int $index = 0, ?string $type = null): self
changeAttributes(string $name, array $values, int $index = 0): self
changeRepeatedAttributes(string $name, array $values, int $index = 0,
?int $indexX = null, ?int $indexY = null,
?string $type = null): self
pageEnd(): self
toArray(): array
toJson(bool $pretty = true): string
save(string $path): void
これは引数を調べる一覧で、実行用コードではありません。toArray()はPHP配列、toJson()はUTF-8 JSON文字列です。save()はPHPが動いているサーバー上のファイルを保存します。利用者のPCへのダウンロードは、HTTP応答をブラウザー側で保存する別の操作です。保存先のフォルダーは先に用意し、PHP実行ユーザーへ書込権限を与えてください。
呼出順の違反はLogicException、空の名前や負のインデックス等はInvalidArgumentException、保存失敗はRuntimeExceptionとして通知されます。HTTP APIでは例外をログへ記録し、利用者へは内部のパス等を含めないエラー応答を返します。
値には文字列、数値、真偽値、nullの単純値を渡します。配列や任意オブジェクトを値として渡さず、帳票オブジェクトごとに明示的に対応付けてください。繰返しのindex、indexX、indexYは0から始まり、負数はエラーです。
drawing=falseは、その繰返し位置を描画しない場合に使います。値設定はページ内のリストへ順に追加されます。同じ名前・同じ位置へ何度も設定する代わりに、アプリ側で値を確定してから一度設定すると分かりやすくなります。changeRepeatedAttributes()で二方向の位置を指定するときは、indexXとindexYの両方を指定します。
型を省略(null)すると、そのページで有効な帳票定義から対応するDynamic型を推論します。型を明示した場合は指定値を優先します。定義にない名前は従来互換のDynamicTextとして記録されます。空の名前、負のindex、二方向indexの片方だけ、null属性値、非有限の数値は例外です。
4.2 ページごとに定義を切り替えるRM009
表紙と本文、途中から異なる一覧形式などでは、ページ開始時に別のPREPDJを渡します。座標をプログラムへ直接書くのではなく、デザイナーで完成させた定義をページへ結び付けます。定義を変更する前に現在のページを必ず確定してください。
5. 明細と改ページRM010
以下は明細処理の考え方を示す抜粋です。01のMessageだけの定義ではなく、ページ・品番・品名・金額という名前の動的テキストを用意した帳票定義と、code・name・amountの列を持つ$details配列が必要です。そのまま試せる複数ページの実装は02のsrc/SampleCatalog.phpと帳票定義を併せてご覧ください。
$print = (new PrintData())->setDefinition($definition);
foreach (array_chunk($details, 20) as $pageNo => $rows) {
$print->pageStart()->setValue('ページ', $pageNo + 1);
foreach ($rows as $rowNo => $row) {
$print->setValue('品番', $row['code'], $rowNo)
->setValue('品名', $row['name'], $rowNo)
->setValue('金額', number_format($row['amount']), $rowNo);
}
$print->pageEnd();
}
1ページの行数は帳票定義と業務仕様から決めます。最終ページの空行を描画するか、明細ごとに罫線を出すかもプログラム側で明示します。ページ番号はPREPEJのページ順と、帳票へ表示する業務上のページ番号を分けて考えます。
5.1 空文字で罫線・図形を消すRM011
罫線・四角・円(Line、DynamicLine、Square/Rectangle、Circle/Ellipse)は、値で表示を切り替えられます。
| 書く値 | 罫線・図形 |
|---|---|
""(空文字) |
描かない(消える) |
null、または何も書かない |
定義どおり描く |
""以外の値(例 "1") |
描く。値は表示には使わない |
繰返しの行でも、1つだけの図形でも同じです。最初に書いたときも、別の値のあとに書いたときも同じです。表の行を途中で終える、合計行の上だけ線を引かない、条件で枠を消す、などに使います。次の明細線は、定義にある罫線のオブジェクト名の例です。
$print->setValue('明細線', '', $rowNo); // この行の罫線を消す
drawing=falseも描きません。PREPEJでは、図形の値が""ならDrawingがtrueでも描きません。
文字の欄(Text)は別の規則です。値が""またはnullなら、文字も枠線も背景色も描きません。SVG・印刷・PDFで同じです。
5.2 何も書かない繰返しは1行目だけ描くRM012
繰返し(RepeatCountが2以上)のある物体に、そのページで何も書かないと、定義どおり1つだけ、1行目の位置に描きます。行の数だけ並べては描きません。
一部の行にだけ書くと、書いた行だけ描きます。書かなかった行は描きません。1行目に書かなければ、1行目も描きません。
表の罫線や枠を全部の行に描くには、描きたい行それぞれにnull(値なし)を書きます。罫線・図形はnullなら描き、""なら消えます(5.1)。次の1行を上の明細のループに足すと、明細の行数ぶん明細線にnullを書きます。drawObjectは値なし(null)を書きます。
$print->drawObject('明細線', $rowNo); // この行の罫線を描く(null を書く)
文字の欄は、nullを書くとその行の文字が空になります。定義の文字を出したい行には、その文字を書きます。PREPEJへ保存した印刷データでも同じです。
6. 画像・バーコード・動的属性RM013
値を書いていない文字・四角には、同じページで前に値を書いた物体の最後の行と同じ行の属性だけが当たります。値なしの罫線・楕円・画像・バーコードの旧形式の属性は当たりません。
次はpageStart()とpageEnd()の間で呼ぶ抜粋です。定義に角印(動的画像)、商品QR(動的バーコード)、合計金額(動的テキスト)を用意し、$stampDataUriには画像のdata URIを設定しておきます。01の定義へそのまま追加しても、対応するオブジェクトがなければ表示されません。
$print->setImage('角印', $stampDataUri)
->setBarcode('商品QR', 'https://example.jp/items/123')
->changeAttributes('合計金額', [
'bold' => true, 'fontSize' => 16,
'horizontalAlignment' => 'Right'
]);
画像は閲覧先でも取得できる管理下のURL、または許容サイズのdata URIにします。サーバーPDF生成では、任意の外部URLをそのまま取得するとSSRFにつながります。固定画像は許可リストまたはアプリ管理下のファイルから解決してください。
動的属性では、名前・型・値を無検証のリクエストから直接渡さないでください。変更できる属性をアプリ側で限定し、数値範囲、色、配置、フォント名を検証します。
7. ブラウザへ渡して表示するRM014
PHP APIはPREPEJをapplication/json; charset=utf-8で返します。ブラウザ側は共通のReports Web APIへ渡します。次は01の配信URLに合わせた表示部分の例です。自作アプリへ組み込む場合は、プレビュアーと印刷データのURLを、そのアプリの配置先へ変更してください。
<iframe id="reportsViewer" src="/reports.web/preview/?empty=1"></iframe>
<script>
const viewer = document.querySelector('#reportsViewer');
viewer.addEventListener('load', async () => {
const response = await fetch('/print-data');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
await viewer.contentWindow.ReportsWeb.setPrintData(await response.json());
});
</script>
親画面とプレビュアーは同一オリジンに置くのが最も簡単です。別オリジンでpostMessageを使う場合は、送信元と送信先のoriginを固定し、*を本番環境で使わないでください。
プレビュアー側ではsetZoom("auto")、ページ移動、検索、印刷、exportPdf()、savePrintData()を利用できます。独自画面へ表示面だけを組み込む場合はhideAll()でツール群を隠し、同じ機能を外側のボタンから呼び出します。
8. PDFを作る二つの場所RM015
8.1 ブラウザで作るRM016
PREPEJを利用者のブラウザへ渡し、ブラウザ内のWASMがPDFを生成します。サーバーのPDF描画負荷を抑え、プレビューからそのまま保存・印刷する操作に向きます。機密データをブラウザへ渡してよいかは、業務の認可設計で判断します。
8.2 サーバーで作るRM017
PHPから共通Node/WASMサービスへHTTPでPREPEJを送ります。 定期処理、メール添付、保存、監査、画面を開かない帳票生成に向きます。
POST /render/pdf
Content-Type: application/json
<PREPEJ JSON>
成功時はapplication/pdfを返します。失敗時は次の状態コードと、理由を書いたJSON({"error":"…"})を返します。
| 状態 | 意味 | 対処 |
|---|---|---|
| 400 | 入力の誤り。JSONが壊れている、数値が有限の数でない、幅・高さなどが負、定義の数値を文字列で書いた、ルートのVersionが無い・"1.0"のような文字列でない、など |
errorの理由を見て、PREPEJを直します |
| 413 | 大きすぎる要求。64MiBを超える、または64MiB以内でもサーバーのメモリーで処理できる量を超える | ページを分けて送るか、Node.jsのメモリー上限(--max-old-space-size)を増やします |
| 408 | 要求の本文が30秒以内に届かなかった。届かない本文で、後ろに並んだ要求を止めないためです | 送信側の接続を確かめて再実行します。時間は環境変数REPORTS_ENGINE_BODY_TIMEOUT_MS(ミリ秒)で変えられます |
| 503 | 混雑。要求は本文の受け取りから PDF の生成まで1件ずつ順番に処理し、処理の開始まで10秒を超えて待った要求を断ります。処理が5分を超えた要求も断り、次の要求へ進みます | しばらく待って再実行します。待ち時間は環境変数REPORTS_ENGINE_QUEUE_WAIT_MS、処理の時間の上限はREPORTS_ENGINE_OPERATION_TIMEOUT_MS(どちらもミリ秒)で変えられます |
408・413・503のときもサーバーは止まらず、次の要求を処理します。業務アプリ側でも処理時間を制限し、失敗時の途中PDFを保存・配信しないでください。ブラウザ生成とサーバー生成は同じPREPEJを入力にするため、業務データ組立てを二重実装する必要はありません。
9. PREPEJを保存・再利用するRM018
saveは完成したPREPEJをUTF-8 JSONとして保存します。保存前にページをすべて確定してください。ファイル名には.prepejを使い、アップロード時には拡張子だけで信用せず、Format、Version、Definition、Pages、サイズ、ページ数を検証します。
保存した文書は、Webプレビュアーの「開く」またはドラッグ&ドロップで再表示できます。HTTPで受け渡す場合もファイルと同じJSONです。再印刷の証跡として保管する場合は、業務データの保持期間、暗号化、アクセス権、削除規則を定めます。
10. Web APIとデータベースRM019
DB列と帳票オブジェクト名はコードで明示的に対応付けます。SQLはプレースホルダーを使い、利用者が参照できる伝票かを取得前に確認します。日付、通貨、端数処理は帳票へ渡す前に業務規則で確定します。
機密帳票のレスポンスにはCache-Control: no-storeを設定します。内部例外、接続文字列、ファイルパスを利用者へ返さず、要求IDを安全なサーバーログへ残します。帳票定義や画像は可能なら公開ルート外へ置きます。
11. テストとリリース確認RM020
配布サンプルの確認は、各READMEの起動・ソース変更・PDF保存・終了の順で行えます。01では第3章のコードを試し、02では明細と改ページ、03では別アプリとの受け渡しを確認してください。開発用リポジトリだけにあるテストファイルを、配布ZIPに存在する前提では扱いません。
最低限、次を確認します。
- 共通印刷データの形式識別子を持ち、Versionが
1.0である - PREPDJを設定せずにページを開始するとエラーになる
- ページを閉じずに次のページを開始できない
- 日本語、改行、数値、真偽値、nullが壊れない
- 明細の先頭・最終行、改ページ前後に欠落や重複がない
- ページ途中の帳票定義切替が、完了済みページを変更しない
- 画像・角印、バーコード、動的属性がSVGとPDFへ反映される
- 保存したPREPEJを開き直すと同じページ・内容になる
- ブラウザ生成とサーバー生成のページ数・主要表示が一致する
ラッパー変更時はWASM描画エンジン全体を毎回再試験するのではなく、生成PREPEJが共通仕様と基準サンプルに一致することを中心に確認します。WASM、プレビュアー、フォント、画像処理を変更した場合は、別途描画回帰試験が必要です。
12. よくある問題RM021
| 症状 | 確認すること |
|---|---|
| 帳票が空になる | PREPDJ上のオブジェクト名とsetValueの名前、indexを確認します。 |
| 2ページ目がない | ページごとにpage start/page endが対になっているか確認します。 |
| 画像が×になる | URLの到達性、data URI、CORS、サーバー側許可リストを確認します。 |
| 日本語が欠ける | 配置したフォント、フォントマップ、WASMとWeb資源の版を確認します。 |
| WASMが読み込めない | .wasmのURL、application/wasm、キャッシュ、同一リリースのJSを確認します。 |
| PDFだけ失敗する | バーコードの値、入力サイズ、画像サイズ、タイムアウト、サーバーログの要求IDを確認します。表せない値のバーコードがあると、PDFは対象のバーコードと値を示すエラーで止まります(プレビューでは枠に赤で「バーコードエラー」と表示します)。 |
| サーバーPDFが400・413・503になる | 応答JSONのerrorを見ます。400は入力の誤り、413は大きすぎる要求、503は混雑です。第8.2節の表を参照してください。 |
13. 配置・更新・ライセンスRM022
ラッパー、プレビュアー、JavaScript/TypeScript、CSS、WASMは同じReports.web配布版から取り出してください。HTMLだけ、またはラッパーだけを別バージョンへ差し替えると、PREPEJや公開APIの互換性を判断できなくなります。
現行の製品版への切替は、購入者のメールアドレスを含む電子署名付きの reports-web.license を、実際に帳票を出力する側へ配置して行います。購入者ごとにWASMを差し替える手順ではありません。ブラウザーとサーバーでは配置先が異なるため、製品版を使い始めるに従ってください。ライセンスファイルを公開サンプルやソース管理へ混入させないでください。正式な動作環境、使用許諾、更新履歴、問い合わせ先は共通製品マニュアルを正本とします。開発ライセンスは開発用PC 1台につき1ライセンスです。