[PHP]stream_filter_append完全解説|ストリームフィルタの追加・文字変換・圧縮・暗号化を実践サンプルで理解する

PHP

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
引数説明
$streamresourceフィルタを追加するストリームリソース
$filternamestringフィルタ名(string.toupperconvert.iconv.* など)
$read_writeint読み取り・書き込みどちらに適用するかのフラグ
$paramsmixedフィルタに渡す追加パラメータ
戻り値resource|falseフィルタリソース(成功時)、失敗時は false

$read_write フラグ

定数説明
STREAM_FILTER_READ1読み取り時のみ適用
STREAM_FILTER_WRITE2書き込み時のみ適用
STREAM_FILTER_ALL3読み書き両方に適用

利用可能な主要フィルタ一覧

<?php
// 登録されているフィルタを確認
print_r(stream_get_filters());
フィルタ名説明
string.rot13ROT13変換
string.toupper大文字に変換
string.tolower小文字に変換
string.strip_tagsHTMLタグを除去
convert.base64-encodeBase64エンコード
convert.base64-decodeBase64デコード
convert.quoted-printable-encodeQuoted-Printableエンコード
convert.iconv.*文字コード変換(convert.iconv.UTF-8.SJIS など)
zlib.deflatezlib圧縮
zlib.inflatezlib伸長
bzip2.compressbzip2圧縮
bzip2.decompressbzip2伸長

基本的な使い方

<?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 を継承すれば独自の変換ロジックも簡単に追加できます。複数のフィルタをチェーンして組み合わせることで、パイプライン的なデータ処理をシンプルに実装できる強力な関数です。

タイトルとURLをコピーしました