streamWrapperが〜みたいな記事をzennに書いたんですけども。 zenn.dev
記事中でも「多分こんな感じで動いてるけど、実装を見てないからわからないよ」と書いているのが、stream_readとファイル読み込みサイズの関係。
動かす
準備
例えば、「いつも決まった文字列(PHPスクリプトとして解釈可能)を返す」というstreamWrapperを用意する。
返すのは "<?php echo time() . PHP_EOL; ?>\n";
とし、これは読み取り文字数*1を無視して、いつも返すようにする。
<?php class InvalidStreamWrapper { private $content = "<?php echo time() . PHP_EOL; ?>\n"; public function stream_read($count) { return $this->content;; } }
毎回固定文字列を返すと、ファイル終端のハンドリングに失敗して無限ループが発生するので、「3回stream_read()を読んだら空データを返す」ようにする
<?php class InvalidStreamWrapper { private $counter = 0; public function stream_read($count) { if ($this->counter > 2) { return ''; } $this->counter++; return $this->content;; }
その他、動作に最低限必要な stream_open()
、 stream_eof()
、stream_set_option()
をダミーで定義して、 return true;
させておく。
また、stream_stat()
も一旦 return true;
で済ませる。
<?php class InvalidStreamWrapper { public function stream_stat() { return true; } public function stream_open($path, $mode, $options, &$opened_path): bool { return true; } public function stream_eof(): bool { return true; } public function stream_set_option($option, $arg1, $arg2) { return true; } }
これを利用するための実行部分は以下
<?php stream_wrapper_unregister('file'); stream_wrapper_register('file', InvalidStreamWrapper::class); echo '========file_get_contents' . PHP_EOL; echo file_get_contents('non-exists-file'); echo '========require' . PHP_EOL; require 'non-exists-file';
sizeの指定なしで動かす
で、実行するとこうなる
========file_get_contents <?php echo time() . PHP_EOL; ?> <?php echo time() . PHP_EOL; ?> <?php echo time() . PHP_EOL; ?> ========require 1695457515 1695457515 1695457515
sizeの指定がない限り、file_get_content()
もrequire
も同様に「終端が来るまでファイルを読み込む」ように見える。
また、stream_eof()
の結果も変わらない。
sizeの指定をして動かす
stream_stat()
がsize情報を返すように改変する。
挙動をわかりやすくするために、ついでに実行部分もいじる。
<?php echo 'fstat.size = ' . (fstat(fopen('non-exists-file', 'r'))['size']) . PHP_EOL; class InvalidStreamWrapper { private $size = 32; public function stream_stat() { return ['size' => $this->size]; } }
まずはsize=32で。これは、 strlen(IngalidStreamWrapper::$content)
と一致する。
実行結果
fstat.size = 32 ========file_get_contents <?php echo time() . PHP_EOL; ?> <?php echo time() . PHP_EOL; ?> <?php echo time() . PHP_EOL; ?> ========require 1695458370
「requireだと1回しか$contentが出力されていない」という形に。
sizeを増やしてみる
<?php private $size = 32 * 2;
すると、次の結果に
fstat.size = 64 ========file_get_contents <?php echo time() . PHP_EOL; ?> <?php echo time() . PHP_EOL; ?> <?php echo time() . PHP_EOL; ?> ========require 1695458351 1695458351
sizeが0の場合は、予想していた挙動と変わった。これは無指定時と同じになる
<?php private $size = 32 * 0;
fstat.size = 0 ========file_get_contents <?php echo time() . PHP_EOL; ?> <?php echo time() . PHP_EOL; ?> <?php echo time() . PHP_EOL; ?> ========require 1695458321 1695458321 1695458321
では、contenの長さと一致しないsizeにしてみるとどうなるか。
例えばsize=4の場合、PHPスクリプトファイルとして読み取られて評価されたが、開始タグがない(壊れている)ので、テキストファイルを読み込まれたのと同じ状態。
<?php private $size = 4;
fstat.size = 4 ========file_get_contents <?php echo time() . PHP_EOL; ?> <?php echo time() . PHP_EOL; ?> <?php echo time() . PHP_EOL; ?> ========require <?ph
PHPスクリプトとして中途半端な文字数にすると、構文エラーとなる
fstat.size = 25 ========file_get_contents <?php echo time() . PHP_EOL; ?> <?php echo time() . PHP_EOL; ?> <?php echo time() . PHP_EOL; ?> ========require Parse error: syntax error, unexpected end of file, expecting "," or ";" in non-exists-file on line 1 Process exited with code 255.
受信できるサイズより大きい場合はどうなるだろうか?
これは問題ないっぽい。ストリームのブロックサイズやstream_eof()
の内容とも関係してくるのかな、というのも気になる。
fstat.size = 320 ========file_get_contents <?php echo time() . PHP_EOL; ?> <?php echo time() . PHP_EOL; ?> <?php echo time() . PHP_EOL; ?> ========require 1695458524 1695458524 1695458524
ちょっとだけphp-srcを読んで見る・・・
- stream_statを呼び出しているのがここ
- https://github.com/php/php-src/blob/d68073c23b7e5bae66adc893beb701d021ea6e74/main/streams/userspace.c#L842
- ↑ https://github.com/php/php-src/blob/26d6bb362753bf63204b3fd21ddb54fdf0e1f52d/main/streams/streams.c#L865
- ↑ https://github.com/php/php-src/blob/0311e60eb431c1e258a1507dfdd821d83b702725/main/main.c#L1540
- ↑ https://github.com/php/php-src/blob/2f4973fd88fcde9a56e4abfdcc7af13c4fb007ff/Zend/zend_stream.c#L60
- ↑ https://github.com/php/php-src/blob/2f4973fd88fcde9a56e4abfdcc7af13c4fb007ff/Zend/zend_stream.c#L156
- registerされたstream wrapperを取得している部分がここ
- stram_stat由来のsize情報を使ってゴニョってるのがここっぽい?
php_stream_read_to_str
とか _php_stream_read
の辺りを読んでいけば良いのかなーって思った。
お腹が空いたのでここまで、また気が向いたらやろうかなー。未定。
*1:stream_read()に渡されるデータサイズ。$count。