eloquent / confetti
PHP 的流数据转换系统。
Requires
- php: >=5.3
- evenement/evenement: >=1,<3
- react/stream: >=0.3,<0.5
Requires (Dev)
- icecave/archer: ~1
This package is auto-updated.
Last update: 2020-06-28 08:29:47 UTC
README
PHP 的流数据转换系统。
安装和文档
- 作为 Composer 包 eloquent/confetti 提供。
- API 文档 可用。
什么是 Confetti?
Confetti 是一个用于实现流数据转换的系统。它允许单个转换实现用于字符串、React 流 和 原生 PHP 流过滤器。Confetti 转换易于实现,并可以促进广泛的流操作,例如编码、加密和增量哈希。
此库不包含转换实现。有关数据转换使用的真实世界示例,请参阅 Endec 和 Lockbox。
转换流
TransformStream 类提供了一种简单的方法,可以将转换包装在 React 流 之外。它实现了 ReadableStreamInterface 和 WritableStreamInterface。其用法如下
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 流 使用的(data
、end
、close
、error
)事件外,如果关闭时没有错误,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'