alexandre-daubois/lazy-stream

只在真正需要时将数据写入流。

v1.6.1 2024-06-13 09:35 UTC

This package is auto-updated.

Last update: 2024-09-08 14:07:25 UTC


README

Minimum PHP Version CI Latest Version License

LazyStream是一个纯PHP、无依赖的库,提供了使用生成器以懒写方式写入流的方法。它允许您将数据增量地写入流,减少内存使用,并在处理大量数据时提高性能。

特性

  • 懒写:该库使用PHP生成器将数据懒写至流。这意味着数据以小块形式写入,减少内存消耗,并允许高效地处理大型数据集。
  • 多流写入(当然也是懒写的):并发地将大型数据集导出到多个文件或网络位置,例如备份和日志。
  • 懒打开:您的流将在您实际需要它之前永远不会被打开。
  • 流兼容性:该库与各种流类型兼容,包括文件流、网络流和自定义流。您可以轻松将其集成到依赖于流操作现有代码中。它实际上可以与任何类型的流一起工作,只要它通过stream_wrapper_register()注册即可。有关更多信息,请参阅此链接:https://php.ac.cn/manual/en/function.stream-wrapper-register.php
  • 流模式:目前该库只支持流读取。计划在不久的将来支持懒写流读取并提供方便的方式来实现。

安装

您可以使用Composer安装LazyStream。在您的项目目录中运行以下命令

composer require alexandre-daubois/lazy-stream

用法

使用LazyStreamWriter懒写至流

为什么使用这个写入器?

该类允许您以增量方式、以小块形式将数据写入流,而不是一次性将整个数据集加载到内存中。当处理大量数据时,这特别有益,因为它减少了内存消耗。它还提供

  • 流兼容性和与第三方库集成:该类与各种流类型兼容,包括文件流、网络流和自定义流。这种灵活性允许您无缝地将它集成到依赖于流操作的现有代码中,并允许您结合其他库利用懒写的优势。
  • 自动关闭选项LazyStreamWriter提供自动关闭选项,在写入过程完成后自动刷新并关闭流。这有助于确保适当的资源管理,并通过自动处理流清理来简化您的代码。
  • 可自定义的打开模式:您可以指定流的打开模式,允许您定义流应该如何打开以进行写入。这种灵活性使您可以根据具体要求选择适当的模式。
  • 异常处理:该类处理与流打开和写入相关的异常,提供有关错误的信息,并促进您代码中的正确错误处理。
  • 支持基于生成器的数据提供者LazyStreamWriter接受实现为生成器的数据提供者,允许您在写入流的同时动态生成或检索数据。这为处理动态数据源提供了灵活性和多功能性。
function provideJsonData(): \Generator
{
    yield '[';

    while (/** ... */) {
        $data = fetchSomewhere();

        yield sprintf('{"id": %d}', $data['id']);
    }

    return true;
}

// The stream is not opened yet, in case you never need it
$stream = new \LazyStream\LazyStreamWriter(
    'https://user:pass@example.com/my-file.json',
    provideJsonData()
);

// Trigger the stream to *actually* initiate connection
// and unwrap the iterator
$stream->trigger();

// Fetch stream's metadata, which will also be done lazily. It is
// *not* required to call `trigger()` to get those data.
$metadata = $stream->getMetadata();

配置LazyStreamWriter行为

有几个选项可用于配置懒流的行为

  • 打开模式:这允许定义用于打开流的模式。任何列出的写入模式 在此 都可以使用。
  • 自动关闭:是否在 trigger() 方法结束时自动刷新并关闭流。如果设置为 false,则在 LazyStreamWriter 对象销毁时无论如何都会进行刷新和关闭。

MultiLazyStreamWriter

MultiLazyStreamWriter 是 LazyStream 库中的一个核心类。这个类使您能够从任何迭代器(如生成器)并发地向多个流写入。这在您需要以内存高效的方式将大量数据写入不同的位置时特别有益。

此类在需要将大量数据写入多个目标时非常有用

  • 日志系统:将日志数据写入多个目标,如本地文件、网络套接字等。
  • 数据导出:并发地将大型数据集导出到多个文件或网络位置。
  • 备份系统:将备份数据写入多个存储位置。

以下是一个使用示例

use LazyStream\MultiLazyStreamWriter;

class BackupProvider
{
    public function provideData(): \Generator
    {
        // Yield backup data
    }
}

