注意: この章で説明する関数は PHP のソースコード内で用いられるものであり、 PHP の関数ではありません。PHP のユーザ用のストリーム関数については ストリーム関数のリファレンス をご覧ください。
PHPストリームAPIは、PHP拡張モジュールにファイルおよびソケット処理 用の統一化された手段を導入するものです。 共通の操作を行なうための標準関数を有する単一のAPIを使用することに より、ストリームAPIは拡張モジュールがファイル、ソケット、URL、メモリ スクリプトが定義したオブジェクトにアクセスすることを可能にします。 ストリームは、新規ストリームを登録するために動的にロードされる モジュール(およびスクリプト!)とすることができる実行時に拡張可能な APIです。
ストリームAPIの目的は、ファイル、URL、その他のストリームにできるデータ ソースを平易な統一されたAPIにより、開発者が容易にオープンできるように することです。APIは、ほぼANSI C stdio関数と(多くの主な関数について同等 の意味を有しており、)類似しています。このため、Cプログラマは、 ストリームに慣れている印象を受けるはずです。
ストリームAPIは、いくつかの異なるレベルを処理します。 基本レベルでは、APIはストリーマブルなデータソースを表す php_streamオブジェクトを定義します。 やや高いレベルでは、APIは、URLからのデータおよびメタデータの取得を サポートするために低レベルAPIをラップしたphp_stream_wrapper オブジェクトを定義します。 追加のパラメータ context は、 ほとんどのストリーム作成関数で使用できます。 これはラッパーの stream_opener メソッドに渡され、ラッパーの挙動を微調整します。
あらゆるストリームは、一度オープンされると任意の数の filters を適用することができます。 これは、ストリームがデータを読み書きする際にそのデータを加工します。
ストリームは、ファイル処理の他の形式にキャスト(変換)でき、 大きな問題もなくサードパーティ製のライブラリと組み合わせて使用する ことができます。これにより、これらのライブラリがURLソースからデータに 直接アクセスできるようになります。 使用するシステムにfopencookie()または funopen()関数がある場合、 任意のPHPストリームをANSI stdioを使う任意のライブラリに渡すことさえ できます!
ストリームの使用方法は、ANSI stdio関数の使用と非常に似ています。 主な違いは、使用を開始するストリームを得る方法です。 多くの場合、ストリームのハンドルを得るために php_stream_open_wrapper()を使用します。 この関数の動作は、以下の例で示すようにfopenと非常によくにています。
例1 PHPホームページを表示するための簡単なストリームの例
php_stream * stream = php_stream_open_wrapper("http://www.php.net", "rb", REPORT_ERRORS, NULL); if (stream) { while(!php_stream_eof(stream)) { char buf[1024]; if (php_stream_gets(stream, buf, sizeof(buf))) { printf(buf); } else { break; } } php_stream_close(stream); }
以下の表にその他の一般的なANSI stdio関数と等価なストリーム関数を 示します。注記で除外されていない限り、関数の意味は同じです。
ANSI stdio関数 | PHP ストリーム関数 | 注意 |
---|---|---|
fopen | php_stream_open_wrapper | ストリームではパラメータが増えています |
fclose | php_stream_close | |
fgets | php_stream_gets | |
fread | php_stream_read | パラメータnmembの値を1と仮定すると、プロトタイプはread(2)により似ることになります |
fwrite | php_stream_write | パラメータnmembの値を1と仮定すると、プロトタイプはwrite(2)により似ることになります |
fseek | php_stream_seek | |
ftell | php_stream_tell | |
rewind | php_stream_rewind | |
feof | php_stream_eof | |
fgetc | php_stream_getc | |
fputc | php_stream_putc | |
fflush | php_stream_flush | |
puts | php_stream_puts | fputsではなく、putsと同じ意味 |
fstat | php_stream_stat | ストリームはより情報の多いstat構造体を有しています |
すべてのストリームは、作成されるとリソースとして登録されます。これにより、 たとえ致命的なエラーが発生したとしても適切な後処理が行われることが保障されます。 PHP のすべてのファイルシステム関数は、ストリームリソースに対して操作することができます。 つまり、あなたの作成した拡張モジュールは、 通常の PHP ファイルポインタをパラメータとして受け取って 結果をストリームで返すことができるということです。 ストリーム API により、この処理が楽にできるようになっています。
例2 ストリームをパラメータとして受け取る方法
PHP_FUNCTION(example_write_hello) { zval *zstream; php_stream *stream; if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zstream)) return; php_stream_from_zval(stream, &zstream); /* これでストリームを使うことができます。しかし、ストリームの "所有者" はこの関数ではなく、呼び出し元のスクリプトです。 つまり、この関数内でストリームを閉じてはいけないということです。 そんなことをすると PHP がクラッシュしてしまいます! */ php_stream_write(stream, "hello\n"); RETURN_TRUE(); }
例3 関数からストリームを返す方法
PHP_FUNCTION(example_open_php_home_page) { php_stream *stream; stream = php_stream_open_wrapper("http://www.php.net", "rb", REPORT_ERRORS, NULL); php_stream_to_zval(stream, return_value); /* これ以降、ストリームの "所有者" は呼び出し元スクリプトとなります。 もしここでストリームを閉じると、PHP がクラッシュしてしまいます! */ }
ストリームの後始末が自動的に行われることから、 わざわざ後始末を気にしたりしないずさんなプログラマでいても大丈夫と思われるかもしれません。 確かにそれでもうまくいくでしょうが、いくつかの理由からこれはお勧めできません。 ストリームのオープン中はシステムリソースがロックされるので、 使用済みのファイルをオープンしたままにしておくと、 他のプロセスがファイルにアクセスできなくなります。 大量のファイルを扱うスクリプトでは、 使用済みのリソースを溜め込み続けるとメモリやファイル記述子の番号がいっぱいになってしまいます。 その結果ウェブサーバがリクエストを受け付けられないようになります。 どうです? あまりいい話ではないでしょう? ストリーム API には、すっきりとしたコードが書けるような細工が組み込まれています。 ストリームを閉じるべき場所で閉じていない場合は、 ウェブサーバのエラーログに有用なデバッグ情報が出力されます。
注意: 拡張モジュールの開発中は、常にデバッグビルド版の PHP を使用するようにしましょう (configure の際に --enable-debug を指定します)。 そうすることで、メモリリークやストリームのリークに関する重要な警告を受け取れるようになります。
時には、リクエストを持続させるためにストリームをオープンし続けることが有用なこともあるでしょう。 例えばログを記録したり結果をファイルにトレースする場合などです。 このようなストリームについて、確実に後始末を行うコードを書くことはさほど難しくありません。 しかしその数行のコードがどうしても必要なのかというと、そうではないでしょう。 このような場合にコードを書く手間を省くため、ストリームに対して 「このストリームの後始末は自動処理にまかせる」という印をつけることができます。 こうすると、ストリームの後始末が自動的に行われた際に、 ストリーム API は何の警告も発しなくなります。この印をつけるには php_stream_auto_cleanup() を使用します。
これらの定数は、ストリームファクトリ関数の操作に影響を及ぼします。
注意: もし、要求されたリソースがネットワークベースであった場合、 この関数は、すべてのデータが読み込まれるまでブロックします。