はじめに
PHPでストリームを扱う際、「このストリームはシーク可能か?」「タイムアウトが発生していないか?」「ブロッキングモードか否か?」といった内部状態を確認したい場面があります。
stream_get_meta_data() は、ストリームリソースに関するメタ情報を連想配列で返す関数です。ファイル・ネットワーク・メモリストリームを問わず、ラッパー種別・読み取りバッファの状態・タイムアウト発生フラグなど、ストリームの「素性」を一括取得できます。
関数の基本情報
| 項目 | 内容 |
|---|---|
| 関数名 | stream_get_meta_data() |
| 対応バージョン | PHP 4.3.0 以降 |
| 返り値 | array(メタ情報の連想配列) |
| カテゴリ | ストリーム関数 |
構文
stream_get_meta_data(resource $stream): array
パラメータ
| パラメータ | 型 | 説明 |
|---|---|---|
$stream | resource | 情報を取得するストリームリソース |
返り値の構造
[
'timed_out' => bool, // タイムアウトが発生したか
'blocked' => bool, // ブロッキングI/Oモードか
'eof' => bool, // EOF(ストリーム終端)に達したか
'unread_bytes' => int, // 内部バッファに残っている未読バイト数
'stream_type' => string, // ストリームの実装種別(例: "STDIO", "tcp_socket/ssl")
'wrapper_type' => string, // ラッパー種別(例: "plainfile", "http", "PHP")
'wrapper_data' => mixed, // ラッパー固有のデータ(HTTP なら応答ヘッダー配列など)
'filters' => array, // アタッチされているフィルター名の配列
'mode' => string, // fopen() に渡したモード文字列(例: "r", "w+b")
'seekable' => bool, // シーク可能か
'uri' => string, // ストリームに関連付けられたURI(ファイルパス・URL)
]
各キーの詳細
| キー | 型 | 説明 |
|---|---|---|
timed_out | bool | 直前のデータ読み取りでタイムアウトが発生した場合 true。stream_set_timeout() と組み合わせて使う |
blocked | bool | ブロッキングモードなら true、ノンブロッキングなら false |
eof | bool | EOF に達していれば true。feof() の代替として使える |
unread_bytes | int | PHP 内部の読み取りバッファに残っているバイト数(正確な値でない場合あり) |
stream_type | string | ストリームの実装種別(STDIO・tcp_socket・tcp_socket/ssl など) |
wrapper_type | string | ラッパー種別(plainfile・http・ftp・PHP など) |
wrapper_data | mixed | ラッパー依存。HTTP ラッパーなら受信ヘッダーの配列。ファイルなら null |
filters | array | アタッチ済みフィルター名の配列(フィルターなしなら空配列) |
mode | string | ストリームオープン時のモード(r・w・r+b など) |
seekable | bool | fseek() が使えるなら true(ネットワークストリームは通常 false) |
uri | string | ストリームに紐付いたURI(ファイルパスやURL) |
基本的な使い方
<?php
// ファイルストリームのメタ情報
$fp = fopen('/tmp/sample.txt', 'r+');
$meta = stream_get_meta_data($fp);
echo "URI : " . $meta['uri'] . PHP_EOL;
echo "モード : " . $meta['mode'] . PHP_EOL;
echo "シーク可能 : " . ($meta['seekable'] ? 'YES' : 'NO') . PHP_EOL;
echo "EOF : " . ($meta['eof'] ? 'YES' : 'NO') . PHP_EOL;
echo "ラッパー : " . $meta['wrapper_type'] . PHP_EOL;
echo "ストリーム : " . $meta['stream_type'] . PHP_EOL;
fclose($fp);
// php://memory のメタ情報
$mem = fopen('php://memory', 'w+');
$meta = stream_get_meta_data($mem);
var_dump($meta['seekable']); // bool(true)
var_dump($meta['wrapper_type']); // string(3) "PHP"
fclose($mem);
実践的なクラスベースの活用例
例1:ストリーム情報インスペクタークラス(StreamInspector)
任意のストリームのメタ情報を構造化して取得し、人間が読みやすい形式でレポートするインスペクタークラスです。
<?php
class StreamInspector
{
private array $meta;
public function __construct(private $stream)
{
$this->meta = stream_get_meta_data($stream);
}
public function isSeekable(): bool { return $this->meta['seekable']; }
public function isBlocked(): bool { return $this->meta['blocked']; }
public function isEof(): bool { return $this->meta['eof']; }
public function isTimedOut(): bool { return $this->meta['timed_out']; }
public function getMode(): string { return $this->meta['mode']; }
public function getUri(): string { return $this->meta['uri'] ?? ''; }
public function getWrapperType(): string { return $this->meta['wrapper_type']; }
public function getStreamType(): string { return $this->meta['stream_type']; }
public function getUnreadBytes(): int { return $this->meta['unread_bytes']; }
/** アタッチ済みフィルターの一覧 */
public function getFilters(): array { return $this->meta['filters'] ?? []; }
/** 書き込み可能モードか判定 */
public function isWritable(): bool
{
return str_contains($this->meta['mode'], 'w')
|| str_contains($this->meta['mode'], 'a')
|| str_contains($this->meta['mode'], '+');
}
/** 読み取り可能モードか判定 */
public function isReadable(): bool
{
return !str_starts_with($this->meta['mode'], 'w')
|| str_contains($this->meta['mode'], '+');
}
/** メタ情報を再取得する(タイムアウト確認など動的チェック用) */
public function refresh(): void
{
$this->meta = stream_get_meta_data($this->stream);
}
public function report(): void
{
echo "=== Stream Inspector ===" . PHP_EOL;
echo sprintf(" URI : %s\n", $this->getUri());
echo sprintf(" モード : %s\n", $this->getMode());
echo sprintf(" ラッパー : %s\n", $this->getWrapperType());
echo sprintf(" ストリーム型 : %s\n", $this->getStreamType());
echo sprintf(" シーク可能 : %s\n", $this->isSeekable() ? 'YES' : 'NO');
echo sprintf(" 書き込み可能 : %s\n", $this->isWritable() ? 'YES' : 'NO');
echo sprintf(" 読み取り可能 : %s\n", $this->isReadable() ? 'YES' : 'NO');
echo sprintf(" ブロッキング : %s\n", $this->isBlocked() ? 'YES' : 'NO');
echo sprintf(" EOF : %s\n", $this->isEof() ? 'YES' : 'NO');
echo sprintf(" 未読バイト数 : %d\n", $this->getUnreadBytes());
if (!empty($this->getFilters())) {
echo sprintf(" フィルター : %s\n", implode(', ', $this->getFilters()));
}
}
}
// 使用例
$fp = fopen('php://memory', 'w+');
$inspector = new StreamInspector($fp);
stream_filter_append($fp, 'string.toupper', STREAM_FILTER_WRITE);
$inspector->refresh();
$inspector->report();
fclose($fp);
// 出力例:
// === Stream Inspector ===
// URI : php://memory
// モード : w+
// ラッパー : PHP
// ストリーム型 : PHP
// シーク可能 : YES
// 書き込み可能 : YES
// 読み取り可能 : YES
// ブロッキング : YES
// EOF : NO
// 未読バイト数 : 0
// フィルター : string.toupper
例2:ネットワークストリームタイムアウト監視クラス(TimeoutAwareReader)
stream_set_timeout() でタイムアウトを設定し、読み取りのたびに stream_get_meta_data() の timed_out フラグを確認して、タイムアウト発生時にリトライや例外をハンドリングするクラスです。
<?php
class StreamTimeoutException extends \RuntimeException {}
class TimeoutAwareReader
{
private int $timeoutSec;
private int $timeoutUsec;
private int $maxRetries;
public function __construct(
private $stream,
int $timeoutSec = 5,
int $timeoutUsec = 0,
int $maxRetries = 3
) {
$this->timeoutSec = $timeoutSec;
$this->timeoutUsec = $timeoutUsec;
$this->maxRetries = $maxRetries;
stream_set_timeout($this->stream, $timeoutSec, $timeoutUsec);
}
/**
* 指定デリミタまでデータを読み取る(タイムアウトをリトライ付きで処理)
*
* @throws StreamTimeoutException リトライ上限を超えた場合
*/
public function readLine(int $maxLength = 4096, string $delimiter = "\n"): string|false
{
$retries = 0;
while ($retries <= $this->maxRetries) {
$data = stream_get_line($this->stream, $maxLength, $delimiter);
$meta = stream_get_meta_data($this->stream);
if ($meta['timed_out']) {
$retries++;
$elapsed = $this->timeoutSec + $this->timeoutUsec / 1_000_000;
error_log("タイムアウト発生({$elapsed}秒)。リトライ {$retries}/{$this->maxRetries}");
if ($retries > $this->maxRetries) {
throw new StreamTimeoutException(
"ストリーム読み取りが {$this->maxRetries} 回タイムアウトしました"
);
}
continue;
}
if ($meta['eof']) {
return false;
}
return $data;
}
return false;
}
/**
* 全行をジェネレータで返す(タイムアウトを自動リトライ)
*
* @return \Generator<int, string>
*/
public function lines(): \Generator
{
while (true) {
try {
$line = $this->readLine();
if ($line === false) break;
yield $line;
} catch (StreamTimeoutException $e) {
error_log("致命的タイムアウト: " . $e->getMessage());
break;
}
}
}
/**
* タイムアウト設定を変更する
*/
public function setTimeout(int $sec, int $usec = 0): void
{
$this->timeoutSec = $sec;
$this->timeoutUsec = $usec;
stream_set_timeout($this->stream, $sec, $usec);
}
}
// 使用例(ローカルファイルでの動作確認)
$fp = fopen('php://memory', 'r+');
fwrite($fp, "line1\nline2\nline3\n");
rewind($fp);
$reader = new TimeoutAwareReader($fp, timeoutSec: 5);
foreach ($reader->lines() as $i => $line) {
echo "行{$i}: {$line}" . PHP_EOL;
}
fclose($fp);
// 出力:
// 行0: line1
// 行1: line2
// 行2: line3
例3:HTTPラッパーレスポンスパーサー(HttpWrapperMetaReader)
file_get_contents() や fopen() で HTTP ストリームを開いた後、stream_get_meta_data() の wrapper_data に格納されるレスポンスヘッダーを解析するクラスです。
<?php
class HttpWrapperMetaReader
{
private array $meta;
private array $headers = [];
private int $statusCode = 0;
private string $statusMessage = '';
public function __construct(private $stream)
{
$this->meta = stream_get_meta_data($stream);
$this->parseHeaders();
}
private function parseHeaders(): void
{
// wrapper_data には HTTP ヘッダー行の配列が入る
$wrapperData = $this->meta['wrapper_data'] ?? [];
if (!is_array($wrapperData)) return;
foreach ($wrapperData as $line) {
// ステータス行
if (preg_match('/^HTTP\/[\d.]+\s+(\d+)\s+(.*)$/', $line, $m)) {
$this->statusCode = (int)$m[1];
$this->statusMessage = trim($m[2]);
continue;
}
// ヘッダー行
if (str_contains($line, ':')) {
[$name, $value] = explode(':', $line, 2);
$this->headers[strtolower(trim($name))][] = trim($value);
}
}
}
public function getStatusCode(): int { return $this->statusCode; }
public function getStatusMessage(): string { return $this->statusMessage; }
public function isSuccess(): bool { return $this->statusCode >= 200 && $this->statusCode < 300; }
public function isRedirect(): bool { return $this->statusCode >= 300 && $this->statusCode < 400; }
/** 指定ヘッダーの最初の値を返す */
public function getHeader(string $name): ?string
{
return $this->headers[strtolower($name)][0] ?? null;
}
/** 指定ヘッダーの全値を返す */
public function getHeaders(string $name): array
{
return $this->headers[strtolower($name)] ?? [];
}
/** Content-Type から charset を抽出する */
public function getCharset(): ?string
{
$ct = $this->getHeader('content-type') ?? '';
if (preg_match('/charset=([^\s;]+)/i', $ct, $m)) {
return strtolower($m[1]);
}
return null;
}
public function isSeekable(): bool { return $this->meta['seekable']; }
public function isTimedOut(): bool { return $this->meta['timed_out']; }
public function getWrapperData(): array { return $this->meta['wrapper_data'] ?? []; }
public function dump(): void
{
echo "ステータス : {$this->statusCode} {$this->statusMessage}" . PHP_EOL;
echo "Content-Type: " . ($this->getHeader('content-type') ?? '不明') . PHP_EOL;
echo "Charset : " . ($this->getCharset() ?? '不明') . PHP_EOL;
echo "シーク可能 : " . ($this->isSeekable() ? 'YES' : 'NO') . PHP_EOL;
}
}
// 使用例(モックストリームで動作確認)
$mockMeta = [
'timed_out' => false,
'blocked' => true,
'eof' => false,
'unread_bytes' => 0,
'stream_type' => 'tcp_socket/ssl',
'wrapper_type' => 'http',
'wrapper_data' => [
'HTTP/1.1 200 OK',
'Content-Type: text/html; charset=UTF-8',
'Content-Length: 1024',
'Cache-Control: no-cache',
],
'filters' => [],
'mode' => 'r',
'seekable' => false,
'uri' => 'https://example.com/',
];
// 実際の HTTP ストリームを使う場合:
// $ctx = stream_context_create(['http' => ['timeout' => 10]]);
// $fp = fopen('https://example.com/', 'rb', false, $ctx);
// $reader = new HttpWrapperMetaReader($fp);
// モックで確認
$fp = fopen('php://memory', 'r+');
// ※ 実際の HTTP ストリームでは wrapper_data が自動的に設定される
$reader = new HttpWrapperMetaReader($fp);
echo "wrapper_type: " . stream_get_meta_data($fp)['wrapper_type'] . PHP_EOL; // PHP
fclose($fp);
例4:シーク可否対応ユニバーサルリーダー(UniversalStreamReader)
stream_get_meta_data() でシーク可否を確認し、可能なら fseek()、不可能ならバッファリングで対応するユニバーサルリーダーです。
<?php
class UniversalStreamReader
{
private bool $seekable;
private string $buffer = '';
private bool $bufferLoaded = false;
public function __construct(private $stream)
{
$meta = stream_get_meta_data($stream);
$this->seekable = $meta['seekable'];
}
/**
* ストリームの全内容を返す(シーク可否を自動判定)
*/
public function readAll(): string
{
if ($this->seekable) {
return stream_get_contents($this->stream, offset: 0);
}
// シーク不可:一度読んだらバッファに保持
if (!$this->bufferLoaded) {
$this->buffer = stream_get_contents($this->stream);
$this->bufferLoaded = true;
}
return $this->buffer;
}
/**
* オフセットを指定して読む(シーク不可ストリームはバッファ経由)
*/
public function readFrom(int $offset, int $length = -1): string
{
if ($this->seekable) {
return stream_get_contents($this->stream, length: $length, offset: $offset);
}
$all = $this->readAll();
return $length === -1
? substr($all, $offset)
: substr($all, $offset, $length);
}
/**
* 内容を行配列で返す
*
* @return string[]
*/
public function lines(): array
{
return explode("\n", rtrim($this->readAll(), "\n"));
}
/**
* キーワードを含む行を返す
*
* @return string[]
*/
public function grep(string $keyword): array
{
return array_values(array_filter(
$this->lines(),
fn($line) => str_contains($line, $keyword)
));
}
public function isSeekable(): bool { return $this->seekable; }
}
// 使用例
$fp = fopen('php://memory', 'r+');
fwrite($fp, "apple\nbanana\napricot\ncherry\navocado\n");
rewind($fp);
$reader = new UniversalStreamReader($fp);
echo "シーク可能: " . ($reader->isSeekable() ? 'YES' : 'NO') . PHP_EOL;
echo "--- 'a' を含む行 ---" . PHP_EOL;
foreach ($reader->grep('a') as $line) {
echo " {$line}" . PHP_EOL;
}
echo "--- offset=7 から12バイト ---" . PHP_EOL;
echo $reader->readFrom(7, 12) . PHP_EOL; // "banana\napri" の一部
fclose($fp);
// 出力:
// シーク可能: YES
// --- 'a' を含む行 ---
// apple
// banana
// apricot
// avocado
// --- offset=7 から12バイト ---
// banana
// apri
例5:ストリームモード検証クラス(StreamModeGuard)
stream_get_meta_data() の mode キーを解析し、ストリームへの操作(読み取り・書き込み・追記・シーク)が許可されているかを事前に検証するガードクラスです。
<?php
class StreamModeException extends \LogicException {}
class StreamModeGuard
{
private string $mode;
private bool $seekable;
public function __construct(private $stream)
{
$meta = stream_get_meta_data($stream);
$this->mode = $meta['mode'];
$this->seekable = $meta['seekable'];
}
public function canRead(): bool
{
// 'w', 'a', 'x', 'c' のみ(+なし)は読み取り不可
return str_contains($this->mode, 'r')
|| str_contains($this->mode, '+');
}
public function canWrite(): bool
{
return str_contains($this->mode, 'w')
|| str_contains($this->mode, 'a')
|| str_contains($this->mode, 'x')
|| str_contains($this->mode, 'c')
|| str_contains($this->mode, '+');
}
public function canSeek(): bool { return $this->seekable; }
public function isAppend(): bool { return str_contains($this->mode, 'a'); }
public function isBinary(): bool { return str_contains($this->mode, 'b'); }
/**
* 読み取り操作の前に呼び出してガードする
*
* @throws StreamModeException 読み取り不可の場合
*/
public function assertReadable(): void
{
if (!$this->canRead()) {
throw new StreamModeException(
"ストリーム(モード: {$this->mode})は読み取り不可です"
);
}
}
/**
* 書き込み操作の前に呼び出してガードする
*
* @throws StreamModeException 書き込み不可の場合
*/
public function assertWritable(): void
{
if (!$this->canWrite()) {
throw new StreamModeException(
"ストリーム(モード: {$this->mode})は書き込み不可です"
);
}
}
/**
* シーク操作の前に呼び出してガードする
*
* @throws StreamModeException シーク不可の場合
*/
public function assertSeekable(): void
{
if (!$this->canSeek()) {
throw new StreamModeException(
"ストリームはシーク不可です(stream_type: " .
stream_get_meta_data($this->stream)['stream_type'] . ")"
);
}
}
public function summary(): string
{
return sprintf(
'mode=%s read=%s write=%s seek=%s append=%s binary=%s',
$this->mode,
$this->canRead() ? 'YES' : 'NO',
$this->canWrite() ? 'YES' : 'NO',
$this->canSeek() ? 'YES' : 'NO',
$this->isAppend() ? 'YES' : 'NO',
$this->isBinary() ? 'YES' : 'NO',
);
}
}
// 使用例
foreach (['r', 'w', 'r+', 'a', 'w+b'] as $mode) {
$fp = fopen('php://memory', $mode);
$guard = new StreamModeGuard($fp);
echo $guard->summary() . PHP_EOL;
fclose($fp);
}
// 書き込み専用ストリームへの読み取りをガードする例
$fp = fopen('php://memory', 'w');
$guard = new StreamModeGuard($fp);
try {
$guard->assertReadable();
} catch (StreamModeException $e) {
echo "ガード発動: " . $e->getMessage() . PHP_EOL;
}
fclose($fp);
// 出力:
// mode=r read=YES write=NO seek=YES append=NO binary=NO
// mode=w read=NO write=YES seek=YES append=NO binary=NO
// mode=r+ read=YES write=YES seek=YES append=NO binary=NO
// mode=a read=NO write=YES seek=YES append=YES binary=NO
// mode=w+b read=YES write=YES seek=YES append=NO binary=YES
// ガード発動: ストリーム(モード: w)は読み取り不可です
例6:フィルター付きストリーム診断クラス(FilteredStreamDiagnostics)
アタッチされているフィルターの一覧を stream_get_meta_data() の filters キーから取得し、フィルターの適用順や数を検証する診断クラスです。
<?php
class FilteredStreamDiagnostics
{
/**
* ストリームに現在アタッチされているフィルターを返す
*
* @return string[]
*/
public function getAttachedFilters($stream): array
{
return stream_get_meta_data($stream)['filters'] ?? [];
}
/**
* 期待するフィルターが全てアタッチされているか検証する
*
* @param string[] $expected
* @return array{ok: bool, missing: string[], extra: string[]}
*/
public function verify($stream, array $expected): array
{
$attached = $this->getAttachedFilters($stream);
return [
'ok' => empty(array_diff($expected, $attached)),
'missing' => array_values(array_diff($expected, $attached)),
'extra' => array_values(array_diff($attached, $expected)),
];
}
/**
* フィルターの適用順を確認するレポートを出力する
*/
public function report($stream): void
{
$meta = stream_get_meta_data($stream);
$filters = $meta['filters'] ?? [];
echo "=== フィルター診断 ===" . PHP_EOL;
echo "URI : " . ($meta['uri'] ?? 'N/A') . PHP_EOL;
echo "アタッチ数 : " . count($filters) . PHP_EOL;
if (empty($filters)) {
echo "(フィルターなし)" . PHP_EOL;
return;
}
echo "適用順:" . PHP_EOL;
foreach ($filters as $i => $filter) {
echo sprintf(" [%d] %s\n", $i + 1, $filter);
}
}
}
// 使用例
$fp = fopen('php://memory', 'w+');
$diag = new FilteredStreamDiagnostics();
stream_filter_append($fp, 'string.toupper', STREAM_FILTER_WRITE);
stream_filter_append($fp, 'string.rot13', STREAM_FILTER_WRITE);
$diag->report($fp);
// 期待するフィルターの検証
$result = $diag->verify($fp, ['string.toupper', 'string.rot13']);
echo "検証OK: " . ($result['ok'] ? 'YES' : 'NO') . PHP_EOL;
$result2 = $diag->verify($fp, ['string.toupper', 'string.tolower']);
echo "検証OK: " . ($result2['ok'] ? 'YES' : 'NO') . PHP_EOL;
echo "不足 : " . implode(', ', $result2['missing']) . PHP_EOL;
echo "余分 : " . implode(', ', $result2['extra']) . PHP_EOL;
fclose($fp);
// 出力:
// === フィルター診断 ===
// URI : php://memory
// アタッチ数 : 2
// 適用順:
// [1] string.toupper
// [2] string.rot13
// 検証OK: YES
// 検証OK: NO
// 不足 : string.tolower
// 余分 : string.rot13
例7:ストリームヘルスチェッカー(StreamHealthChecker)
stream_get_meta_data() を定期的に呼び出してストリームの健全性(EOF・タイムアウト・ブロッキング状態)を監視し、異常を検知したら通知するヘルスチェッカークラスです。
<?php
class StreamHealthStatus
{
public function __construct(
public readonly bool $healthy,
public readonly bool $eof,
public readonly bool $timedOut,
public readonly bool $blocked,
public readonly int $unreadBytes,
public readonly string $checkedAt,
public readonly array $issues,
) {}
public function __toString(): string
{
$status = $this->healthy ? '✓ HEALTHY' : '✗ UNHEALTHY';
$parts = ["{$status} @ {$this->checkedAt}"];
foreach ($this->issues as $issue) {
$parts[] = " ⚠ {$issue}";
}
return implode(PHP_EOL, $parts);
}
}
class StreamHealthChecker
{
/** @var callable[] */
private array $onUnhealthy = [];
public function __construct(private $stream) {}
/**
* ヘルスチェックを実行して結果を返す
*/
public function check(): StreamHealthStatus
{
$meta = stream_get_meta_data($this->stream);
$issues = [];
if ($meta['eof']) {
$issues[] = 'EOF に達しています';
}
if ($meta['timed_out']) {
$issues[] = 'タイムアウトが発生しています';
}
if (!$meta['blocked']) {
$issues[] = 'ノンブロッキングモードです(意図的でなければ要確認)';
}
if ($meta['unread_bytes'] > 65536) {
$issues[] = "未読バッファが大きすぎます({$meta['unread_bytes']} バイト)";
}
$healthy = empty($issues);
$status = new StreamHealthStatus(
healthy: $healthy,
eof: $meta['eof'],
timedOut: $meta['timed_out'],
blocked: $meta['blocked'],
unreadBytes: $meta['unread_bytes'],
checkedAt: date('Y-m-d H:i:s'),
issues: $issues,
);
if (!$healthy) {
foreach ($this->onUnhealthy as $callback) {
$callback($status);
}
}
return $status;
}
/**
* 異常検知時のコールバックを登録する
*/
public function onUnhealthy(callable $callback): static
{
$this->onUnhealthy[] = $callback;
return $this;
}
/**
* $intervalMs ミリ秒ごとに $times 回チェックし、全結果を返す
*
* @return StreamHealthStatus[]
*/
public function monitor(int $times = 5, int $intervalMs = 1000): array
{
$results = [];
for ($i = 0; $i < $times; $i++) {
$results[] = $this->check();
if ($i < $times - 1) {
usleep($intervalMs * 1000);
}
}
return $results;
}
}
// 使用例
$fp = fopen('php://memory', 'r+');
fwrite($fp, "test data\n");
rewind($fp);
$checker = new StreamHealthChecker($fp);
$checker->onUnhealthy(function (StreamHealthStatus $s) {
error_log("ストリーム異常: " . implode(', ', $s->issues));
});
// 通常状態
echo $checker->check() . PHP_EOL;
// EOF 状態を意図的に作る
stream_get_contents($fp); // 全て読み切る
echo $checker->check() . PHP_EOL;
fclose($fp);
// 出力:
// ✓ HEALTHY @ 2025-05-13 10:00:00
// ✗ UNHEALTHY @ 2025-05-13 10:00:00
// ⚠ EOF に達しています
関連する関数との比較
| 関数 | 役割 |
|---|---|
stream_get_meta_data() | ストリームのメタ情報(モード・シーク可否・タイムアウト等)を取得 |
stream_set_timeout() | ストリームのタイムアウトを設定(timed_out フラグと連携) |
stream_set_blocking() | ストリームのブロッキングモードを切り替え(blocked フラグと連携) |
fstat() | ファイルストリームのファイルシステム情報(サイズ・更新日時等)を取得 |
get_resource_type() | リソースの型名(stream・curl など)を返す |
stream_get_filters() | 利用可能なフィルター名一覧を返す(filters キーとは別) |
注意点とベストプラクティス
1. timed_out は「発生したか」であり「現在中か」ではない
timed_out は直前の読み取り操作でタイムアウトが起きたことを示します。次の読み取りが成功すれば false に戻ります。定期監視する場合は毎回 stream_get_meta_data() を再呼び出しして確認しましょう。
2. wrapper_data の形式はラッパーによって異なる
HTTP ラッパーではレスポンスヘッダーの配列ですが、他のラッパーでは null や別の形式になります。is_array() で確認してから参照しましょう。
$wrapperData = stream_get_meta_data($fp)['wrapper_data'];
if (is_array($wrapperData)) {
// HTTP ヘッダーを処理
}
3. filters キーはアタッチ済みのフィルター名のみ
stream_get_filters() が利用可能な全フィルター一覧を返すのに対し、stream_get_meta_data() の filters キーはそのストリームに現在アタッチされているフィルターの配列です。
4. unread_bytes は目安値
内部バッファの未読バイト数は実装依存の概算値であり、正確なバイト数とは限りません。厳密なバイト数管理には ftell() や自前のカウンタを使いましょう。
まとめ
| キー | 主な用途 |
|---|---|
seekable | fseek() 前の可否確認 |
timed_out | ネットワーク読み取りのタイムアウト検知 |
eof | ストリーム終端の確認 |
mode | 読み書き可否の判定 |
wrapper_type | HTTP / ファイル / メモリの種別判定 |
wrapper_data | HTTP レスポンスヘッダーの取得 |
filters | アタッチ済みフィルターの確認 |
stream_type | ストリーム実装種別の把握 |
uri | ストリームに紐付いたパス・URL の確認 |
stream_get_meta_data() はストリームの「現在の状態」を知るための万能な診断関数です。タイムアウト監視・シーク可否の分岐・フィルター確認・HTTPヘッダー取得など、ストリームを堅牢に扱うあらゆる場面で活用できます。
