【问题标题】:PHP - Array to CSV by ColumnPHP - 按列将数组转换为 CSV
【发布时间】:2014-11-19 14:39:23
【问题描述】:

问题:我有一个关联数组,其中所有键代表 csv 标题,每个 $key => 数组中的值代表该列中的项目。

研究: 据我所知,fputcsv 喜欢逐行进行,但这个基于列的数组使这变得复杂。我还没有找到任何可以实现这一点的函数。

示例:

Array(
    ['fruits'] => Array(
        [0] => 'apples',
        [1] => 'oranges',
        [2] => 'bananas'
    ),
    ['meats'] => Array(
        [0] => 'porkchop',
        [1] => 'chicken',
        [2] => 'salami',
        [3] => 'rabbit'
    ),
)

需要成为:

fruits,meats
apples,porkchop
oranges,chicken
bananas,salami
,rabbit

为什么难:

您需要知道制作空白点的最大行数。

【问题讨论】:

    标签: php arrays csv fputcsv


    【解决方案1】:

    我必须编写自己的函数。想也许它可以帮助别人!

    /*
     * The array is associative, where the keys are headers
     * and the values are the items in that column.
     * 
     * Because the array is by column, this function is probably costly.
     * Consider a different layout for your array and use a better function.
     * 
     * @param $array array The array to convert to csv.
     * @param $file string of the path to write the file.
     * @param $delimeter string a character to act as glue.
     * @param $enclosure string a character to wrap around text that contains the delimeter
     * @param $escape string a character to escape the enclosure character.
     * @return mixed int|boolean result of file_put_contents.
     */
    
    function array_to_csv($array, $file, $delimeter = ',', $enclosure = '"', $escape = '\\'){
        $max_rows = get_max_array_values($array);
        $row_array = array();
        $content = '';
        foreach ($array as $header => $values) {
        $row_array[0][] = $header;
        $count = count($values);
        for ($c = 1; $c <= $count; $c++){
            $value = $values[$c - 1];
            $value = preg_replace('#"#', $escape.'"', $value);
            $put_value = (preg_match("#$delimeter#", $value)) ? $enclosure.$value.$enclosure : $value;
            $row_array[$c][] = $put_value;
        }
        // catch extra rows that need to be blank
        for (; $c <= $max_rows; $c++) {
            $row_array[$c][] = '';
        }
        }
        foreach ($row_array as $cur_row) {
        $content .= implode($delimeter,$cur_row)."\n";
        }
        return file_put_contents($file, $content);
    }
    

    还有这个:

    /*
     * Get maximum number of values in the entire array.
     */
    function get_max_array_values($array){
        $max_rows = 0;
        foreach ($array as $cur_array) {
        $cur_count = count($cur_array);
        $max_rows = ($max_rows < $cur_count) ? $cur_count : $max_rows;
        }
        return $max_rows;
    }
    

    新方式(使用类)

    稍后我为此编写了一个类,现在我会提供给任何正在寻找的人:

    class CSVService {
    
        protected $csvSyntax;
    
        public function __construct()
        {
            return $this;
        }
    
        public function renderCSV($contents, $filename = 'data.csv')
        {
            header('Content-type: text/csv');
            header('Content-Disposition: attachment; filename="' . $filename . '"');
    
            echo $contents;
        }
    
        public function CSVtoArray($filename = '', $delimiter = ',') {
            if (!file_exists($filename) || !is_readable($filename)) {
                return false;
            }
    
            $headers = null;
            $data = array();
            if (($handle = fopen($filename, 'r')) !== false) {
                while (($row = fgetcsv($handle, 0, $delimiter, '"')) !== false) {
                    if (!$headers) {
                        $headers = $row;
                        array_walk($headers, 'trim');
                        $headers = array_unique($headers);
                    } else {
                        for ($i = 0, $j = count($headers); $i < $j;  ++$i) {
                            $row[$i] = trim($row[$i]);
                            if (empty($row[$i]) && !isset($data[trim($headers[$i])])) {
                                $data[trim($headers[$i])] = array();
                            } else if (empty($row[$i])) {
                                continue;
                            } else {
                                $data[trim($headers[$i])][] = stripcslashes($row[$i]);
                            }
                        }
                    }
                }
                fclose($handle);
            }
            return $data;
        }
    
        protected function getMaxArrayValues($array)
        {
            return array_reduce($array, function($carry, $item){
                return ($carry > $c = count($item)) ? $carry : $c;
            }, 0);
        }
    
        private function getCSVHeaders($array)
        {
            return array_reduce(
                    array_keys($array),
                    function($carry, $item) {
                        return $carry . $this->prepareCSVValue($item) . $this->csvSyntax->delimiter;
                    }, '') . "\n";
        }
    
        private function prepareCSVValue($value, $delimiter = ',', $enclosure = '"', $escape = '\\')
        {
            $valueEscaped = preg_replace('#"#', $escape . '"', $value);
            return (preg_match("#$delimiter#", $valueEscaped)) ?
                    $enclosure . $valueEscaped . $enclosure : $valueEscaped;
        }
    
        private function setUpCSVSyntax($delimiter, $enclosure, $escape)
        {
            $this->csvSyntax = (object) [
                'delimiter' => $delimiter,
                'enclosure' => $enclosure,
                'escape'    => $escape,
            ];
        }
    
        private function getCSVRows($array)
        {
            $n = $this->getMaxArrayValues($array);
            $even = array_values(
                array_map(function($columnArray) use ($n) {
                    for ($i = count($columnArray); $i <= $n; $i++) {
                        $columnArray[] = '';
                    }
                    return $columnArray;
                }, $array)
            );
    
            $rowString = '';
    
            for ($row = 0; $row < $n; $row++) {
                for ($col = 0; $col < count($even); $col++) {
                    $value = $even[$col][$row];
                    $rowString .=
                            $this->prepareCSVValue($value) .
                            $this->csvSyntax->delimiter;
                }
                $rowString .= "\n";
            }
    
            return $rowString;
        }
    
        public function arrayToCSV($array, $delimiter = ',', $enclosure = '"', $escape = '\\', $headers = true) {
            $this->setUpCSVSyntax($delimiter, $enclosure, $escape);
    
            $headersString = ($headers) ? $this->getCSVHeaders($array) : '';
    
            $rowsString = $this->getCSVRows($array);
    
    
            return $headersString . $rowsString;
        }
    
    }
    

    【讨论】:

    • 完美工作。不过接受你的回答。
    • 在接受我自己的答案之前我正在等待 - 因为有人可以有更好的答案:D
    • preg_match 上都失败了。 “警告:preg_match() 期望参数 2 为字符串,给定数组 ...” 结果:col1,col2,col3\nArray,Array,Array
    • 我没遇到过这个问题……为什么第二个参数是数组?它应该是一个字符串?您的数据结构是什么?
    【解决方案2】:
    $data = array(
        'fruits' => array(
            'apples',
            'oranges',
            'bananas'
        ),
        'meats' => array(
            'porkchop',
            'chicken',
            'salami',
            'rabbit'
        ),
    );
    
    $combined = array(array('fruits', 'meats'));
    
    for($i = 0; $i < max(count($data['fruits']), count($data['meats'])); $i++)
    {   
        $row = array(); 
    
        $row[] = isset($data['fruits'][$i]) ? $data['fruits'][$i] : '';
        $row[] = isset($data['meats'][$i])  ? $data['meats'][$i]  : '';
    
        $combined[] = $row;
    }
    
    ob_start();
    
    $fp = fopen('php://output', 'w');
    
    foreach($combined as $row)
      fputcsv($fp, $row);
    
    fclose($fp);
    
    $data = ob_get_clean();
    
    var_dump($data);
    

    转换为 csv 和解析数组可以在同一个循环中完成。或者你的意思是可能有更多的列?此代码可以很容易地修改为数组的一般类型。像这样,对于任意数量的列

    $data = array(
        'fruits' => array(
            'apples',
            'oranges',
            'bananas'
        ),
        'meats' => array(
            'porkchop',
            'chicken',
            'salami',
            'rabbit'
        ),
    );
    $heads = array_keys($data);
    $maxs = array();
    foreach($heads as $head)
      $maxs[] = count($data[$head]);
    ob_start();
    $fp = fopen('php://output', 'w');
    fputcsv($fp, $heads);
    for($i = 0; $i < max($maxs); $i++)
    {   
        $row = array(); 
        foreach($heads as $head)
           $row[] = isset($data[$head][$i]) ? $data[$head][$i] : '';
        fputcsv($fp, $row);   
    }
    fclose($fp);
    $data = ob_get_clean();
    var_dump($data);
    

    【讨论】:

    • 现在的问题是,这些解决方案中的哪一个成本更低/效率更高——尤其是对于巨大的数组(很多列)。
    • @amurrell 我的代码应该可以毫无问题地处理巨大的数组,中间数据不会存储在任何地方,也没有正则表达式。可以改进的一件事是将 max() 从循环移动到单独的变量中。它只是逐行读取,用空字符串替换缺失的项目,并将每一行转换为 csv 行,仅此而已。输出缓冲只是为了将csv字符串存储到变量中,没有它可以直接保存到文件中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-07-11
    • 2013-04-27
    • 2011-04-25
    • 2011-11-22
    • 2016-03-18
    • 1970-01-01
    相关资源
    最近更新 更多