PHP - 零长度(空)CSV,但仅适用于较大的数据集

时间:2013-08-12 09:36:40

标签: php csv yii export

虽然我的应用程序是使用Yii框架构建的,但这更像是一般的PHP问题(我认为)。

我有一些代码,它采用Yii CActiveDataProvider,循环并构建CSV导出。这非常有效,可以正确构建和发送CSV。

尝试导出较大的数据集时遇到问题。我已成功输出~2500条记录而没有任何问题但是当我为更大的数据集(~5000条记录)运行完全相同的代码时,脚本似乎运行正常但发送零长度/空白CSV。我无法弄清楚为什么......它似乎运行了一段时间然后发送CSV,没有错误或警告在日志中。可能是输出在准备好之前被冲洗或类似吗?

代码如下(为清楚起见,在此处添加了几个内联注释):

<?php
header('Content-type: text/csv');
header('Content-Disposition: attachment; filename="vacancies.csv"');

set_time_limit(240); // I know this is arbitrarily long, it's just to avoid any timeout

$outstream = fopen("php://output", 'w');

$headings = array(
            $vacancy->getAttributeLabel('vacancy_id'), // this is a Yii method that returns the active record attribute as a string
            ...         
            );

fputcsv($outstream, $headings, ',', '"');

foreach($dp->getData() as $vacancy){ // the getData() method pulls the next active record model out of the Yii dataprovider and the values for various attributes are set below
    $row = array(
                $vacancy->vacancy_id,
                ...
                );

    fputcsv($outstream, $row, ',', '"');
}

fclose($outstream);
?>

关于为什么这样做可以达到一定数量的记录的任何想法?

更新 按照下面的建议重新检查日志后,我发现我实际上已经没有内存了,呵呵!

我可以写出文件系统,这可以让我获得大约3000条记录,但随后内存不足。有没有想过改变我的代码以避免内存耗尽的最佳方法?

1 个答案:

答案 0 :(得分:1)

非常感谢有关检查错误日志的建议,我不知道错过了我遇到的内存不足错误。

问题实际上是由我使用Yii Framework中的CActiveDataProvider的方式引起的。直接从DataProvider中读取,就像我在我的问题中所做的那样是将每一行读入内存,因为脚本运行就意味着我最终耗尽了PHP可用的内存。

有几种方法可以解决这个问题,一种是将数据提供者的分页设置为较少数量的记录,并手动迭代数据,每次迭代时只将pagesize加载到内存中。

我选择的选项是使用CDataProviderIterator为我处理$iterator = new CDataProviderIterator($dp);,这可以防止内存填满我正在检索的记录。

请注意,我还必须添加ob_flush();调用,以防止输出缓冲区填满CSV内容。

作为参考,我最终得到以下内容:

<?php
header('Content-type: text/csv');
header('Content-Disposition: attachment; filename="vacancies.csv"');

set_time_limit(240); 

$outstream = fopen("php://output", 'w');

$headings = array(
        $vacancy->getAttributeLabel('vacancy_id'), 
        ...         
        );

fputcsv($outstream, $headings, ',', '"');

$iterator = new CDataProviderIterator($dp); // create an iterator instead of just using the dataprovider

foreach($iterator  as $vacancy){ // use the new iterator here
    $row = array(
            $vacancy->vacancy_id,
            ...
            );

    fputcsv($outstream, $row, ',', '"');

    ob_flush(); // explicitly call a flush to avoid filling the buffer
}

fclose($outstream);
?>

如果没有这么多的建议,请不要再回头再看日志了。)