はじめに
PHPのストリームフィルターを使う際、「どんなフィルターが使えるのか確認したい」「カスタムフィルターが正しく登録されているか検証したい」「環境によって使えるフィルターが違う」といった場面があります。
stream_get_filters() は、現在のPHP環境で利用可能なストリームフィルター名を配列で一括取得する関数です。組み込みフィルターとユーザー登録フィルターの両方を返すため、動的なフィルター選択や登録確認に欠かせないユーティリティです。
関数の基本情報
| 項目 | 内容 |
|---|---|
| 関数名 | stream_get_filters() |
| 対応バージョン | PHP 5.0.0 以降 |
| 返り値 | string[](フィルター名の配列) |
| カテゴリ | ストリーム関数 |
構文
stream_get_filters(): array
パラメータ
なし。
返り値
利用可能なフィルター名の文字列配列。登録順は不定です。
基本的な使い方
<?php
$filters = stream_get_filters();
print_r($filters);
// 出力例(環境により異なる):
// Array
// (
// [0] => zlib.*
// [1] => bzip2.*
// [2] => convert.iconv.*
// [3] => mcrypt.*
// [4] => mdecrypt.*
// [5] => string.rot13
// [6] => string.toupper
// [7] => string.tolower
// [8] => string.strip_tags
// [9] => convert.*
// [10] => consumed
// [11] => dechunk
// [12] => zlib.*
// )
特定フィルターの存在確認
<?php
function filterExists(string $name): bool
{
return in_array($name, stream_get_filters(), true);
}
var_dump(filterExists('string.rot13')); // bool(true)
var_dump(filterExists('my.custom')); // bool(false)
主な組み込みフィルター一覧
| フィルター名 | 説明 |
|---|---|
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.quoted-printable-decode | Quoted-Printable デコード |
convert.iconv.* | 文字コード変換(iconv 拡張が必要) |
zlib.deflate | zlib 圧縮(zlib 拡張が必要) |
zlib.inflate | zlib 展開(zlib 拡張が必要) |
bzip2.compress | bzip2 圧縮(bz2 拡張が必要) |
bzip2.decompress | bzip2 展開(bz2 拡張が必要) |
consumed | データを消費するだけで何も出力しない |
dechunk | HTTP chunked transfer を展開 |
ワイルドカード表記について:
zlib.*やconvert.iconv.*のようにワイルドカードで返る場合があります。stream_filter_append()に渡す際は具体的な名前(zlib.deflateなど)を指定します。
実践的なクラスベースの活用例
例1:フィルターレジストリクラス(FilterRegistry)
利用可能なフィルターを管理し、カテゴリ別分類・存在確認・登録済み検証をまとめて行うレジストリクラスです。
<?php
class FilterRegistry
{
private array $available;
public function __construct()
{
$this->available = stream_get_filters();
}
/** フィルターが利用可能かどうか確認する */
public function has(string $name): bool
{
// 完全一致
if (in_array($name, $this->available, true)) {
return true;
}
// ワイルドカード(例: zlib.*)との照合
foreach ($this->available as $filter) {
if (str_ends_with($filter, '.*')) {
$prefix = rtrim($filter, '.*');
if (str_starts_with($name, $prefix . '.')) {
return true;
}
}
}
return false;
}
/** プレフィックスでフィルターを絞り込む */
public function startsWith(string $prefix): array
{
return array_values(array_filter(
$this->available,
fn($f) => str_starts_with($f, $prefix)
));
}
/** カテゴリ別に分類して返す */
public function grouped(): array
{
$groups = [];
foreach ($this->available as $filter) {
$category = str_contains($filter, '.') ? explode('.', $filter)[0] : 'other';
$groups[$category][] = $filter;
}
ksort($groups);
return $groups;
}
/** 全フィルター数を返す */
public function count(): int
{
return count($this->available);
}
/** 一覧を返す */
public function all(): array
{
return $this->available;
}
}
// 使用例
$registry = new FilterRegistry();
var_dump($registry->has('string.rot13')); // bool(true)
var_dump($registry->has('zlib.deflate')); // bool(true)
var_dump($registry->has('nonexistent')); // bool(false)
echo "stringカテゴリ: " . implode(', ', $registry->startsWith('string')) . PHP_EOL;
// → string.rot13, string.toupper, string.tolower, string.strip_tags
echo "総フィルター数: " . $registry->count() . PHP_EOL;
foreach ($registry->grouped() as $category => $filters) {
echo "[{$category}] " . implode(', ', $filters) . PHP_EOL;
}
例2:フィルター存在確認付きストリームラッパー(SafeFilterStream)
フィルターを適用する前に stream_get_filters() で存在確認を行い、未登録フィルターに対してフォールバックや例外を投げる安全なストリームラッパーです。
<?php
class FilterNotFoundException extends \RuntimeException {}
class SafeFilterStream
{
private $fp;
/** @var resource[] */
private array $attached = [];
public function __construct($stream)
{
$this->fp = $stream;
}
/**
* フィルターを安全にアタッチする
*
* @throws FilterNotFoundException フィルターが存在しない場合
*/
public function attach(string $filterName, int $mode = STREAM_FILTER_ALL, mixed $params = null): static
{
if (!$this->filterExists($filterName)) {
throw new FilterNotFoundException(
"フィルター '{$filterName}' は利用できません。" .
"stream_filter_register() で登録してください。"
);
}
$this->attached[] = stream_filter_append($this->fp, $filterName, $mode, $params);
return $this;
}
/**
* フィルターが存在すればアタッチ、なければスキップ(ソフトフォールバック)
*/
public function attachIfExists(string $filterName, int $mode = STREAM_FILTER_ALL, mixed $params = null): bool
{
if (!$this->filterExists($filterName)) {
return false;
}
$this->attached[] = stream_filter_append($this->fp, $filterName, $mode, $params);
return true;
}
private function filterExists(string $name): bool
{
$available = stream_get_filters();
if (in_array($name, $available, true)) return true;
foreach ($available as $f) {
if (str_ends_with($f, '.*') && str_starts_with($name, rtrim($f, '.*') . '.')) {
return true;
}
}
return false;
}
public function write(string $data): void { fwrite($this->fp, $data); }
public function read(int $len = 4096): string { return stream_get_contents($this->fp, offset: 0); }
public function detachAll(): void
{
foreach ($this->attached as $handle) {
stream_filter_remove($handle);
}
$this->attached = [];
}
}
// 使用例
$fp = fopen('php://memory', 'w+');
$sfs = new SafeFilterStream($fp);
// 存在するフィルター → 正常にアタッチ
$sfs->attach('string.toupper', STREAM_FILTER_WRITE);
$sfs->write('hello world');
echo $sfs->read() . PHP_EOL; // → HELLO WORLD
// 存在しないフィルター → 例外
try {
$sfs->attach('nonexistent.filter');
} catch (FilterNotFoundException $e) {
echo $e->getMessage() . PHP_EOL;
}
// ソフトフォールバック
$result = $sfs->attachIfExists('optional.filter');
echo $result ? "アタッチ成功" : "フィルターなし(スキップ)";
echo PHP_EOL;
fclose($fp);
例3:環境診断レポートクラス(StreamFilterDiagnostics)
現在の PHP 環境で利用可能なフィルターを診断し、必須フィルターの有無・拡張機能の状態・フィルター統計をレポートとして出力するクラスです。
<?php
class StreamFilterDiagnostics
{
/** 拡張機能と対応フィルターの対応表 */
private const EXTENSION_FILTERS = [
'zlib' => ['zlib.deflate', 'zlib.inflate'],
'bz2' => ['bzip2.compress', 'bzip2.decompress'],
'iconv' => ['convert.iconv.*'],
'mbstring' => [],
];
/** アプリケーションが必要とするフィルター */
private array $requiredFilters;
public function __construct(array $requiredFilters = [])
{
$this->requiredFilters = $requiredFilters;
}
public function run(): array
{
$available = stream_get_filters();
$report = [
'total' => count($available),
'filters' => $available,
'extensions' => $this->checkExtensions($available),
'required' => $this->checkRequired($available),
'categories' => $this->categorize($available),
];
return $report;
}
private function checkExtensions(array $available): array
{
$result = [];
foreach (self::EXTENSION_FILTERS as $ext => $filters) {
$loaded = extension_loaded($ext);
$result[$ext] = [
'loaded' => $loaded,
'filters' => $filters,
'status' => $loaded ? 'OK' : 'NOT LOADED',
];
}
return $result;
}
private function checkRequired(array $available): array
{
$result = [];
foreach ($this->requiredFilters as $filter) {
$exists = in_array($filter, $available, true);
$result[$filter] = $exists ? 'OK' : 'MISSING';
}
return $result;
}
private function categorize(array $filters): array
{
$categories = [];
foreach ($filters as $filter) {
$cat = str_contains($filter, '.') ? explode('.', $filter)[0] : 'other';
$categories[$cat] = ($categories[$cat] ?? 0) + 1;
}
arsort($categories);
return $categories;
}
public function printReport(): void
{
$report = $this->run();
echo "=== Stream Filter Diagnostics ===" . PHP_EOL;
echo "利用可能フィルター総数: {$report['total']}" . PHP_EOL;
echo PHP_EOL . "--- 拡張機能ステータス ---" . PHP_EOL;
foreach ($report['extensions'] as $ext => $info) {
echo " [{$info['status']}] {$ext}" . PHP_EOL;
}
if (!empty($report['required'])) {
echo PHP_EOL . "--- 必須フィルター確認 ---" . PHP_EOL;
foreach ($report['required'] as $filter => $status) {
echo " [{$status}] {$filter}" . PHP_EOL;
}
}
echo PHP_EOL . "--- カテゴリ別内訳 ---" . PHP_EOL;
foreach ($report['categories'] as $cat => $count) {
echo " {$cat}: {$count}個" . PHP_EOL;
}
}
}
// 使用例
$diag = new StreamFilterDiagnostics(
requiredFilters: ['string.rot13', 'zlib.deflate', 'myapp.custom']
);
$diag->printReport();
// 出力例:
// === Stream Filter Diagnostics ===
// 利用可能フィルター総数: 12
//
// --- 拡張機能ステータス ---
// [OK] zlib
// [OK] bz2
// [OK] iconv
// [NOT LOADED] mbstring
//
// --- 必須フィルター確認 ---
// [OK] string.rot13
// [OK] zlib.deflate
// [MISSING] myapp.custom
//
// --- カテゴリ別内訳 ---
// string: 4個
// convert: 3個
// zlib: 2個
例4:フィルター自動選択クラス(FilterAutoSelector)
複数の候補フィルターから、環境で利用可能な最初のものを自動選択するクラスです。圧縮アルゴリズムの優先選択など、環境差異を吸収する場面で活用できます。
<?php
class FilterAutoSelector
{
/**
* 優先順リストから最初に利用可能なフィルターを返す
*
* @param string[] $candidates 優先順位順のフィルター名リスト
* @return string|null 選択されたフィルター名、なければ null
*/
public function selectFirst(array $candidates): ?string
{
$available = stream_get_filters();
foreach ($candidates as $candidate) {
if (in_array($candidate, $available, true)) {
return $candidate;
}
}
return null;
}
/**
* 利用可能なフィルターを全て返す(優先順維持)
*
* @param string[] $candidates
* @return string[]
*/
public function selectAll(array $candidates): array
{
$available = stream_get_filters();
return array_values(array_filter(
$candidates,
fn($c) => in_array($c, $available, true)
));
}
}
class CompressionStreamWriter
{
private string $chosenFilter;
public function __construct()
{
$selector = new FilterAutoSelector();
// 圧縮フィルターを優先順に自動選択
$this->chosenFilter = $selector->selectFirst([
'zlib.deflate',
'bzip2.compress',
'zlib.inflate', // フォールバック(実際は非圧縮相当)
]) ?? throw new \RuntimeException('利用可能な圧縮フィルターがありません');
echo "選択された圧縮フィルター: {$this->chosenFilter}" . PHP_EOL;
}
public function compress(string $data): string
{
$fp = fopen('php://memory', 'w+');
stream_filter_append($fp, $this->chosenFilter, STREAM_FILTER_WRITE);
fwrite($fp, $data);
fflush($fp);
$compressed = stream_get_contents($fp, offset: 0);
fclose($fp);
return $compressed;
}
}
// 使用例
$writer = new CompressionStreamWriter();
$compressed = $writer->compress(str_repeat('PHP stream filter ', 100));
echo "圧縮後サイズ: " . strlen($compressed) . " バイト" . PHP_EOL;
echo "圧縮前サイズ: " . strlen(str_repeat('PHP stream filter ', 100)) . " バイト" . PHP_EOL;
例5:カスタムフィルター自動登録マネージャー(FilterAutoRegister)
アプリケーション起動時にカスタムフィルタークラスを自動スキャン・登録し、stream_get_filters() で二重登録を防ぐマネージャーです。
<?php
// --- カスタムフィルタークラスの定義 ---
class SlugifyFilter extends php_user_filter
{
public function filter($in, $out, &$consumed, bool $closing): int
{
while ($bucket = stream_bucket_make_writeable($in)) {
$text = mb_strtolower($bucket->data);
$text = preg_replace('/[^\w\s-]/u', '', $text);
$text = preg_replace('/[\s_]+/', '-', trim($text));
$bucket->data = $text;
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
class HtmlEscapeFilter extends php_user_filter
{
public function filter($in, $out, &$consumed, bool $closing): int
{
while ($bucket = stream_bucket_make_writeable($in)) {
$bucket->data = htmlspecialchars($bucket->data, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
// --- 自動登録マネージャー ---
class FilterAutoRegister
{
/** @var array{name: string, class: string}[] */
private array $registered = [];
/** @var array{name: string, class: string}[] */
private array $skipped = [];
/**
* フィルター名とクラス名のマップをまとめて登録する
*
* @param array<string, string> $map ['filter.name' => ClassName::class, ...]
*/
public function registerAll(array $map): void
{
$existing = stream_get_filters();
foreach ($map as $filterName => $className) {
// 既に登録済みならスキップ
if (in_array($filterName, $existing, true)) {
$this->skipped[] = ['name' => $filterName, 'class' => $className];
continue;
}
if (!class_exists($className)) {
throw new \InvalidArgumentException("クラス '{$className}' が見つかりません");
}
if (stream_filter_register($filterName, $className)) {
$this->registered[] = ['name' => $filterName, 'class' => $className];
}
}
}
public function getRegistered(): array { return $this->registered; }
public function getSkipped(): array { return $this->skipped; }
public function summary(): void
{
echo "登録済み: " . count($this->registered) . " 件" . PHP_EOL;
foreach ($this->registered as $r) {
echo " + {$r['name']} ({$r['class']})" . PHP_EOL;
}
echo "スキップ: " . count($this->skipped) . " 件" . PHP_EOL;
foreach ($this->skipped as $s) {
echo " = {$s['name']} (既に登録済み)" . PHP_EOL;
}
}
}
// 使用例
$manager = new FilterAutoRegister();
$manager->registerAll([
'text.slugify' => SlugifyFilter::class,
'html.escape' => HtmlEscapeFilter::class,
'string.rot13' => 'DummyClass', // 既存フィルター → スキップ
]);
$manager->summary();
// 出力:
// 登録済み: 2 件
// + text.slugify (SlugifyFilter)
// + html.escape (HtmlEscapeFilter)
// スキップ: 1 件
// = string.rot13 (既に登録済み)
// 動作確認
$fp = fopen('php://memory', 'w+');
stream_filter_append($fp, 'text.slugify', STREAM_FILTER_WRITE);
fwrite($fp, 'Hello World! PHPのストリーム処理');
echo stream_get_contents($fp, offset: 0) . PHP_EOL;
// → hello-world-php
fclose($fp);
例6:フィルターチェーンバリデーター(FilterChainValidator)
stream_get_filters() を使って、フィルターチェーンの定義をストリームへ適用する前に全フィルターの存在を一括検証するクラスです。
<?php
class FilterChainValidator
{
/** @var array{filter: string, mode: int, params: mixed}[] */
private array $chain = [];
private array $errors = [];
public function add(string $filterName, int $mode = STREAM_FILTER_ALL, mixed $params = null): static
{
$this->chain[] = ['filter' => $filterName, 'mode' => $mode, 'params' => $params];
return $this;
}
/**
* チェーン内の全フィルターが利用可能か検証する
*/
public function validate(): bool
{
$this->errors = [];
$available = stream_get_filters();
foreach ($this->chain as $entry) {
$name = $entry['filter'];
$found = in_array($name, $available, true);
// ワイルドカードフィルターとの照合
if (!$found) {
foreach ($available as $f) {
if (str_ends_with($f, '.*') && str_starts_with($name, rtrim($f, '.*') . '.')) {
$found = true;
break;
}
}
}
if (!$found) {
$this->errors[] = "フィルター '{$name}' が見つかりません";
}
}
return empty($this->errors);
}
/**
* 検証済みチェーンをストリームに適用する
*
* @throws \RuntimeException 未登録フィルターが含まれる場合
*/
public function applyTo($stream): array
{
if (!$this->validate()) {
throw new \RuntimeException(
"フィルターチェーンの検証に失敗しました:\n" .
implode("\n", $this->errors)
);
}
$handles = [];
foreach ($this->chain as $entry) {
$handles[] = stream_filter_append(
$stream,
$entry['filter'],
$entry['mode'],
$entry['params']
);
}
return $handles;
}
public function getErrors(): array { return $this->errors; }
}
// 使用例
$fp = fopen('php://memory', 'w+');
$validator = new FilterChainValidator();
$validator
->add('string.toupper', STREAM_FILTER_WRITE)
->add('string.rot13', STREAM_FILTER_WRITE)
->add('missing.filter', STREAM_FILTER_WRITE); // 存在しないフィルター
// 検証だけ先に実行
if (!$validator->validate()) {
echo "エラー:" . PHP_EOL;
foreach ($validator->getErrors() as $err) {
echo " - {$err}" . PHP_EOL;
}
}
// 正常なチェーンだけで再構築して適用
$safeValidator = new FilterChainValidator();
$safeValidator
->add('string.toupper', STREAM_FILTER_WRITE)
->add('string.rot13', STREAM_FILTER_WRITE);
try {
$safeValidator->applyTo($fp);
fwrite($fp, 'hello');
echo stream_get_contents($fp, offset: 0) . PHP_EOL;
// HELLO → ROT13 → URYYB
} catch (\RuntimeException $e) {
echo $e->getMessage() . PHP_EOL;
}
fclose($fp);
例7:フィルター利用状況モニタークラス(FilterUsageMonitor)
アプリケーション全体でどのフィルターを何回使ったかを記録し、stream_get_filters() で利用可能フィルターとの比較レポートを生成する監視クラスです。
<?php
class FilterUsageMonitor
{
/** @var array<string, int> フィルター名 => 使用回数 */
private array $usageCount = [];
/**
* フィルターをアタッチして使用を記録する
*
* @return resource|false
*/
public function attach($stream, string $filterName, int $mode = STREAM_FILTER_ALL, mixed $params = null)
{
$available = stream_get_filters();
if (!in_array($filterName, $available, true)) {
trigger_error("未登録フィルター: {$filterName}", E_USER_WARNING);
return false;
}
$this->usageCount[$filterName] = ($this->usageCount[$filterName] ?? 0) + 1;
return stream_filter_append($stream, $filterName, $mode, $params);
}
/**
* 使用状況レポートを返す
*/
public function report(): array
{
$available = stream_get_filters();
$used = array_keys($this->usageCount);
$unused = array_values(array_diff($available, $used));
arsort($this->usageCount);
return [
'usage' => $this->usageCount,
'used' => $used,
'unused' => $unused,
'total_available' => count($available),
'total_used' => count($used),
];
}
public function printReport(): void
{
$r = $this->report();
echo "=== Filter Usage Report ===" . PHP_EOL;
echo "利用可能: {$r['total_available']} 個 / 使用済み: {$r['total_used']} 個" . PHP_EOL;
echo PHP_EOL . "--- 使用回数ランキング ---" . PHP_EOL;
foreach ($r['usage'] as $name => $count) {
$bar = str_repeat('█', min($count, 20));
echo sprintf(" %-30s %s (%d回)\n", $name, $bar, $count);
}
}
}
// 使用例
$monitor = new FilterUsageMonitor();
for ($i = 0; $i < 5; $i++) {
$fp = fopen('php://memory', 'w+');
$monitor->attach($fp, 'string.toupper', STREAM_FILTER_WRITE);
fwrite($fp, "test {$i}");
fclose($fp);
}
for ($i = 0; $i < 3; $i++) {
$fp = fopen('php://memory', 'w+');
$monitor->attach($fp, 'string.rot13', STREAM_FILTER_WRITE);
fwrite($fp, "data {$i}");
fclose($fp);
}
$monitor->attach(fopen('php://memory', 'w+'), 'nonexistent.filter'); // 警告が発生
$monitor->printReport();
// 出力例:
// === Filter Usage Report ===
// 利用可能: 12 個 / 使用済み: 2 個
//
// --- 使用回数ランキング ---
// string.toupper █████ (5回)
// string.rot13 ███ (3回)
関連する関数との比較
| 関数 | 役割 |
|---|---|
stream_get_filters() | 利用可能なフィルター名を一覧取得 |
stream_filter_register() | カスタムフィルタークラスをシステムに登録 |
stream_filter_append() | フィルターをストリームの末尾にアタッチ |
stream_filter_prepend() | フィルターをストリームの先頭にアタッチ |
stream_filter_remove() | アタッチ済みフィルターを取り外す |
stream_get_wrappers() | 利用可能なストリームラッパー名を一覧取得 |
stream_get_wrappers() との違い
stream_get_filters() がストリーム上を流れるデータへの変換処理(フィルター)の一覧であるのに対し、stream_get_wrappers() は fopen('http://...') のようなストリームプロトコル(ラッパー)の一覧を返します。
print_r(stream_get_wrappers());
// → ['php', 'file', 'glob', 'data', 'http', 'ftp', 'zip', 'compress.zlib', ...]
注意点とベストプラクティス
1. ワイルドカードフィルターの扱い
zlib.* や convert.iconv.* のようにワイルドカードで返る場合があります。in_array() で完全一致確認するだけでは見落とすため、プレフィックス照合も合わせて行いましょう(例1・例2を参照)。
2. 呼び出しコストに注意
stream_get_filters() は呼び出しのたびに内部リストを構築します。ループ内で繰り返し呼ぶ場合は一度変数に保存して再利用しましょう。
// NG:ループ内で毎回呼ぶ
foreach ($filters as $f) {
if (in_array($f, stream_get_filters())) { ... }
}
// OK:一度取得して使い回す
$available = stream_get_filters();
foreach ($filters as $f) {
if (in_array($f, $available)) { ... }
}
3. カスタムフィルター登録後は再取得する
stream_filter_register() でフィルターを登録した後に stream_get_filters() を呼ぶと、登録したフィルターも含まれます。キャッシュしている場合は再取得が必要です。
stream_filter_register('myapp.trim', MyTrimFilter::class);
// 登録後に再取得
$available = stream_get_filters();
var_dump(in_array('myapp.trim', $available)); // bool(true)
まとめ
| ポイント | 内容 |
|---|---|
| 基本動作 | 現在の PHP 環境で利用可能な全フィルター名を文字列配列で返す |
| 引数 | なし |
| 戻り値 | string[](フィルター名の配列、順序は不定) |
| ワイルドカード | zlib.* など .* 終わりの要素はプレフィックス照合が必要 |
| カスタムフィルター | stream_filter_register() 後に再取得すれば一覧に含まれる |
| 活用シーン | 登録確認・環境診断・自動選択・二重登録防止・バリデーション・使用状況監視 |
stream_get_filters() は一見シンプルですが、フィルターの存在確認・環境差異の吸収・動的なフィルター選択といった場面で重要な役割を果たします。stream_filter_register() や stream_filter_append() と組み合わせることで、堅牢なストリームフィルター管理基盤を構築できます。
