はじめに
これまでの記事で、配列を使ってフォーマット出力を行う vprintf()(直接出力)と vsprintf()(文字列を返す)を解説してきました。今回紹介する vfprintf() は、この2つとよく似ていますが、出力先を任意のファイルポインタやストリームに指定できるという点が異なります。
vfprintf() は、名前の通り fprintf()(ファイルへの書式付き出力)と vprintf()(配列引数によるフォーマット)を組み合わせたような関数です。ログファイルへの直接書き込み、標準エラー出力へのメッセージ送信、あるいは複数の宛先に同じフォーマットで出力したい場合など、出力先を柔軟に選びたい場面で活躍します。本記事では基本的な使い方から実践的な活用例まで詳しく解説します。
関数概要
| 項目 | 内容 |
|---|---|
| 関数名 | vfprintf() |
| 所属拡張 | コア関数(標準で常に利用可能) |
| シグネチャ | vfprintf(resource $stream, string $format, array $values): int |
| 引数1 | $stream — 出力先のストリームリソース(fopen()などで取得) |
| 引数2 | $format — 書式指定文字列 |
| 引数3 | $values — プレースホルダーに埋め込む値の配列 |
| 戻り値 | 書き込まれたバイト数 |
| 対応バージョン | PHP 5.0.0以降 |
| 類似関数 | fprintf()(可変長引数版)、vprintf()(標準出力への直接出力) |
処理の流れ(イメージ図)
書式指定文字列と値の配列
"[%s] %s: %d件処理\n" + ["2026-08-17", "同期処理", 128]
│
▼
vfprintf($stream, $format, $values)
│
┌──────────┴──────────────────┐
│ 出力先ストリームに応じて │
│ 書き込み先が変わる │
└──────────┬──────────────────┘
▼
┌─────────────┬─────────────┬─────────────┐
│ STDOUT │ STDERR │ ファイル │
│ (標準出力) │ (標準エラー出力)│ (fopen()で開いた) │
└─────────────┴─────────────┴─────────────┘
ポイントは、vprintf() が常に標準出力に書き込むのに対し、vfprintf() は第1引数で指定した任意のストリーム(ファイル、標準エラー出力、ネットワークストリームなど)に書き込める、より汎用的な関数だという点です。
実践サンプル7選
例1:基本的な使い方(標準出力への書き込み)
<?php
class BasicVfprintfDemo
{
public function writeToStdout(string $format, array $values): int
{
// STDOUTは標準出力を表す定数
return vfprintf(STDOUT, $format, $values);
}
}
$demo = new BasicVfprintfDemo();
$demo->writeToStdout("処理結果: %s (%d件)\n", ['成功', 42]);
例2:標準エラー出力へのメッセージ送信
<?php
class ErrorStreamWriter
{
/**
* CLIツールにおいて、通常の出力(STDOUT)と
* エラーメッセージ(STDERR)を明確に分離する
*/
public function reportError(string $message, array $context): void
{
vfprintf(STDERR, "[ERROR] {$message}\n", $context);
}
}
$writer = new ErrorStreamWriter();
$writer->reportError('ファイル %s の読み込みに失敗しました(コード: %d)', ['/tmp/data.csv', 404]);
例3:ログファイルへ直接書式付きで書き込むロガー
<?php
class DirectFileLogger
{
private $handle;
public function __construct(string $logFilePath)
{
// 追記モードでファイルを開く
$this->handle = fopen($logFilePath, 'a');
}
/**
* fopen()で開いたストリームに、
* vfprintf()で直接フォーマット済みログを書き込む
*/
public function log(string $level, string $template, array $context): void
{
$prefix = sprintf('[%s] [%s] ', date('Y-m-d H:i:s'), strtoupper($level));
fwrite($this->handle, $prefix);
vfprintf($this->handle, $template . "\n", $context);
}
public function __destruct()
{
if (is_resource($this->handle)) {
fclose($this->handle);
}
}
}
$logger = new DirectFileLogger('/tmp/app_direct.log');
$logger->log('info', 'バッチ処理 %s が %d件のレコードを処理しました', ['月次集計', 350]);
例4:複数の出力先に同じフォーマットで書き込む
<?php
class MultiDestinationWriter
{
/**
* 同じ書式・同じデータを、
* 複数のストリーム(画面とファイルなど)に同時に出力する
*/
public function writeToMultiple(array $streams, string $format, array $values): void
{
foreach ($streams as $stream) {
vfprintf($stream, $format, $values);
}
}
}
$writer = new MultiDestinationWriter();
$fileHandle = fopen('/tmp/dual_output.log', 'a');
$writer->writeToMultiple(
[STDOUT, $fileHandle],
"処理完了: %s (%d件, %.2f秒)\n",
['データ移行', 1000, 3.45]
);
fclose($fileHandle);
例5:CLIバッチ処理の進捗表示クラス
<?php
class BatchProgressReporter
{
/**
* バッチ処理の進捗を標準出力にリアルタイムで書き込む
* (CLIツールでの利用を想定)
*/
public function reportProgress(int $current, int $total, string $taskName): void
{
$percentage = $total > 0 ? ($current / $total) * 100 : 0;
vfprintf(STDOUT, "\r[%s] %d/%d (%.1f%%)", [$taskName, $current, $total, $percentage]);
if ($current === $total) {
fwrite(STDOUT, "\n完了しました。\n");
}
}
}
$reporter = new BatchProgressReporter();
for ($i = 1; $i <= 5; $i++) {
$reporter->reportProgress($i, 5, 'データ処理中');
usleep(200000); // デモ用のウェイト
}
例6:一時的なメモリストリームへ書式付きで出力する
<?php
class InMemoryStreamFormatter
{
/**
* php://memory ストリームを使うことで、
* 実際のファイルを作らずにフォーマット結果を蓄積できる
*/
public function buildReport(array $rows, string $format): string
{
$stream = fopen('php://memory', 'r+');
foreach ($rows as $row) {
vfprintf($stream, $format, $row);
}
rewind($stream);
$content = stream_get_contents($stream);
fclose($stream);
return $content;
}
}
$formatter = new InMemoryStreamFormatter();
$report = $formatter->buildReport(
[['ノート', 300], ['ペン', 120]],
"商品名: %-10s 価格: %5d円\n"
);
echo $report;
例7:fprintfとvfprintfの使い分けを示す比較クラス
<?php
class FprintfVsVfprintfComparator
{
/**
* 同じ結果を、fprintf()(可変長引数)と
* vfprintf()(配列引数)それぞれで生成する比較
*/
public function withFprintf($stream, string $name, int $score): int
{
return fprintf($stream, "%sさんのスコアは%d点です。\n", $name, $score);
}
public function withVfprintf($stream, array $data): int
{
return vfprintf($stream, "%sさんのスコアは%d点です。\n", $data);
}
}
$comparator = new FprintfVsVfprintfComparator();
$comparator->withFprintf(STDOUT, '太郎', 85);
$comparator->withVfprintf(STDOUT, ['太郎', 85]);
// 両方とも同じ出力になる
関連関数との比較
| 関数 | 役割 | vfprintfとの違い |
|---|---|---|
vfprintf() | 配列を使って任意のストリームへ書式付き出力 | 本記事の対象。出力先をストリームとして指定できる |
fprintf() | 可変長引数を使って任意のストリームへ書式付き出力 | 値を個別の引数として渡す点が異なる |
vprintf() | 配列を使って標準出力へ直接書式付き出力 | 出力先が常に標準出力に固定されている |
printf() | 可変長引数を使って標準出力へ直接書式付き出力 | 値を個別の引数として渡し、出力先も固定されている |
vsprintf() | 配列を使ってフォーマット済み文字列を生成 | ストリームに書き込まず、文字列を戻り値として返す |
よくある落とし穴(注意点)
- 第1引数にストリームリソースを渡し忘れる
vprintf()との違いを忘れて、第1引数を書式文字列だと勘違いしてしまうミスがよくあります。vfprintf()は必ず第1引数にファイルポインタなどのストリームを指定する必要があります。 - ファイルを開いたままにしてリソースリークを起こす
fopen()で開いたストリームは、使い終わったら必ずfclose()で閉じる必要があります。例3のように、デストラクタでのクローズ処理を実装しておくと安全です。 - STDOUT/STDERR定数はCLI SAPIで特に重要 Webサーバー経由(Apache/PHP-FPMなど)で実行する場合、
STDOUTやSTDERRへの書き込みの扱いはCLI実行時とは異なる場合があります。CLIツールとWebアプリケーションでは動作を混同しないよう注意しましょう。 - 書き込みモードの指定を誤る
fopen()の第2引数(モード)を'w'(新規作成・上書き)にすべきところを'a'(追記)にしてしまう、あるいはその逆といったミスは、ログファイルの内容を意図せず消去してしまう事故につながります。用途に応じたモードを正しく選択しましょう。 - 戻り値のバイト数を文字列だと誤解する
vfprintf()の戻り値は書き込まれたバイト数(整数)であり、書き込んだ内容そのものではありません。書き込んだ文字列も併せて保持しておきたい場合は、vsprintf()で文字列を作成してからfwrite()する設計も検討しましょう。
まとめ
| 観点 | まとめ |
|---|---|
| 何をする関数か | 配列で渡した値を書式指定文字列に埋め込み、任意のストリームに書き込む |
| 主な用途 | CLIツールでの標準エラー出力、ログファイルへの直接書き込み、複数出力先への同時書き込みなど |
vprintf()との違い | 出力先を標準出力に限定せず、任意のストリームを指定できる |
fprintf()との違い | 値を可変長引数ではなく配列としてまとめて渡せる |
| 注意点 | ストリームリソースの指定忘れ、ファイルのクローズ忘れ、書き込みモードの誤り |
vfprintf() は、vprintf() の柔軟性の高い版として、出力先を自由に選びたい場面で力を発揮する関数です。CLIツールの開発やログ機構の実装など、標準出力以外への書式付き出力が必要な場面では、ぜひ活用を検討してみてください。