// Write your backups in many locations at once
$stream = new MultiLazyStreamWriter([
        'https://user:pass@example.com/backup.json',
        'gs://backup_path/backup.json',
        's3://backup_path/backup.json',
    ],
    (new BackupProvider())->provideData()
);

$stream->trigger();

LazyStreamChunkWriter

LazyStreamChunkWriter 类是一个专用类,允许您以块的形式将数据写入流。其机制与其他写入器相当不同。您可以通过调用 send() 方法来写入数据,而不是从生成器写入数据。这允许以更受控和“自然”的方式写入数据,而无需担心生成器和迭代器。

LazyStreamChunkWriter 总是向流中追加数据,因此无法控制打开模式。 这是为了确保正确的自动关闭行为。如果您需要向空流中写入数据,应使用 LazyStreamWriter 类或确保在发送数据之前流为空。

以下是如何使用 LazyStreamChunkWriter 类的示例

use LazyStream\LazyStreamChunkWriter;

$stream = new LazyStreamChunkWriter('https://user:pass@example.com/my-file.json');

$data = /** fetch data from somewhere */;
$stream->send($data);

// normal flow of the application

$data = /** fetch data from somewhere else */;
$stream->send($data);

使用 LazyStreamReader 惰性读取流

文件默认情况下已经以惰性方式读取:当您调用 fread() 时,您只会获取所需字节数,而不会更多。LazyStreamReader 做同样的事情,但它还允许您在读取操作之间保持流开启或关闭。

为什么使用这个读取器?

LazyStreamReader 类的自动关闭功能提供了几个具体用例,其中它可以很有用

  • 自动资源管理:当与流资源(如文件或网络连接)一起工作时,正确关闭它们以释放系统资源非常重要。通过启用自动关闭,您确保流在每次读取操作后自动关闭,从而避免潜在的资源泄露。
  • 迭代读取:如果您想迭代地从流中读取数据,执行顺序读取操作,自动关闭可以简化您的代码。每次读取后,流会自动关闭,在下次读取操作中,它会在相同位置重新打开,允许无缝迭代流的数据。
  • 异步处理:当与异步读取操作或并行任务一起工作时,自动关闭可能很有用。每次读取后,流会关闭,允许其他任务在需要时访问流。当任务准备好执行下一次读取操作时,流会自动重新打开。
  • 细粒度内存管理:如果您正在处理大型或持续时间长的流,关闭流后释放读取缓冲区使用的内存可能是有益的。自动关闭通过在每次读取操作后立即关闭流来实现细粒度的内存管理。

通过使用自动关闭功能,您可以简化资源管理,简化迭代或异步操作,并在从流读取数据时更好地控制内存管理。

在创建新的 LazyStreamReader 对象时,将 autoClose 选项设置为 true,您将请求在每次读取操作后关闭流,并在下一次读取操作被触发时再次打开它。您将从关闭流前的相同位置恢复。

// The stream is not opened yet, in case you never need it
$stream = new \LazyStream\LazyStreamReader('https://user:pass@example.com/my-file.png', chunkSize: 1024, autoClose: true, binary: true);

// Use the stream directly in the loop
foreach ($stream as $str) {
    // With auto-closing, the stream is already closed here. You can
    // do any long operation, and the stream will be opened again when
    // you get in the next loop iteration
}

LazyStream 与第三方库的使用

这个库也很好地与第三方库兼容。例如,您可以将其与google/cloud-storage包结合使用,以便在不担心内存问题(及其他问题)的情况下将大文件写入您的存储桶。

确实,Google Cloud Storage 包包含注册 Google Storage 流包装器并使用 gs:// 协议的方式。以下是一个使用该库的示例

use Google\Cloud\Storage\StorageClient;

class GoogleCloudStorageLazyStreamFactory
{
    private StorageClient $storageClient;

    public function __construct(string $serviceAccountPath, string $projectId)
    {
        // Pass your service account file and other needed information
        $this->storageClient = new StorageClient([
            'keyFilePath' => $serviceAccountPath,
            'projectId' => $projectId,
        ]);

        // This is the key to register the new `gs://` protocol
        $this->storageClient->registerStreamWrapper();
    }

    public function __destruct()
    {
        // Optionally, unregister wrapper once the factory is destroyed
        $this->storageClient->unregisterStreamWrapper();
    }

    public function createLazyStream(string $bucket, string $path, \Generator $generator): LazyStream
    {
        // You can then create a new LazyStream with this protocol
        // and stream big files to your bucket
        return new LazyStream(sprintf('gs://%s/%s', $bucket, $path), $generator);
    }
}