PHP 自定义流过滤器
流过滤器允许对流中的文本数据进行更改。这允许在写入或读取流时更改文本,而不是在流运行后更改文本。PHP中内置了一个框架,允许将自定义过滤器添加到内置过滤器组中。
让我们先看看PHP内置了哪些过滤器。
stream_get_filters()
要查看系统上存在哪些过滤器,您可以运行该stream_get_filters()函数。这将返回可用过滤器的列表。
$ php -r "print_r(stream_get_filters());" Array ( [0] => zlib.* [1] => bzip2.* [2] => convert.iconv.* [3] => string.rot13 [4] => string.toupper [5] => string.tolower [6] => string.strip_tags [7] => convert.* [8] => consumed [9] => dechunk )
这里有很多过滤器可供选择,但我们如何创建自己的过滤器?弄清楚如何生成自定义过滤器有点困难,因为这可能是PHP中记录最少的方面。这里使用的大部分内部函数根本没有记录,这使得很难弄清楚发生了什么。
在我们开始创建自己的过滤器之前,有一些事情需要解决。
水桶旅
“bucketbrigade”(如果你不知道的话)是一个英语成语,它描述了一系列人来回传递水桶以便为火供水。
当我们调用自定义过滤器时,会向它传递一个桶队。在内部,这是一个PHP资源userfilter.bucketbrigade,它的作用类似于userfilter.bucket资源的双向链表。该 userfilter.bucket资源包含我们的数据。
stream_bucket_make_writeable()
使用这个函数,我们可以传入一个userfilter.bucket旅 资源并从中获取下一个userfilter.bucket对象。
$bucket=stream_bucket_make_writeable($in);
该对象是一个stdClass对象,具有以下结构。
bucket-这是userfilter.bucket资源。
数据-如果一次处理太多,这将是所有数据或数据的一部分。
datalen-data属性中数据的长度。
为了证明这一点,让我们看一下实际对象的结构。
print_r(stream_bucket_make_writeable($in));
我们将打印出这样的东西(取决于我们过滤的数据)。
object(stdClass)#2 (3) { ["bucket"]=>resource(10) of type (userfilter.bucket) ["data"]=>string(119) "Antimony is a silvery, lustrous gray metalloid with a Mohs scale hardness of 3, which is too soft to make hard objects." ["datalen"]=>int(119) }
如果资源中没有剩余数据,则此函数将返回null。这意味着我们可以循环遍历bucketbrigade并继续抓取bucket项目,直到找到null为止。
stream_bucket_prepend()和 stream_bucket_append()
有了我们从我们那里得到的对象,stream_bucket_make_writeable()我们现在需要把它放在某个地方。这就是函数stream_bucket_prepend()和stream_bucket_append()进来的地方。
自定义过滤器有一个$in参数和一个$out参数(它们都是userfilter.bucket旅 资源)。数据stream_bucket_make_writable()以对象的形式从$in参数中提取出来。然后在此对象中更改数据,然后使用这些函数之一将其传递回$out参数。
以下是使用流的整个过程的示例。
$bucket = stream_bucket_make_writeable($in); $bucket->data = strtoupper($bucket->data); stream_bucket_append($out, $bucket);
stream_bucket_new()
该函数接受一个资源和一个字符串,并将返回一个存储桶,我们可以使用它向输出存储桶旅添加内容。该函数具有以下足迹。
stream_bucket_new(resource$stream,string$buffer):stdClass
我们可以采用任何流,使用它来创建一个包含一些字符串内容的存储桶,然后将其附加到我们的过滤器输出流中。例如,我们可以生成一个流内存流,然后像这样创建一个新的存储桶。
$stream = fopen('php://memory', 'r'); $bucket = stream_bucket_new($stream, 'monkey'); stream_bucket_append($out, $bucket);
我们创建的任何自定义过滤器都会自动获得一个名为流的属性。这本质上是一个指向正在处理的流的链接,并为我们提供了一个方便的资源,我们可以使用它来生成一个新的存储桶。例如,要将字符串添加到输出流中,我们可以执行以下操作。
$bucket = stream_bucket_new($this->stream, PHP_EOL); stream_bucket_append($out, $bucket);
请注意,过滤器对象的流属性是在第一次filter()调用该方法时创建的。如前所述,它将被设置为我们目前正在处理的流。
创建自定义过滤器
自定义过滤器被创建为一个类,其中在应用过滤器时调用某些函数。这些函数在php_user_filter类中有详细说明,但直接使用该类并没有用。为了方便子类的创建,我创建了一个接口,强制过滤器类实现某些功能。
interface CustomFilter { /** * Called when applying the filter. * * @param resource $in * in is a resource pointing to a bucket brigade which contains one or more bucket * objects containing data to be filtered. * @param resource $out * out is a resource pointing to a second bucket brigade into which your modified * buckets should be placed. * @param int $consumed * consumed, which must always be declared by reference, should be incremented by * the length of the data which your filter reads in and alters. In most cases * this means you will increment consumed by $bucket->datalen for each $bucket. * @param bool $closing * If the stream is in the process of closing (and therefore this is the last pass * through the filterchain), the closing parameter will be set to TRUE. * * @return int * The filter() method must return one of three values upon completion. * - PSFS_PASS_ON: Filter processed successfully with data available in the out * bucket brigade. * - PSFS_FEED_ME: Filter processed successfully, however no data was available to * return. More data is required from the stream or prior filter. * - PSFS_ERR_FATAL (default): The filter experienced an unrecoverable error and * cannot continue. */ public function filter($in, $out, &$consumed = NULL, bool $closing = false): int; /** * Called when creating the filter. * * @return bool * Your implementation of this method should return FALSE on failure, or TRUE on success. */ public function onCreate(): bool; /** * Called when closing the filter. */ public function onClose(): void; }
使用这个接口,我组合了一个对字符串执行过滤器的类。作为一个例子,我创建了一个类,它将接受一个字符串并将每个字符转换为X。
请注意,此类的所有属性都是由PHP自动生成的。
class CensorFilter implements CustomFilter { /** * Name of the filter registered by stream_filter_append(). * * @var string */ public $filtername; /** * Additional parameters passed through the stream_filter_append() function. * * @var mixed */ public $params; /** * A resource of type 'userfilter.filter'. * * @var resource */ public $filter; /** * A resource of type 'stream'. * * @var resource */ public $stream; public function filter($in, $out, &$consumed = NULL, bool $closing = false): int { while ($bucket = stream_bucket_make_writeable($in)) { $bucket->data = preg_replace('/[a-zA-Z][0-9]/', 'X', $bucket->data); $consumed += $bucket->datalen; stream_bucket_append($out, $bucket); } return PSFS_PASS_ON; } public function onCreate(): bool { return true; } public function onClose(): void { } }
filter()此类中的方法是我们的审查过滤器中进行大部分工作的地方。这个函数的两个重要部分是$in,它是我们的传入bucketbrigade和$out,它是传出bucketbrigade。在上面的类中,我们正在执行以下操作:
获取$in桶旅并调用stream_bucket_make_writable()它,直到我们得到一个空值。
从创建的存储桶中获取数据并使用将其交换为审查版本preg_replace()。
通过将桶的长度写入$consumed变量,让PHP知道我们消耗了多少。这是通过引用传递的,因此此分配将在上游冒泡。请注意,由于我们没有更改文本的长度,因此可以不理会这个值。
使用该stream_bucket_append()函数将存储桶写入$out存储桶旅。
返回PSFS_PASS_ON,假设我们已经成功通过了流。
一个重要的方面是我们必须循环遍历bucketbrigade中的所有数据,否则我们将生成以下警告。
PHPWarning: fgets():Unprocessedfilterbucketsremainingoninputbrigadeincustom_filter.phponline115
使用自定义过滤器
要将此类用作过滤器,我们调用了两个函数。该 stream_filter_register()函数将注册过滤器类,该 stream_filter_append()函数会将过滤器附加到流中。
这是一个所有正在运行的示例。
//注册CensorFilter类。 stream_filter_register("censor", "CensorFilter"); //从文本文件创建流。 $stream = fopen('test.txt', 'r'); //将审查过滤器附加到我们的流中。 stream_filter_append($stream, "censor"); //将流读出到命令行。 while (false !== ($line = fgets($stream))) { echo $line; } //关闭流。 fclose($stream);
带有包含以下内容的文本文件。
Antimonyisasilvery,lustrousgraymetalloidwithaMohsscalehardnessof3.
这个的输出是。
XXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.
我们还可以使用php://filter语法将过滤器应用于流。
stream_filter_register("censor", "CensorFilter"); $input = fopen('php://filter/read=censor/resource=test.txt', 'r'); while (false !== ($line = fgets($input))) { echo $line; }
最后,我要感谢这篇文章,它帮助我解决了PHP的这一部分中的一些未知问题。