出力バッファリング
PHP言うても、PHP自体のソースコードレベルの話ばっかりしてるから、カテゴリ名が微妙だ。PHP APIとか?
PHP出力制御関数
PHPでは、出力制御関数というものが用意されています。これは、出力をそのまま出力するのでなくて、一旦バッファに格納し、コールバックで加工したり、変数に入れたりということをするものです。
<?php ob_start(); echo "Hello\n"; $hello = ob_get_contents(); ob_end_clean(); var_dump($hello); ?>
といった感じで、ob_start()から始めて、ob_end_clean()までのデータは画面に出力される代わりにバッファに格納されます。そして、ob_get_contents()で、変数に代入しています。この他の使い方として、ob_start(コールバック関数名)と、ob_end_flush()の組み合わせで、コールバック関数で加工するなどがあります。
ちょっといいかもしれない例 (PHPスクリプト篇)
よい例が思いつかないですが、知ってると非常に強力なことが、ごくまれにあります。(年に1回くらい?) 自分が最近Extensionで実装したものだと、includeとの組み合わせです。
PHPリファレンス includeの16.10にもincludeしたファイルのバッファリングの例がありますが、includeは、戻り値を返してくれるのを利用してみます。
- include先でreturnした → returnした値が返却
- include成功したけど、returnしてない → 1 が返却
- include失敗 → E_WARNINGで、false が返却
これをふまえて、失敗したら出力を破棄してしまうというコード。
本体
<?php ob_start(); $ret = include 'hoge.php'; if ($ret) { ob_end_flush(); } else { ob_end_clean(); } ?>
ほげほげ! ほげほげ! <?php // さらに読み込むときも if(! include 'moge.php') return false; return false; // 何か失敗したらreturn falseで返る ?>
E_ERRORのエラーが起きてしまうと、通常と同じく実行が終了してしまいますが、PHPコードの割合が低いものをincludeする場合には、便利なことがあるかもしれません。
しかし、if(! include 'moge.php') て動くけど気持ち悪い。。
Cレベルでの実装
と、前置き長くなったですが、この仕組みをPHPソースレベルで利用するのは、意外と簡単です。
使ってる例として、highlight_file()があります。この関数は、第2引数で、trueが指定されてると、戻り値に返却、falseでは、標準出力に出力します。
ext/standard/basic_functions.c
/* {{{ proto bool highlight_file(string file_name [, bool return] ) Syntax highlight a source file */ PHP_FUNCTION(highlight_file) { zval *filename; zend_syntax_highlighter_ini syntax_highlighter_ini; zend_bool i = 0; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|b", &filename, &i) == FAILURE) { RETURN_FALSE; } convert_to_string(filename); if (PG(safe_mode) && (!php_checkuid(Z_STRVAL_P(filename), NULL, CHECKUID_ALLOW_ONLY_FILE))) { RETURN_FALSE; } if (php_check_open_basedir(Z_STRVAL_P(filename) TSRMLS_CC)) { RETURN_FALSE; } if (i) { php_start_ob_buffer (NULL, 0, 1 TSRMLS_CC); } php_get_highlight_struct(&syntax_highlighter_ini); if (highlight_file(Z_STRVAL_P(filename), &syntax_highlighter_ini TSRMLS_CC) == FAILURE) { RETURN_FALSE; } if (i) { php_ob_get_buffer (return_value TSRMLS_CC); php_end_ob_buffer (0, 0 TSRMLS_CC); } else { RETURN_TRUE; } } /* }}} */
syntax hilightをする機能本体は別の関数に分離されているので、すっきりしてます。この中で、第2引数は zend_bool i にセットされます。(C言語なので、bool型がなく、そのためzend_bool) そして、 i が真のとき、php_start_ob_buffer (NULL, 0, 1 TSRMLS_CC)が呼ばれています。これが、PHPレベルでのob_start()にあたる関数です。
関数定義はこんなのです。(main/php_output.h)
int php_start_ob_buffer(zval *output_handler, // コールバック関数 uint chunk_size, // chunk_size byteを超える度に、 最初の改行の際にコールバック関数をコール zend_bool erase // 0のとき、スクリプト終了までバッファは削除されません TSRMLS_DC); void php_end_ob_buffer(zend_bool send_buffer, // バッファを出力するか破棄するか zend_bool just_flush // バッファを継続するかどうか TSRMLS_DC);
上のコードで、 i が真のときの挙動は、
php_start_ob_buffer (NULL, 0, 1 TSRMLS_CC); php_get_highlight_struct(&syntax_highlighter_ini); if (highlight_file(Z_STRVAL_P(filename), &syntax_highlighter_ini TSRMLS_CC) == FAILURE) { RETURN_FALSE; } php_ob_get_buffer (return_value TSRMLS_CC); php_end_ob_buffer (0, 0 TSRMLS_CC);
php_end_ob_bufferのPHP関数との対応
php_end_ob_bufferは、中身を出力するか破棄するか、と、バッファを継続するか終わるかを引数で指定することができ、ざっくり以下のPHP関数と対応しています。
ob_flush() → php_end_ob_buffer(1, 1 TSRMLS_CC); ob_clean() → php_end_ob_buffer(0, 1 TSRMLS_CC); ob_end_flush() → php_end_ob_buffer(1, 0 TSRMLS_CC); ob_end_clean() → php_end_ob_buffer(0, 0 TSRMLS_CC); ob_get_flush() → php_ob_get_buffer(return_value TSRMLS_CC) してから php_end_ob_buffer(1, 0 TSRMLS_CC); ob_get_clean() → php_ob_get_buffer(return_value TSRMLS_CC) してから php_end_ob_buffer(0, 0 TSRMLS_CC); ob_get_contents() → php_ob_get_buffer(return_value TSRMLS_CC)