eloquent/confetti

此包已废弃,不再维护。未建议替代包。

PHP 的流数据转换系统。

0.3.1 2014-05-20 14:40 UTC

This package is auto-updated.

Last update: 2020-06-28 08:29:47 UTC


README

PHP 的流数据转换系统。

The most recent stable version is 0.3.1 Current build status image Current coverage status image

安装和文档

什么是 Confetti

Confetti 是一个用于实现流数据转换的系统。它允许单个转换实现用于字符串、React 流原生 PHP 流过滤器Confetti 转换易于实现,并可以促进广泛的流操作,例如编码、加密和增量哈希。

此库不包含转换实现。有关数据转换使用的真实世界示例,请参阅 EndecLockbox

转换流

TransformStream 类提供了一种简单的方法,可以将转换包装在 React 流 之外。它实现了 ReadableStreamInterfaceWritableStreamInterface。其用法如下

use Eloquent\Confetti\TransformStream;

$stream = new TransformStream(new Base64DecodeTransform);
$stream->on(
    'data',
    function ($data, $stream) {
        echo $data;
    }
);
$stream->on(
    'error',
    function ($error, $stream) {
        throw $error;
    }
);

try {
    $stream->write('Zm9v'); // outputs 'foo'
    $stream->end('YmFy');   // outputs 'bar'
} catch (Exception $e) {
    // unable to decode
}

成功事件

除了 React 流 使用的(dataendcloseerror)事件外,如果关闭时没有错误,TransformStream 类将发出 success 事件。将传递 success 回调函数流,并且可以通过调用 $stream->transform() 访问内部转换

$stream->on(
    'success',
    function ($stream) {
        echo get_class($stream->transform());
    }
);

组合转换

可以使用 CompoundTransform 类将任意数量的转换组合成一个单个转换实例。这对于创建应用多个转换的流很有用

use Eloquent\Confetti\CompoundTransform;
use Eloquent\Confetti\TransformStream;

$stream = new TransformStream(
    new CompoundTransform(array(new Rot13Transform, new Base64DecodeTransform))
);
$stream->on(
    'data',
    function ($data, $stream) {
        echo $data;
    }
);

$stream->write('Mz9i'); // outputs 'foo'
$stream->end('LzSl');   // outputs 'bar'

实现转换

Confetti的核心是TransformInterface接口。正确实现的转换可以用于基于字符串和流式转换。

一个简单的转换可能看起来像以下这样

use Eloquent\Confetti\TransformInterface;

class Rot13Transform implements TransformInterface
{
    public function transform($data, &$context, $isEnd = false)
    {
        return array(str_rot13($data), strlen($data), null);
    }
}

转换接收任意量的数据作为字符串,并返回一个元组(数组),其中第一个元素是转换后的数据,第二个元素是消耗的字节数量(在这个例子中,数据总是被完全消耗),第三个元素是发生的任何错误。

现在可以通过几种方式使用这个转换。要将转换应用于字符串,只需调用transform()并传递布尔值true作为$isEnd参数

$transform = new Rot13Transform;

list($data) = $transform->transform('foobar', $context, true);
echo $data; // outputs 'sbbone'

要将转换用作React流,创建一个新的TransformStream并注入转换。

use Eloquent\Confetti\TransformStream;

$stream = new TransformStream(new Rot13Transform);
$stream->on(
    'data',
    function ($data, $stream) {
        echo $data;
    }
);

$stream->end('foobar'); // outputs 'sbbone'

原生流过滤器

转换也可以用来实现原生PHP流过滤器,但PHP的流过滤器系统要求每个过滤器作为一个单独的类实现。Confetti包含一个抽象类,它可以大大简化流过滤器的实现。

要创建流过滤器,只需扩展AbstractNativeStreamFilter并实现createTransform()方法

use Eloquent\Confetti\AbstractNativeStreamFilter;

class Rot13NativeStreamFilter extends AbstractNativeStreamFilter
{
    protected function createTransform()
    {
        return new Rot13Transform;
    }
}

过滤器注册后,可以像使用任何其他流过滤器一样使用它

stream_filter_register('confetti.rot13', 'Rot13NativeStreamFilter');

$path = '/path/to/file';
$stream = fopen($path, 'wb');
stream_filter_append($stream, 'confetti.rot13');
fwrite($stream, 'foobar');
fclose($stream);
echo file_get_contents($path); // outputs 'sbbone'

请注意,检测原生流过滤器失败的唯一方法是检查写入的数据长度。如果长度为0,则表示发生错误

stream_filter_register(
    'confetti.base64decode',
    'Base64DecodeNativeStreamFilter'
);

$path = '/path/to/file';
$stream = fopen($path, 'wb');
stream_filter_append($stream, 'confetti.base64decode');
if (!fwrite($stream, '!!!!')) {
    echo 'Decoding failed.';
}
fclose($stream);

复杂转换

更复杂的转换可能无法逐字节消耗数据。例如,尝试在接收每个字节时对其进行base64解码会导致无效输出。正确数据只有在收到完整的4个base64字节块之后才能知道。还可能收到不适合base64编码方案的字节。

一个base64解码转换可能被实现如下

use Eloquent\Confetti\AbstractTransform;
use Eloquent\Confetti\BufferedTransformInterface;

class Base64DecodeTransform extends AbstractTransform implements
    BufferedTransformInterface
{
    public function transform($data, &$context, $isEnd = false)
    {
        $consume = $this->blocksSize(strlen($data), 4, $isEnd);
        if (!$consume) {
            return array('', 0, null);
        }

        $consumedData = substr($data, 0, $consume);
        if (1 === strlen(rtrim($consumedData, '=')) % 4) {
            return array('', 0, new Exception('Base64 decode failed.'));
        }

        $outputBuffer = base64_decode($consumedData, true);
        if (false === $outputBuffer) {
            return array('', 0, new Exception('Base64 decode failed.'));
        }

        return array($outputBuffer, $consume, null);
    }

    public function bufferSize()
    {
        return 4;
    }
}

这个转换现在将解码base64数据块并将结果追加到输出缓冲区。bufferSize()方法建议适合使用此转换的类的适当缓冲区大小(在这种情况下,4字节——base64块的大小),调用AbstractTransform::blocksSize()确保每次只消耗4字节的块。如果传递了无效的字节,或者数据流在无效的字节数上结束,则返回异常作为第三个元组元素以指示错误。

上下文参数

转换还可以使用$context参数。此参数可以分配任何值,并用作在流转换生命周期内保证持续存在的任意数据存储。当第一次调用transform()时,$context参数将是null。在后续调用中,$context将包含之前分配给变量的任何内容。这允许实现高级行为,如缓冲。

以下是一个上下文使用的示例,考虑一个生成传入数据的MD5哈希的转换

use Eloquent\Confetti\TransformInterface;

class Md5Transform implements TransformInterface
{
    public function transform($data, &$context, $isEnd = false)
    {
        if (null === $context) {
            $context = hash_init('md5');
        }

        hash_update($context, $data);

        if ($isEnd) {
            $output = hash_final($context);
        } else {
            $output = '';
        }

        return array($output, strlen($data), null);
    }
}

在这种情况下,$context参数用于存储哈希上下文。现在转换的工作方式如下

use Eloquent\Confetti\TransformStream;

$stream = new TransformStream(new Md5Transform);
$stream->on(
    'data',
    function ($data, $stream) {
        echo $data;
    }
);

$stream->write('foo');
$stream->end('bar'); // outputs '3858f62230ac3c915f300c664312c63f'