【PHP8.x】php_user_filter::filter()メソッドの使い方
filterメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
filterメソッドは、PHPのphp_user_filterクラスに属し、ユーザーが定義するストリームフィルターが、実際にデータをフィルタリングする処理を実行するメソッドです。このメソッドは、ファイルやネットワーク接続など、ストリームを介してデータが読み書きされる際に、PHPのストリームI/Oサブシステムによって自動的に呼び出されます。
開発者はこのメソッドをオーバーライドし、ストリームを通過するデータをリアルタイムで加工する独自のロジックを実装します。引数としては、フィルタリング対象のデータが格納された入力バッファを表すストリームリソース($in)、フィルタリング後のデータを書き込む出力バッファを表すストリームリソース($out)、処理された入力データのバイト数を記録するための参照渡し変数($consumed)、そしてストリームが閉じられようとしているかを示す真偽値($closing)を受け取ります。
メソッドは処理結果に応じて、PSFS_PASS_ON(正常に処理を終え、データを出力バッファに書き込んだ場合)、PSFS_FEED_ME(入力バッファにデータが不足しており、さらなるデータが必要な場合)、またはPSFS_ERR_FATAL(処理中に致命的なエラーが発生した場合)のいずれかの定数を返します。これにより、データの圧縮、暗号化、特定フォーマットへの変換など、様々な用途でストリームデータを柔軟に処理することが可能になります。
構文(syntax)
1<?php 2 3class MyCustomFilter extends php_user_filter 4{ 5 public function filter($in, $out, int &$consumed, bool $closing): int 6 { 7 // フィルタリング処理をここに実装します 8 // $in からデータを読み込み、処理し、$out へ書き込む 9 // 処理したバイト数を $consumed に加算する 10 // フィルタリング結果を示す定数 (例: PSFS_PASS_ON, PSFS_FEED_ME, PSFS_ERR_FATAL) を返します 11 return PSFS_PASS_ON; 12 } 13}
引数(parameters)
resource $in, resource $out, int &$consumed, bool $closing
- resource $in: 読み込むためのストリームリソース
- resource $out: 書き込むためのストリームリソース
- int &$consumed: 読み込んだバイト数を格納する参照渡し整数
- bool $closing: ストリームが閉じられる場合に true になるブール値
戻り値(return)
int
このメソッドは、ストリームフィルターの処理結果を示す整数値を返します。具体的には、成功した場合はPSFS_PASS_ON (1)、エラーが発生した場合はPSFS_ERR_FATAL (8)、または処理を中断した場合はPSFS_SPECIAL_USER_TRANSFORM (6) などの定数が返されます。
サンプルコード
PHPカスタムストリームフィルターでHTMLタグを除去する
1<?php 2 3/** 4 * カスタムストリームフィルターを定義するクラス。 5 * 入力ストリームからHTMLタグを除去します。 6 * これは、`filter_input(..., FILTER_SANITIZE_STRING)` のように、 7 * ユーザー入力から潜在的に危険なHTMLタグを取り除くサニタイズ処理を 8 * ストリームに適用する例です。 9 */ 10class MyStripTagsFilter extends php_user_filter 11{ 12 /** 13 * ストリームのフィルタリング処理を実行します。 14 * 15 * @param resource $in 入力バケットブリゲード (処理するデータを含むバケットのコレクション) 16 * @param resource $out 出力バケットブリゲード (処理後のデータを格納するバケットのコレクション) 17 * @param int &$consumed これまでに消費されたバイト数を追跡するカウンター (参照渡し) 18 * @param bool $closing ストリームが閉じられようとしているかを示すフラグ 19 * @return int フィルタリングのステータス 20 * - `PSFS_PASS_ON`: フィルタリングが成功し、すべてのデータを処理した 21 * - `PSFS_FEED_ME`: さらなる入力が必要である (通常は不要) 22 * - `PSFS_ERR_FATAL`: 致命的なエラーが発生した 23 */ 24 public function filter(resource $in, resource $out, int &$consumed, bool $closing): int 25 { 26 // 入力バケットブリゲードからすべてのバケットを繰り返し処理します。 27 while ($bucket = stream_bucket_make_writeable($in)) { 28 // キーワード 'php filter_input' に関連付けて、入力データからHTMLタグを除去するサニタイズ処理を実装します。 29 // これは `filter_input(INPUT_GET, 'param', FILTER_SANITIZE_STRING)` のような動作を模倣し、 30 // 潜在的に危険なHTMLタグ(例:<script>タグ)を取り除きます。 31 $processedData = strip_tags($bucket->data); 32 33 // 処理後のデータでバケットを更新し、その新しい長さを設定します。 34 $bucket->data = $processedData; 35 $bucket->datalen = strlen($processedData); 36 37 // 消費されたバイト数の合計を更新します。 38 $consumed += $bucket->datalen; 39 40 // 処理されたデータをアウトプットストリームに追加します。 41 stream_bucket_append($out, $bucket); 42 } 43 44 // フィルタリングが成功し、すべてのデータを処理したことを示します。 45 return PSFS_PASS_ON; 46 } 47} 48 49// --- 使用例 --- 50 51// 1. カスタムフィルターを登録し、'strip_tags_filter' という名前で利用可能にします。 52// stream_filter_register() は成功すると true を返します。 53if (!stream_filter_register("strip_tags_filter", MyStripTagsFilter::class)) { 54 error_log("エラー: ストリームフィルター 'strip_tags_filter' の登録に失敗しました。"); 55 exit(1); 56} 57 58// 2. テスト用のHTMLを含む文字列データを用意します。 59$originalString = "Hello <b>World</b>! This is a <script>alert('XSS');</script> example with <p>some</p> HTML tags."; 60echo "=== オリジナル文字列 ===\n" . $originalString . PHP_EOL . PHP_EOL; 61 62// 3. メモリ上のストリームを作成します ('php://memory' は一時的なファイルとして動作します)。 63// fopen() は成功するとリソースを返します。 64$stream = fopen('php://memory', 'r+'); 65if ($stream === false) { 66 error_log("エラー: 'php://memory' ストリームを開くことに失敗しました。"); 67 exit(1); 68} 69 70// 4. 元のデータをストリームに書き込みます。 71fwrite($stream, $originalString); 72// ストリームポインタを先頭に戻し、読み込み準備をします。 73fseek($stream, 0); 74 75// 5. ストリームにカスタムフィルターを適用します。 76// 'strip_tags_filter' を読み込みモード (STREAM_FILTER_READ) で追加します。 77// これにより、ストリームからデータを読み出す際に `MyStripTagsFilter::filter` メソッドが自動的に呼び出されます。 78// stream_filter_append() は成功するとリソースを返します。 79$filter = stream_filter_append($stream, "strip_tags_filter", STREAM_FILTER_READ); 80if ($filter === false) { 81 error_log("エラー: ストリームフィルターの追加に失敗しました。"); 82 fclose($stream); 83 exit(1); 84} 85 86// 6. フィルターされたデータをストリームから読み込みます。 87// stream_get_contents() はストリームの残りすべてを文字列として読み込みます。 88$filteredString = stream_get_contents($stream); 89 90echo "=== フィルター後の文字列 ===\n" . $filteredString . PHP_EOL; 91 92// 7. ストリームを閉じます。 93fclose($stream); 94 95?>
PHP 8 の php_user_filter::filter メソッドは、カスタムストリームフィルターの核となる処理ロジックを実装するために使われます。このメソッドは、入力ストリームからデータを受け取り、定義されたルールに基づいて加工し、出力ストリームへと渡す役割を担います。
引数 $in は、処理対象の入力データが格納されたバケットの集合です。$out は、加工後のデータが格納されるバケットの集合です。&$consumed は、これまでにフィルターで処理されたバイト数の合計を追跡するカウンターで、参照渡しのためメソッド内で値を更新できます。$closing は、ストリームが閉じられようとしているかを示すブール値のフラグです。戻り値はフィルタリングのステータスを示し、PSFS_PASS_ON はフィルタリングが成功し、すべてのデータを処理したことを意味します。
提供されたサンプルコードでは、filter_input 関数がユーザー入力からHTMLタグを除去してサニタイズするのと同様に、ストリームを通じて渡されるデータから潜在的に危険なHTMLタグを取り除くカスタムフィルターを実装しています。具体的には、入力バケットからデータを読み込み、PHPの strip_tags() 関数を使用してHTMLタグを除去します。その後、タグが除去されたデータを新しいバケットとして出力ストリームに追加することで、ストリームデータのサニタイズを自動的に行っています。このメソッドは、データが読み書きされる際に、リアルタイムで安全なデータ処理を適用するための非常に強力な仕組みです。
このサンプルコードは、filter_input のようなWeb入力の簡易なサニタイズとは異なり、ファイルやネットワークなどの「ストリーム」データそのものに独自の変換処理を適用する低レベルな方法です。データは「バケット」という単位で filter メソッドに渡されますので、各バケットを正しく処理し、変換後のデータを新しいバケットとして出力ストリームへ追加する流れを理解することが重要です。処理したバイト数は参照渡しの $consumed に正確に加算し、処理成功時には PSFS_PASS_ON を返すように注意してください。fopen で開いたストリームは、必ず fclose で閉じてリソースを解放する必要があります。この仕組みは、データ圧縮や暗号化、ログ加工など様々な用途に応用可能ですが、エラーハンドリングも丁寧に行うことをおすすめします。
PHPカスタムフィルターでfilter_var検証
1<?php 2 3/** 4 * php_user_filter を継承したカスタムストリームフィルターの例。 5 * このフィルターは、入力ストリームの各行を処理し、 6 * キーワードである filter_var を用いてデータの検証やサニタイズを行います。 7 */ 8class MyCustomFilter extends php_user_filter 9{ 10 /** 11 * ストリームデータのフィルタリングロジックを実装するメソッド。 12 * 13 * @param resource $in 入力バケットを格納するストリームリソース 14 * @param resource $out 出力バケットを格納するストリームリソース 15 * @param int &$consumed フィルタリングによって消費されたデータのバイト数 16 * @param bool $closing ストリームが閉じられようとしているかを示すフラグ 17 * @return int フィルタリング処理の結果を示す定数 (PSFS_PASS_ON, PSFS_FEED_ME, PSFS_ERR_FATAL) 18 */ 19 public function filter( 20 resource $in, 21 resource $out, 22 int &$consumed, 23 bool $closing 24 ): int { 25 while ($bucket = stream_bucket_make_writeable($in)) { 26 $originalData = $bucket->data; 27 $processedData = ''; 28 29 // 入力データを改行で分割し、各行を処理 30 $lines = explode("\n", $originalData); 31 32 foreach ($lines as $line) { 33 $trimmedLine = trim($line); 34 35 if ($trimmedLine === '') { 36 // 空行はそのまま追加(またはスキップ) 37 $processedData .= "\n"; 38 continue; 39 } 40 41 // キーワード: filter_var を使用してデータを検証・サニタイズする例 42 // 例1: 行が有効な整数であるかを検証 43 if (filter_var($trimmedLine, FILTER_VALIDATE_INT) !== false) { 44 $processedData .= "VALID_INT: " . $trimmedLine . "\n"; 45 } 46 // 例2: 行が有効なメールアドレスであるかを検証 47 elseif (filter_var($trimmedLine, FILTER_VALIDATE_EMAIL) !== false) { 48 $processedData .= "VALID_EMAIL: " . $trimmedLine . "\n"; 49 } 50 // 例3: それ以外の場合、HTMLエンティティをエスケープしてサニタイズ 51 else { 52 $sanitizedLine = filter_var($trimmedLine, FILTER_SANITIZE_FULL_SPECIAL_CHARS); 53 $processedData .= "SANITIZED: " . $sanitizedLine . "\n"; 54 } 55 } 56 57 // 処理されたデータを新しいバケットに格納し、出力ストリームに追加 58 $bucket->data = $processedData; 59 $bucket->datalen = strlen($processedData); 60 stream_bucket_append($out, $bucket); 61 62 // 消費されたバイト数を更新 63 $consumed += $bucket->datalen; 64 } 65 66 // 処理が成功し、残りのデータがあれば次に渡す 67 return PSFS_PASS_ON; 68 } 69 70 /** 71 * このカスタムフィルターの使用例を示す静的メソッド。 72 * 単体で動作可能なコードとしてデモンストレーションを行います。 73 */ 74 public static function demonstrateUsage(): void 75 { 76 // カスタムフィルターを登録 77 // 登録名 "my_custom_filter" と MyCustomFilter クラスを関連付ける 78 if (!stream_filter_register("my_custom_filter", MyCustomFilter::class)) { 79 die("カスタムフィルターの登録に失敗しました。\n"); 80 } 81 82 // テストデータ 83 $testData = <<<EOT 84Hello World 8512345 86test@example.com 87<script>alert('XSS');</script> 88Another line 8967890 90invalid-email 91EOT; 92 93 echo "--- 元のデータ ---\n"; 94 echo $testData . "\n\n"; 95 96 // メモリ上のストリームを作成 97 $fp = fopen("php://memory", "r+"); 98 if ($fp === false) { 99 die("メモリストリームのオープンに失敗しました。\n"); 100 } 101 102 // ストリームにテストデータを書き込む 103 fwrite($fp, $testData); 104 // ストリームのポインタを先頭に戻す 105 rewind($fp); 106 107 // 作成したカスタムフィルターをストリームに追加 108 // このフィルターは読み取り時に適用される 109 stream_filter_append($fp, "my_custom_filter", STREAM_FILTER_READ); 110 111 echo "--- フィルタリングされたデータ ---\n"; 112 // フィルタリングされたデータを読み出し、出力 113 while (!feof($fp)) { 114 echo fread($fp, 8192); // データを読み出すたびにフィルターが適用される 115 } 116 117 // ストリームを閉じる 118 fclose($fp); 119 } 120} 121 122// スクリプトが直接実行された場合にデモンストレーションを実行 123MyCustomFilter::demonstrateUsage();
PHPのphp_user_filterクラスのfilterメソッドは、カスタムストリームフィルターの中核をなす機能です。このメソッドを実装することで、ファイルやネットワークなどあらゆるストリームのデータをリアルタイムで加工できます。
引数$inは入力されるストリームデータを、$outは処理後のデータが出力されるストリームをそれぞれリソースとして受け取ります。&$consumedは、フィルターがどれだけのデータを処理し消費したかをバイト単位で示す整数値で、参照渡しのためメソッド内で更新されます。$closingは、ストリームが閉じられようとしている場合にtrueとなる論理値です。メソッドは、処理が成功したか、追加データが必要か、エラーが発生したかを示す整数値(例: PSFS_PASS_ON)を戻り値として返します。
サンプルコードのMyCustomFilterクラスのfilterメソッドでは、入力されたデータを一行ずつ読み込み、キーワードであるfilter_var関数を使用してデータの検証とサニタイズを行っています。具体的には、各行が有効な整数値か、有効なメールアドレスであるかをチェックし、それ以外の場合はHTMLエンティティをエスケープして安全な文字列に変換しています。処理されたデータは整形され、出力ストリーム$outに追加されます。demonstrateUsageメソッドは、stream_filter_registerでフィルターを登録し、stream_filter_appendで実際のストリームに適用することで、データが動的にフィルタリングされる様子を示しています。
php_user_filterは、ファイルの読み書きを途中で加工する高度な機能であり、ストリーム処理の深い理解が必要です。filterメソッドの$consumed引数は、処理したデータ量を正確に更新しないと、データ破損の原因となるため特に注意してください。filter_varは、ユーザー入力の検証や無害化に必須の機能で、セキュリティ対策として非常に重要です。例えばメールアドレスの形式チェックやHTMLタグのエスケープに活用し、SQLインジェクションやXSS攻撃防止に役立ちます。検証は入力形式の確認、サニタイズは危険な文字の除去や変換を行う機能であり、違いを理解して適切に使い分けることが重要です。カスタムフィルターはstream_filter_registerで登録し、stream_filter_append等でストリームに適用する一連の手順を正しく行う必要があります。