流过滤器
概述
流过滤器(Stream Filters)是 PHP 流系统中的数据转换层,可以在数据读写过程中自动进行编码转换、加密解密、字符串处理等操作。PHP 内置了多种过滤器(如 string.toupper、convert.iconv、zlib.deflate 等),同时也支持自定义过滤器。
适用场景
- 文件编码转换
- 数据加密/解密
- 字符串大小写转换
- Base64 编解码
- 数据压缩/解压
基础概念
内置过滤器
| 过滤器 | 功能 |
|---|---|
string.rot13 | ROT13 编码 |
string.toupper | 转大写 |
string.tolower | 转小写 |
string.strip_tags | 去除 HTML/PHP 标签 |
string.base64 | PHP 8.0+ Base64 编解码 |
convert.iconv.* | 字符编码转换 |
convert.base64-encode | Base64 编码 |
convert.base64-decode | Base64 解码 |
convert.quoted-printable-encode | QP 编码 |
convert.quoted-printable-decode | QP 解码 |
zlib.deflate | 压缩 |
zlib.inflate | 解压 |
bzip2.compress | Bzip2 压缩 |
bzip2.decompress | Bzip2 解压 |
核心函数
| 函数 | 功能 |
|---|---|
stream_filter_append() | 追加过滤器 |
stream_filter_prepend() | 前置过滤器 |
stream_filter_remove() | 移除过滤器 |
stream_get_filters() | 获取所有已注册过滤器 |
过滤器方向
stream_filter_append() 在读取时最后执行(写入时最先执行),stream_filter_prepend() 在读取时最先执行。
语法与代码示例
使用 php://filter 读取
php
<?php
// 读取时转大写
$content = file_get_contents(
'php://filter/read=string.toupper/resource=/tmp/data.txt'
);
echo $content;
// Base64 编码读取
$encoded = file_get_contents(
'php://filter/read=convert.base64-encode/resource=/tmp/image.jpg'
);
// 去除 HTML 标签后读取
$plain = file_get_contents(
'php://filter/read=string.strip_tags/resource=/tmp/page.html'
);
// 链式过滤器(先去标签再转大写)
$clean = file_get_contents(
'php://filter/read=string.strip_tags|string.toupper/resource=/tmp/data.html'
);使用 php://filter 写入
php
<?php
// 写入时进行 Base64 编码
file_put_contents(
'php://filter/write=convert.base64-encode/resource=/tmp/encoded.txt',
'Hello, World!'
);
// encoded.txt: SGVsbG8sIFdvcmxkIQ==
// 写入时压缩
file_put_contents(
'php://filter/write=zlib.deflate/resource=/tmp/compressed.bin',
file_get_contents('/tmp/large_data.txt')
);
// 读取时解压
$original = file_get_contents(
'php://filter/read=zlib.inflate/resource=/tmp/compressed.bin'
);stream_filter_append 动态添加过滤器
php
<?php
// 打开文件后动态添加过滤器
$handle = fopen('/tmp/data.txt', 'r+');
// 添加大写过滤器(读取方向)
$filter = stream_filter_append($handle, 'string.toupper', STREAM_FILTER_READ);
// 添加 Base64 编码过滤器(写入方向)
$encFilter = stream_filter_append($handle, 'convert.base64-encode', STREAM_FILTER_WRITE);
// 写入数据(自动 Base64 编码)
fwrite($handle, 'Hello');
rewind($handle);
// 读取数据(自动转大写)
echo stream_get_contents($handle); // HELLO
// 移除过滤器
stream_filter_remove($filter);
fclose($handle);字符编码转换
php
<?php
// convert.iconv 将文件从 GBK 转为 UTF-8
$content = file_get_contents(
'php://filter/read=convert.iconv.GBK.UTF-8/resource=/tmp/gbk_file.txt'
);
// 写入时转换为 GBK
file_put_contents(
'php://filter/write=convert.iconv.UTF-8.GBK/resource=/tmp/gbk_output.txt',
'中文字符'
);
// 查看可用的 iconv 编码
$filters = stream_get_filters();
$iconvFilters = array_filter($filters, fn($f) => str_starts_with($f, 'convert.iconv.'));
echo "可用的编码转换过滤器: " . count($iconvFilters) . " 个\n";ROT13 编码
php
<?php
// ROT13 是简单的字母替换编码(不是加密!)
$encoded = file_get_contents(
'php://filter/read=string.rot13/resource=/tmp/secret.txt'
);
// 写入 ROT13 编码
file_put_contents(
'php://filter/write=string.rot13/resource=/tmp/encoded.txt',
'Hello World'
);
// encoded.txt: Uryyb Jbeyq
// ROT13 是对称的,编码两次恢复原文
$decoded = file_get_contents(
'php://filter/read=string.rot13/resource=/tmp/encoded.txt'
);
echo $decoded; // Hello World实战示例
自定义流过滤器
php
<?php
declare(strict_types=1);
// PHP 8.1+:继承 php_user_filter
class CsvToArrayFilter extends php_user_filter
{
private string $buffer = '';
private int $lineNumber = 0;
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);
$this->buffer = array_pop($lines); // 保存不完整的行
foreach ($lines as $line) {
$line = trim($line);
if ($line === '') continue;
$this->lineNumber++;
$fields = str_getcsv($line);
$outputLine = sprintf(
"Line %d: %s (%d fields)\n",
$this->lineNumber,
$fields[0] ?? '',
count($fields)
);
$bucket = stream_bucket_new($this->stream, $outputLine);
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
public function onCreate(): bool
{
$this->buffer = '';
$this->lineNumber = 0;
return true;
}
}
// 注册自定义过滤器
stream_filter_register('csv.analyze', CsvToArrayFilter::class);
// 使用自定义过滤器
$result = file_get_contents('php://filter/read=csv.analyze/resource=/tmp/data.csv');
echo $result;
/*
Line 1: Alice (3 fields)
Line 2: Bob (3 fields)
*/数据压缩流处理器
php
<?php
declare(strict_types=1);
class CompressionHelper
{
/**
* 使用流过滤器压缩文件
*/
public static function compressFile(string $source, string $dest, string $algorithm = 'zlib'): void
{
$filterName = match ($algorithm) {
'zlib' => 'zlib.deflate',
'bzip2' => 'bzip2.compress',
default => throw new InvalidArgumentException("不支持的算法: {$algorithm}"),
};
$sourceHandle = fopen($source, 'rb');
$destHandle = fopen($dest, 'wb');
if (!$sourceHandle || !$destHandle) {
throw new RuntimeException('文件打开失败');
}
stream_filter_append($destHandle, $filterName);
stream_copy_to_stream($sourceHandle, $destHandle);
fclose($sourceHandle);
fclose($destHandle);
}
/**
* 使用流过滤器解压文件
*/
public static function decompressFile(string $source, string $dest, string $algorithm = 'zlib'): void
{
$filterName = match ($algorithm) {
'zlib' => 'zlib.inflate',
'bzip2' => 'bzip2.decompress',
default => throw new InvalidArgumentException("不支持的算法: {$algorithm}"),
};
$sourceHandle = fopen($source, 'rb');
$destHandle = fopen($dest, 'wb');
if (!$sourceHandle || !$destHandle) {
throw new RuntimeException('文件打开失败');
}
stream_filter_append($sourceHandle, $filterName);
stream_copy_to_stream($sourceHandle, $destHandle);
fclose($sourceHandle);
fclose($destHandle);
}
}
// 使用
CompressionHelper::compressFile('/tmp/data.txt', '/tmp/data.zlib');
CompressionHelper::decompressFile('/tmp/data.zlib', '/tmp/restored.txt');注意事项
过滤器顺序
php
<?php
// 链式过滤器的执行顺序
// php://filter/read=A|B|C/resource=file
// 读取时:先 C,再 B,最后 A
// 等价于 A(B(C(data)))
$doubleFiltered = file_get_contents(
'php://filter/read=string.toupper|string.rot13/resource=/tmp/data.txt'
);
// 数据先经过 rot13,再经过 toupper流过滤器 vs php://filter
php
<?php
// php://filter 方式(适合一次性操作)
$content = file_get_contents('php://filter/read=string.toupper/resource=/tmp/data.txt');
// stream_filter_append 方式(适合需要多次读写)
$handle = fopen('/tmp/data.txt', 'r+');
$filter = stream_filter_append($handle, 'string.toupper', STREAM_FILTER_READ);
$data = stream_get_contents($handle);
stream_filter_remove($filter);
fclose($handle);最佳实践
1. 使用 convert.iconv 替代手动转换
php
<?php
// 不好:手动读取再转换
$content = file_get_contents('/tmp/gbk.txt');
$content = mb_convert_encoding($content, 'UTF-8', 'GBK');
// 好:使用流过滤器透明转换
$content = file_get_contents(
'php://filter/read=convert.iconv.GBK.UTF-8/resource=/tmp/gbk.txt'
);2. 大文件压缩使用流
php
<?php
// 流式压缩,不需要全部加载到内存
$in = fopen('/tmp/large_file.bin', 'rb');
$out = fopen('/tmp/large_file.bin.zlib', 'wb');
stream_filter_append($out, 'zlib.deflate');
stream_copy_to_stream($in, $out);
fclose($in);
fclose($out);进阶用法
调试与测试技巧
php
<?php
declare(strict_types=1);
// 单元测试辅助函数
function createTestResource(): mixed
{
return match (true) {
default => new stdClass(),
};
}
// 调试输出函数
function debugOutput(mixed , string = ''): void
{
= ? ": " : '';
.= print_r(, true);
fwrite(STDERR, . "\n");
}
// 性能基准测试
function benchmark(callable , int = 1000): float
{
= hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
$fn();
}
return (hrtime(true) - $start) / 1e9;
}日志记录实践
php
<?php
declare(strict_types=1);
/**
* 简易日志记录器
*/
class SimpleLogger
{
private string $logFile;
private string $level = 'INFO';
public function __construct(string $logFile)
{
$this->logFile = $logFile;
}
public function info(string $message, array $context = []): void
{
$this->log('INFO', $message, $context);
}
public function warning(string $message, array $context = []): void
{
$this->log('WARNING', $message, $context);
}
public function error(string $message, array $context = []): void
{
$this->log('ERROR', $message, $context);
}
private function log(string $level, string $message, array $context): void
{
$timestamp = date('Y-m-d H:i:s');
$contextStr = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '';
$line = "[{$timestamp}] [{$level}] {$message}{$contextStr}\n";
file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
}
}配置与环境检测
php
<?php
declare(strict_types=1);
// 环境检测工具
class EnvironmentChecker
{
public static function checkRequirements(array $requirements): array
{
$results = [];
foreach ($requirements as $name => $check) {
$results[$name] = is_callable($check) ? $check() : false;
}
return $results;
}
public static function getSystemInfo(): array
{
return [
'php_version' => PHP_VERSION,
'os' => PHP_OS,
'sapi' => PHP_SAPI,
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'loaded_extensions' => get_loaded_extensions(),
];
}
}常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 连接超时 | 网络问题/配置错误 | 检查配置,增加超时时间 |
| 权限不足 | 文件/目录权限 | 使用 chmod/chown 修正 |
| 性能下降 | 索引缺失/数据量大 | 添加索引,优化查询 |
| 数据不一致 | 并发冲突/事务残留 | 使用锁机制和事务 |
| 内存溢出 | 大数据集/未释放资源 | 增大内存限制,分批处理 |
故障排除步骤
- 检查错误日志和异常信息
- 确认配置和环境是否正确
- 使用调试工具逐步排查
- 参考官方文档查找已知问题
版本兼容性说明
| 功能 | 最低版本 | 说明 |
|---|---|---|
| 基础功能 | PHP 8.1 | 本文档基准版本 |
| 只读属性 | PHP 8.1 | public readonly 修饰符 |
| 枚举类型 | PHP 8.1 | enum 类型和 match 表达式 |
| Fiber | PHP 8.1 | 协程/轻量级并发 |
| 命名参数 | PHP 8.0 | foo(arg_name: value) |
| 联合类型 | PHP 8.0 | `int |
| Null 安全运算符 | PHP 8.0 | $obj?->method() |
| 析构器 promotion | PHP 8.0 | __construct(public $x) |
php
<?php
declare(strict_types=1);
// 版本兼容性检测
function ensureVersion(string $minVersion): void
{
if (version_compare(PHP_VERSION, $minVersion, '<')) {
throw new RuntimeException(
sprintf('需要 PHP %s+, 当前版本: %s', $minVersion, PHP_VERSION)
);
}
}
ensureVersion('8.1.0');