利用PHP的流,使用書簡式的Zun-Doko-Kiyoshi遊戲

“ズンドコキヨシまとめ” – 丘塔

我感觉错过了时机,但我尝试使用流来实现,因为我还没有看到这种实现方法。

“Stream是什么意思?”PHP中的流是处理大型二进制数据的机制。虽然有类似的迭代器和生成器的机制,但流专门针对二进制序列,具有伪装文件系统和与套接字通信结合等独特功能。

嗯,这和C语言的流非常相似。接口也继承了C语言的风格,并不像node.js的API。

使用流媒体的沉默打击计划

    1. 生成一个无限输出“ズン”和“ドコ”的流。

 

    1. 使用流过滤器监视输出,并在满足条件时将“キヨシ”添加到输出中。

 

    在“キヨシ”后停止输出任何内容,结束循环。

这样的感觉怎么样呢?虽然看起来有些复杂,但我认为对学习流的人来说正合适。

创建一个无限的Zundoko Stream。如果要在PHP端实现流(stream)的话,可以使用streamWrapper。
在这里(http://jp2.php.net/manual/ja/class.streamwrapper.php)可以找到相关方法的实现。我们将一直去实现这些方法,但是这次我们只需要实现读取功能,所以会尽可能地省略其他方法的实现。

class Zundoko
{
    function stream_open($path, $mode, $options, &$opened_path) {
        return true;
    }
    function stream_read(int $count) {
        return (mt_rand(0, 1) ? 'ズン' : 'ドコ') . PHP_EOL;
    }
    function stream_eof() {
        return false;
    }
}
stream_wrapper_register('zundoko', 'Zundoko');

通过这个操作,zundoko:// schema 和 Zundoko stream 成功关联起来了。你可以通过 fopen 来创建 zundoko stream。

$zd = fopen('zundoko://', 'r');
for ($i = 0; $i < 10; ++$i) {
    echo fgets($zd);
}

如果执行,应该会显示“嘭嘭嘭嘭嘭嘭嘭嘭嘭嘭嘭”,如果设为无限循环,就可以无限次地进行“嘭嘭嘭嘭嘭嘭嘭嘭嘭嘭嘭”。

您还可以使用file_get_contents在指定长度下获取“ズンドコ”字符串。由于必须指定字节长度,因此您需要将所需的“ズンドコ”个数乘以7。

echo file_get_contents('zundoko://', false, null, 0, 70); //ズンドコ10個出力

创建Kiyoshi流过滤器
为了处理流,您可以创建另一个流并连接它们,但是由于存在流过滤器机制,我决定试试看这种方法是否多余。

流过滤器是一种在流中进行数据转换的机制。在简单的情况下,可以使用类似string.rot13这样的方法来执行rot13转换。通过继承内置类php_user_filter,可以创建流过滤器。

class Kiyoshi extends php_user_filter
{
    private $zundoko = [];
    private $finished = false;
    const KIYOSHI = ['ズン', 'ズン', 'ズン', 'ズン', 'ドコ'];

    public function filter($in, $out, &$consumed, $closing)
    {
        if ($this->finished) {
            while ($bucket = stream_bucket_make_writeable($in));
            return PSFS_ERR_FATAL;
        }
        while ($bucket = stream_bucket_make_writeable($in)) {
            $this->zundoko[] = rtrim($bucket->data);
            if (count($this->zundoko) > 5) {
                array_shift($this->zundoko);
            }
            if (self::KIYOSHI === $this->zundoko) {
                $bucket->data    .= "キ・ヨ・シ!\n";
                $bucket->datalen += 19;
                $this->finished = true;
            }
            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}
stream_filter_register('kiyoshi', 'Kiyoshi');

在php_user_filter中实现的是一个名为filter的方法。
传递给filter的$in和$out是称为Bucket Brigade的特殊资源,类似于流处理的二进制数组。每个二进制数组对应一个Bucket,多个Bucket集合在一起称为Bucket Brigade。

使用stream_bucket_make_writeable函数可以将一个个Bucket取出来。

$bucket = stream_bucket_make_writeable($in);

您只需对$bucket执行var_dump操作就可以了解到它是一个简单的stdClass对象。

class stdClass#3 (3) {
  public $bucket =>
  resource(68) of type (userfilter.bucket)
  public $data =>
  string(7) "ドコ
"
  public $datalen =>
  int(7)
}

在filter方法中,我们可以从传入的Bucket Brigade中逐个取出Bucket,并对其进行处理,然后使用stream_bucket_append方法将其传递给$out。这样就可以实现所需的功能。

完成版 – Final version

<?php
// streamでズンドコキヨシ
class Zundoko
{
    function stream_open($path, $mode, $options, &$opened_path) {
        return true;
    }
    function stream_read(int $count) {
        return (mt_rand(0, 1) ? 'ズン' : 'ドコ') . PHP_EOL;
    }
    function stream_eof() {
        return false;
    }
}
stream_wrapper_register('zundoko', 'Zundoko');

class Kiyoshi extends php_user_filter
{
    private $zundoko = [];
    private $finished = false;
    const KIYOSHI = ['ズン', 'ズン', 'ズン', 'ズン', 'ドコ'];
    public function filter($in, $out, &$consumed, $closing)
    {
        if ($this->finished) {
            while ($bucket = stream_bucket_make_writeable($in));
            return PSFS_ERR_FATAL;
        }
        while ($bucket = stream_bucket_make_writeable($in)) {
            $this->zundoko[] = rtrim($bucket->data);
            if (count($this->zundoko) > 5) {
                array_shift($this->zundoko);
            }
            if (self::KIYOSHI === $this->zundoko) {
                $bucket->data     .= "キ・ヨ・シ!\n";
                $bucket->datalen  += 19;
                $this->finished = true;
            }
            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}
stream_filter_register('kiyoshi', 'Kiyoshi');

$zd = fopen('zundoko://', 'r');
stream_filter_append($zd, 'kiyoshi');
while ($result = fgets($zd)) {
    echo $result;
}

或许在不使用无意义的流过滤器的情况下,只需通过Zundoko流来完成清宫判定会更简单吧。

bannerAds