stream_filter_appendとは?
stream_filter_append() は、既存のストリームリソースにフィルタを追加(末尾に連結)する関数です。
PHPのストリームフィルタとは、ストリームを流れるデータをリアルタイムに変換・加工する仕組みです。stream_filter_append() を使うと、ファイルの読み書き・ネットワーク通信などのストリームに対して、文字コード変換・大文字小文字変換・圧縮・Base64エンコード・ROT13変換などの処理をデータが流れる際に自動的に適用できます。
「フィルタをデータに適用する」のではなく、「フィルタをストリームに取り付けて、以降の読み書きに自動適用させる」という発想がポイントです。
基本構文
stream_filter_append(
resource $stream,
string $filtername,
int $read_write = PSFS_FEED_ME,
mixed $params = null
): resource|false
| 引数 | 型 | 説明 |
|---|---|---|
$stream | resource | フィルタを追加するストリームリソース |
$filtername | string | フィルタ名(string.toupper・convert.iconv.* など) |
$read_write | int | 読み取り・書き込みどちらに適用するかのフラグ |
$params | mixed | フィルタに渡す追加パラメータ |
| 戻り値 | resource|false | フィルタリソース(成功時)、失敗時は false |
$read_write フラグ
| 定数 | 値 | 説明 |
|---|---|---|
STREAM_FILTER_READ | 1 | 読み取り時のみ適用 |
STREAM_FILTER_WRITE | 2 | 書き込み時のみ適用 |
STREAM_FILTER_ALL | 3 | 読み書き両方に適用 |
利用可能な主要フィルタ一覧
<?php
// 登録されているフィルタを確認
print_r(stream_get_filters());
| フィルタ名 | 説明 |
|---|---|
string.rot13 | ROT13変換 |
string.toupper | 大文字に変換 |
string.tolower | 小文字に変換 |
string.strip_tags | HTMLタグを除去 |
convert.base64-encode | Base64エンコード |
convert.base64-decode | Base64デコード |
convert.quoted-printable-encode | Quoted-Printableエンコード |
convert.iconv.* | 文字コード変換(convert.iconv.UTF-8.SJIS など) |
zlib.deflate | zlib圧縮 |
zlib.inflate | zlib伸長 |
bzip2.compress | bzip2圧縮 |
bzip2.decompress | bzip2伸長 |
基本的な使い方
<?php
// 書き込み時に自動で大文字変換するフィルタを追加
$stream = fopen('/tmp/upper_output.txt', 'w');
stream_filter_append($stream, 'string.toupper', STREAM_FILTER_WRITE);
fwrite($stream, "hello, php stream filter!\n");
fclose($stream);
echo file_get_contents('/tmp/upper_output.txt');
// HELLO, PHP STREAM FILTER!
<?php
// 読み取り時にROT13変換するフィルタを追加
file_put_contents('/tmp/rot13_src.txt', "Hello World\n");
$stream = fopen('/tmp/rot13_src.txt', 'r');
stream_filter_append($stream, 'string.rot13', STREAM_FILTER_READ);
echo fread($stream, 1024); // Uryyb Jbeyq
fclose($stream);
実践クラスサンプル
サンプル1:文字コード変換ライタークラス
UTF-8で書き込んだ内容を自動でShift_JISに変換してファイルに保存します。
<?php
class EncodingStreamWriter
{
private resource $stream;
private string $filterName;
public function __construct(string $path, string $fromEncoding, string $toEncoding)
{
$this->stream = fopen($path, 'wb');
$this->filterName = "convert.iconv.{$fromEncoding}.{$toEncoding}";
if (stream_filter_append($this->stream, $this->filterName, STREAM_FILTER_WRITE) === false) {
throw new RuntimeException("フィルタの追加に失敗しました: {$this->filterName}");
}
}
public function write(string $data): int
{
$result = fwrite($this->stream, $data);
return $result !== false ? $result : 0;
}
public function writeLine(string $line): int
{
return $this->write($line . PHP_EOL);
}
public function close(): void
{
fclose($this->stream);
}
}
// 使用例:UTF-8 → Shift_JIS 変換して保存
$writer = new EncodingStreamWriter(
path: '/tmp/output_sjis.txt',
fromEncoding: 'UTF-8',
toEncoding: 'SJIS'
);
$writer->writeLine('PHPストリームフィルタのテスト');
$writer->writeLine('文字コード変換サンプル');
$writer->writeLine('日本語テキスト');
$writer->close();
// ファイルサイズで変換を確認
$utf8Bytes = strlen('PHPストリームフィルタのテスト' . PHP_EOL
. '文字コード変換サンプル' . PHP_EOL
. '日本語テキスト' . PHP_EOL);
echo "UTF-8 バイト数(概算): {$utf8Bytes}\n";
echo "保存ファイルサイズ: " . filesize('/tmp/output_sjis.txt') . " バイト\n";
サンプル2:Base64エンコード・デコードストリームクラス
バイナリファイルをBase64に変換して保存し、読み取り時に自動デコードします。
<?php
class Base64StreamHandler
{
/**
* バイナリデータをBase64エンコードしてファイルに書き込む
*/
public static function encode(string $srcPath, string $dstPath): int
{
$src = fopen($srcPath, 'rb');
$dst = fopen($dstPath, 'wb');
// 書き込み時にBase64エンコードフィルタを追加
stream_filter_append($dst, 'convert.base64-encode', STREAM_FILTER_WRITE);
$bytes = stream_copy_to_stream($src, $dst);
fclose($src);
fclose($dst);
echo "エンコード完了: {$dstPath} ({$bytes} バイト入力)\n";
return $bytes !== false ? $bytes : 0;
}
/**
* Base64エンコードされたファイルを読み取り時に自動デコードする
*/
public static function decode(string $srcPath, string $dstPath): int
{
$src = fopen($srcPath, 'rb');
$dst = fopen($dstPath, 'wb');
// 読み取り時にBase64デコードフィルタを追加
stream_filter_append($src, 'convert.base64-decode', STREAM_FILTER_READ);
$bytes = stream_copy_to_stream($src, $dst);
fclose($src);
fclose($dst);
echo "デコード完了: {$dstPath} ({$bytes} バイト出力)\n";
return $bytes !== false ? $bytes : 0;
}
/**
* 文字列をBase64ストリームとして読み取る
*/
public static function decodeString(string $base64): string
{
$stream = fopen('php://memory', 'r+b');
fwrite($stream, $base64);
rewind($stream);
stream_filter_append($stream, 'convert.base64-decode', STREAM_FILTER_READ);
return stream_get_contents($stream);
}
}
// 使用例
$original = "PHPストリームフィルタ Base64テスト\nバイナリ風データ: " . implode('', array_map('chr', range(0, 31)));
file_put_contents('/tmp/base64_src.bin', $original);
Base64StreamHandler::encode('/tmp/base64_src.bin', '/tmp/base64_encoded.txt');
Base64StreamHandler::decode('/tmp/base64_encoded.txt', '/tmp/base64_decoded.bin');
$decoded = file_get_contents('/tmp/base64_decoded.bin');
echo "元データと一致: " . ($decoded === $original ? 'YES' : 'NO') . "\n";
// エンコード内容を確認
$encoded = file_get_contents('/tmp/base64_encoded.txt');
echo "エンコード結果(先頭50文字): " . substr($encoded, 0, 50) . "...\n";
サンプル3:zlib圧縮ストリームクラス
書き込み時に自動圧縮、読み取り時に自動伸長します。
<?php
class ZlibStreamCompressor
{
/**
* データをzlib圧縮してファイルに保存する
*/
public static function compress(string $data, string $dstPath, int $level = 6): int
{
$dst = fopen($dstPath, 'wb');
// 圧縮レベルをパラメータで渡す
stream_filter_append($dst, 'zlib.deflate', STREAM_FILTER_WRITE, $level);
$bytes = fwrite($dst, $data);
fclose($dst);
return $bytes !== false ? $bytes : 0;
}
/**
* zlib圧縮ファイルを読み取り時に自動伸長する
*/
public static function decompress(string $srcPath): string
{
$src = fopen($srcPath, 'rb');
stream_filter_append($src, 'zlib.inflate', STREAM_FILTER_READ);
$content = stream_get_contents($src);
fclose($src);
return $content !== false ? $content : '';
}
/**
* ファイルをそのまま圧縮する(ストリーム間コピー)
*/
public static function compressFile(string $srcPath, string $dstPath): array
{
$src = fopen($srcPath, 'rb');
$dst = fopen($dstPath, 'wb');
stream_filter_append($dst, 'zlib.deflate', STREAM_FILTER_WRITE, 9);
stream_copy_to_stream($src, $dst);
fclose($src);
fclose($dst);
$originalSize = filesize($srcPath);
$compressedSize = filesize($dstPath);
$ratio = $originalSize > 0
? round((1 - $compressedSize / $originalSize) * 100, 1)
: 0;
return [
'original_size' => $originalSize,
'compressed_size' => $compressedSize,
'ratio' => $ratio,
];
}
}
// 使用例
$testData = str_repeat("PHPのストリームフィルタで圧縮テスト。繰り返しデータは高圧縮率です。\n", 200);
ZlibStreamCompressor::compress($testData, '/tmp/compressed.zlib');
$decompressed = ZlibStreamCompressor::decompress('/tmp/compressed.zlib');
echo "元サイズ: " . strlen($testData) . " バイト\n";
echo "圧縮後サイズ: " . filesize('/tmp/compressed.zlib') . " バイト\n";
echo "復元一致: " . ($decompressed === $testData ? 'YES' : 'NO') . "\n";
// ファイル圧縮
file_put_contents('/tmp/compress_src.txt', $testData);
$stats = ZlibStreamCompressor::compressFile('/tmp/compress_src.txt', '/tmp/compress_dst.zlib');
echo "\n圧縮統計:\n";
echo " 元サイズ: {$stats['original_size']} バイト\n";
echo " 圧縮後: {$stats['compressed_size']} バイト\n";
echo " 圧縮率: {$stats['ratio']}%\n";
サンプル4:複数フィルタをチェーンして適用するクラス
stream_filter_append() を複数回呼び出してフィルタを連鎖させます。
<?php
class FilterChainBuilder
{
private resource $stream;
/** @var resource[] */
private array $filters = [];
public function __construct(resource $stream)
{
$this->stream = $stream;
}
public function append(string $filtername, int $readWrite = STREAM_FILTER_WRITE, mixed $params = null): static
{
$filter = stream_filter_append($this->stream, $filtername, $readWrite, $params);
if ($filter === false) {
throw new RuntimeException("フィルタの追加に失敗しました: {$filtername}");
}
$this->filters[$filtername] = $filter;
return $this;
}
public function removeFilter(string $filtername): bool
{
if (!isset($this->filters[$filtername])) {
return false;
}
$result = stream_filter_remove($this->filters[$filtername]);
unset($this->filters[$filtername]);
return $result;
}
public function getStream(): resource
{
return $this->stream;
}
public function getAppliedFilters(): array
{
return array_keys($this->filters);
}
}
// 使用例:大文字変換 → ROT13 をチェーン
$stream = fopen('/tmp/chain_output.txt', 'w');
$builder = (new FilterChainBuilder($stream))
->append('string.toupper', STREAM_FILTER_WRITE) // 先に大文字化
->append('string.rot13', STREAM_FILTER_WRITE); // さらにROT13
echo "適用フィルタ: " . implode(' → ', $builder->getAppliedFilters()) . "\n";
fwrite($stream, "hello php stream filter chain\n");
fclose($stream);
$result = file_get_contents('/tmp/chain_output.txt');
echo "出力結果: {$result}";
// HELLO PHP STREAM FILTER CHAIN → ROT13 → URYYB CUC FGERNZ SVYGRE PUNVA
// 確認:手動で同じ変換
echo "期待値: " . str_rot13(strtoupper("hello php stream filter chain\n"));
サンプル5:HTMLタグ除去フィルタでテキスト抽出するクラス
string.strip_tags フィルタでHTMLファイルからテキストだけを取り出します。
<?php
class HtmlStripStreamReader
{
private string $filePath;
public function __construct(string $filePath)
{
$this->filePath = $filePath;
}
/**
* HTMLファイルのタグを除去してテキストだけを返す
*/
public function extractText(): string
{
$stream = fopen($this->filePath, 'rb');
stream_filter_append($stream, 'string.strip_tags', STREAM_FILTER_READ);
$text = stream_get_contents($stream);
fclose($stream);
// 連続する空白・改行を整理
$text = preg_replace('/\s+/', ' ', $text ?? '');
return trim($text);
}
/**
* タグを除去しつつ別ファイルに書き出す
*/
public function saveAsText(string $dstPath): int
{
$src = fopen($this->filePath, 'rb');
$dst = fopen($dstPath, 'wb');
stream_filter_append($src, 'string.strip_tags', STREAM_FILTER_READ);
$bytes = stream_copy_to_stream($src, $dst);
fclose($src);
fclose($dst);
return $bytes !== false ? $bytes : 0;
}
}
// 使用例
$html = <<<HTML
<!DOCTYPE html>
<html>
<head><title>PHPストリームフィルタ</title></head>
<body>
<h1>stream_filter_appendの解説</h1>
<p>PHPのストリームに<strong>フィルタ</strong>を追加して、データを<em>自動変換</em>できます。</p>
<ul>
<li>文字コード変換</li>
<li>圧縮・伸長</li>
<li>Base64エンコード</li>
</ul>
</body>
</html>
HTML;
file_put_contents('/tmp/sample.html', $html);
$reader = new HtmlStripStreamReader('/tmp/sample.html');
$text = $reader->extractText();
echo "抽出テキスト:\n{$text}\n";
$bytes = $reader->saveAsText('/tmp/sample_text.txt');
echo "\nテキストファイル保存: {$bytes} バイト\n";
サンプル6:カスタムストリームフィルタを登録・利用するクラス
php_user_filter を継承して独自のフィルタを実装・登録します。
<?php
/**
* 各行の先頭にプレフィックスを付加するカスタムフィルタ
*/
class LinePrefixFilter extends php_user_filter
{
private string $prefix = '';
private string $buffer = '';
public function onCreate(): bool
{
$this->prefix = is_string($this->params) ? $this->params : '[LOG] ';
$this->buffer = '';
return true;
}
public function filter($in, $out, &$consumed, bool $closing): int
{
while ($bucket = stream_bucket_make_writeable($in)) {
$this->buffer .= $bucket->data;
$consumed += $bucket->datalen;
}
// バッファ内の改行でセグメント分割してプレフィックスを付与
$lines = explode("\n", $this->buffer);
$output = '';
// 最後の要素は改行なし(未完成行)なのでバッファに戻す
$this->buffer = array_pop($lines);
foreach ($lines as $line) {
$output .= $this->prefix . $line . "\n";
}
if ($closing && $this->buffer !== '') {
$output .= $this->prefix . $this->buffer;
$this->buffer = '';
}
if ($output !== '') {
$bucket = stream_bucket_new($this->stream, $output);
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
// カスタムフィルタを登録
stream_filter_register('line.prefix', LinePrefixFilter::class);
// 使用例:書き込み時に各行へプレフィックスを付加
$stream = fopen('/tmp/prefixed_log.txt', 'w');
stream_filter_append($stream, 'line.prefix', STREAM_FILTER_WRITE, '[INFO] ');
fwrite($stream, "アプリケーション起動\n");
fwrite($stream, "データベース接続完了\n");
fwrite($stream, "リクエスト受信\n");
fclose($stream);
echo file_get_contents('/tmp/prefixed_log.txt');
サンプル7:読み書き両方にフィルタを適用するログ暗号化クラス
書き込み時にROT13エンコード、読み取り時にROT13デコードを自動適用します(擬似的な可逆変換の例)。
<?php
class Rot13LogStorage
{
private string $filePath;
public function __construct(string $filePath)
{
$this->filePath = $filePath;
}
/**
* データをROT13変換して書き込む
*/
public function write(string $data): void
{
$stream = fopen($this->filePath, 'ab');
stream_filter_append($stream, 'string.rot13', STREAM_FILTER_WRITE);
fwrite($stream, $data . "\n");
fclose($stream);
}
/**
* ROT13デコードして読み取る
*/
public function read(): string
{
if (!file_exists($this->filePath)) {
return '';
}
$stream = fopen($this->filePath, 'rb');
stream_filter_append($stream, 'string.rot13', STREAM_FILTER_READ);
$content = stream_get_contents($stream);
fclose($stream);
return $content !== false ? $content : '';
}
/**
* ファイルの生データ(エンコードされたまま)を返す
*/
public function readRaw(): string
{
return file_exists($this->filePath)
? file_get_contents($this->filePath)
: '';
}
public function clear(): void
{
if (file_exists($this->filePath)) {
unlink($this->filePath);
}
}
}
// 使用例
$storage = new Rot13LogStorage('/tmp/rot13_log.txt');
$storage->clear();
$storage->write('ユーザーログイン: user@example.com');
$storage->write('商品購入: product_id=42, amount=9800');
$storage->write('ログアウト: 2026-01-15 10:30:00');
echo "=== 生データ(エンコード済み)===\n";
echo $storage->readRaw();
echo "\n=== デコード後 ===\n";
echo $storage->read();
stream_filter_append と stream_filter_prepend の違い
| 関数 | フィルタの挿入位置 | 用途 |
|---|---|---|
stream_filter_append() | フィルタチェーンの末尾に追加 | 後から追加する処理、通常はこちらを使う |
stream_filter_prepend() | フィルタチェーンの先頭に追加 | 既存フィルタよりも前に処理させたい場合 |
フィルタは追加した順にデータが流れます。append で大文字変換 → ROT13 の順に追加すると、データは「大文字化されてからROT13変換」されます。
関連関数との比較
| 関数 | 用途 |
|---|---|
stream_filter_append() | フィルタをチェーン末尾に追加 |
stream_filter_prepend() | フィルタをチェーン先頭に追加 |
stream_filter_remove() | 追加したフィルタを削除 |
stream_filter_register() | カスタムフィルタを登録 |
stream_get_filters() | 利用可能なフィルタ名一覧を取得 |
stream_context_create() | ストリームのコンテキスト(通信設定)を作成 |
まとめ
| 項目 | 内容 |
|---|---|
| 関数名 | stream_filter_append() |
| 分類 | ストリームフィルタ関数 |
| PHP バージョン | PHP 4.3.0以上 |
| 戻り値 | フィルタリソース(成功時)、false(失敗時) |
| 主な用途 | ストリームへのリアルタイムデータ変換フィルタの追加 |
stream_filter_append() を使うと、ファイルやネットワーク通信などのストリームに対して、データが流れるたびに自動で変換処理を挟み込めます。文字コード変換・圧縮・Base64・ROT13・HTMLタグ除去などの組み込みフィルタはそのまま使え、php_user_filter を継承すれば独自の変換ロジックも簡単に追加できます。複数のフィルタをチェーンして組み合わせることで、パイプライン的なデータ処理をシンプルに実装できる強力な関数です。
