PHP でデータをストリームする方法

PHP でデータをストリームする方法

Takahiro Iwasa
Takahiro Iwasa
2 min read
PHP

PHP は php://output を使用してレスポンスをストリーミングできます。 これは大量のデータを処理する際に便利です。

php://output is a write-only stream that allows you to write to the output buffer mechanism in the same way as print and echo.

PHP 7.1 の例

以下は、ユーザーが CSV ファイルをダウンロードできるようにする簡単な例です。 重要なポイントは、 fopen('php://output', 'w'); でストリームを開く部分です。

App\Lib\Stream\DownloadStream.php

<?php

namespace App\Lib\Stream;

/**
 * Class DownloadStream
 * @package App\Lib\Stream
 */
class DownloadStream
{
    public static function getStream(string $fileName)
    {
        // Send header
        header("Content-Type: application/octet-stream");
        header("Content-Disposition: attachment; filename={$fileName}");

        // Get output stream
        $stream = fopen('php://output', 'w');

        // Filter to conver character code to ShiftJIS
        stream_filter_prepend($stream, 'convert.iconv.utf-8/cp932');
        // Filter to convert newline to CRLF
        stream_filter_register('CrlfFilter', 'App\Lib\Stream\Filter\CrlfFilter');
        stream_filter_append($stream, 'CrlfFilter');

        return $stream;
    }
}

App\Lib\Stream\Filter\CrlfFilter.php

これは行末を CRLF に変換するストリームフィルターです。 データのストリーミングには直接関係ありませんが、 Windows ユーザーにとって便利です。

<?php

namespace App\Lib\Stream\Filter;

/**
 * Class CrlfFilter
 * @package App\Lib\Stream\Filter
 */
class CrlfFilter extends \php_user_filter
{
    /**
     * Callback method when this filter is applied
     * @param resource $in
     * @param resource $out
     * @param int $consumed
     * @param bool $closing
     * @return int
     */
    public function filter($in, $out, &$consumed, $closing)
    {
        while ($bucket = stream_bucket_make_writeable($in)) {
            // Clear newline codes in advance
            $bucket->data = preg_replace("/\r$/", "", $bucket->data);
            $bucket->data = preg_replace("/\n$/", "", $bucket->data);
            // Append CRLF
            $bucket->data = $bucket->data . "\r\n";

            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}

使用法

以下は使用例です。

// Filename
$fileName = 'test.csv';

// Get download stream
$stream = DownloadStream::getStream($fileName);

// Output header
fputcsv($stream, [
    'last_name',
    'first_name',
    'company',
]);

// Output record
fputcsv($stream, [
    'Jane',
    'Doe',
    'ABC Company',
]);

// Close stream
fclose($stream);

まとめ

ユーザーに100万レコードを含む CSV ファイルをダウンロードさせるようなケースの場合、 php://output を利用できます。

この投稿が、お役に立てば幸いです。

Takahiro Iwasa

Takahiro Iwasa

Software Developer at KAKEHASHI Inc.
Involved in the requirements definition, design, and development of cloud-native applications using AWS. Now, building a new prescription data collection platform at KAKEHASHI Inc. Japan AWS Top Engineers 2020-2023.