Forcing fputcsv to Use Enclosure For *all* Fields
Asked Answered
G

6

46

When I use fputcsv to write out a line to an open file handle, PHP will add an enclosing character to any column that it believes needs it, but will leave other columns without the enclosures.

For example, you might end up with a line like this

11,"Bob ",Jenkins,"200 main st. USA ",etc

Short of appending a bogus space to the end of every field, is there any way to force fputcsv to always enclose columns with the enclosure (defaults to a ") character?

Groveman answered 22/3, 2010 at 1:30 Comment(3)
Just curious, as my guess is that you're question was in:re to Magento/Varien_Io_File::streamWriteCsv() (which ultimately just uses fputcsv), did you ever find a good solution for this? Possibly using Varien_File_Csv?Goldstein
@Goldstein Never did, (and this was four+ years ago, but I don't think it was specifically Magento related)Groveman
Take a look at this custom fputcsv implementation that should fit your needs: https://mcmap.net/q/152578/-alternative-to-fputcsvEncomiast
A
35

No, fputcsv() only encloses the field under the following conditions

/* enclose a field that contains a delimiter, an enclosure character, or a newline */
if (FPUTCSV_FLD_CHK(delimiter) ||
  FPUTCSV_FLD_CHK(enclosure) ||
  FPUTCSV_FLD_CHK(escape_char) ||
  FPUTCSV_FLD_CHK('\n') ||
  FPUTCSV_FLD_CHK('\r') ||
  FPUTCSV_FLD_CHK('\t') ||
  FPUTCSV_FLD_CHK(' ')
)

There is no "always enclose" option.

Ampliate answered 22/3, 2010 at 7:16 Comment(4)
sad, because fgetcsv fails with Österreich without double quotes!Monachism
VolkerK is, of course quite right. fputcsv cannot help you with this problem (I have the same issue). Let me help you get a jump start on the next search you will be doing: https://mcmap.net/q/153125/-convert-array-into-csvSandglass
where did you find this? What is your source?Amputee
@Amputee It's from the php source code, in /ext/standard/file.c php_fputcsvAmu
S
22

Building on Martin's answer, if you want to avoid inserting any characters that don't stem from the source array (Chr(127), Chr(0), etc), you can replace the fputcsv() line with the following instead:

fputs($fp, implode(",", array_map("encodeFunc", $row))."\r\n");

Granted, fputs() is slower than fputcsv(), but it's a cleaner output. The complete code is thus:

/***
 * @param $value array
 * @return string array values enclosed in quotes every time.
 */
function encodeFunc($value) {
    ///remove any ESCAPED double quotes within string.
    $value = str_replace('\\"','"',$value);
    //then force escape these same double quotes And Any UNESCAPED Ones.
    $value = str_replace('"','\"',$value);
    //force wrap value in quotes and return
    return '"'.$value.'"';
}

$fp = fopen("filename.csv", 'w');
foreach($table as $row){
    fputs($fp, implode(",", array_map("encodeFunc", $row))."\r\n");
}
fclose($fp);
Sustain answered 6/12, 2016 at 11:21 Comment(2)
I did actually find after posting this answer that with fputcsv that the function really is pretty lame and using standard write-to-file functions as you've also done sorted my code out and sidestepped all issues with CSV consistency across platforms and programs.Bugbane
Thank you for the good example. I needed to always enclose all values in quotes. And, I needed to force the line endings to CR+LF instead of just LF. This example solves both problems.Discourse
D
21

Not happy with this solution but it is what I did and worked. The idea is to set an empty char as enclosure character on fputcsv and add some quotes on every element of your array.

function encodeFunc($value) {
    return "\"$value\"";
}

fputcsv($handler, array_map(encodeFunc, $array), ',', chr(0));
Drucill answered 23/1, 2014 at 23:7 Comment(4)
Including chr(0) in the file causes issues in some import scripts if the file is UTF-8Nonsuit
the csv contain ^@ if you are using chr(0)Selfabnegation
You can get rid of the nulls (chr(0)) in the resulting file by filtering them after you are done outputing your csv: file_put_contents($file, str_replace(chr(0), '', file_get_contents($file)));. Also your encodeFunc will need to escape double quotes as well if your values have them. See my revision here. Other than that it seems to work, just keep in mind it's a hack.Mapes
don't forget to escape quotes in data value itself. as said in the RFC 4180 standard, when the enclosure is a ", the escape character is the " as well (escape character = enclosure). So the $value in the loop should be manipulated like this: $value = str_replace('"', '""', $value);Vite
B
4

After a lot of scrafffing around and some somewhat tedious character checking, I have a version of the above referenced codes by Diego and Mahn that will correctly strip out encasings and replace with double quotes on all fields in fputcsv. and then output the file to the browser to download.

I also had a secondary issue of not being able to be sure that double quotes were always / never escaped.

Specifically for when outputting directly to browser using the php://input stream as referenced by Diego. Chr(127) is a space character so the CSV file has a few more spaces than otherwise but I believe this sidesteps the issue of chr(0) NULL characters in UTF-8.

/***
 * @param $value array
 * @return string array values enclosed in quotes every time.
 */
function encodeFunc($value) {
    ///remove any ESCAPED double quotes within string.
    $value = str_replace('\\"','"',$value);
    //then force escape these same double quotes And Any UNESCAPED Ones.
    $value = str_replace('"','\"',$value);
    //force wrap value in quotes and return
    return '"'.$value.'"';
}


$result = $array_Set_Of_DataBase_Results;
$fp = fopen('php://output', 'w');
if ($fp && $result) {
    header('Content-Type: text/csv');
    header('Content-Disposition: attachment; filename="export-'.date("d-m-Y").'.csv"');
    foreach($result as $row) {
        fputcsv($fp, array_map("encodeFunc", $row), ',', chr(127));
    }
    unset($result,$row);
    die;
}

I hope this is useful for some one.

Bugbane answered 30/11, 2016 at 16:46 Comment(0)
E
0

A function to auto create and download in browser a csv while forcing to enclose all or only specified columns

It uses a string, that is quite unlikely to existing in the dataset, which contains at least one space and this forces the the enclosure.

After fputsv does it's job you retrieve the file and replace the forcing string with empty character. This cannot work in large streams of data unless you modify it but for most applications it's should be sufficient

Just input the columns you want to force enlcose or ['*'] to enclose everything and in case for some strange reason the forcing string is already included in your data you can pass a different non existing string to force enclose

function csvDownload(
        array   $array,
        string  $filename = "export.csv",
        ?string $separator = ",",
        ?string $enclosure = '"',
        ?string $escape = "\\",
        ?array  $forceEnclose = [],
        string  $forceString = '{ || force enclosing || }'
    ): void
    {
        $tempFile = fopen('php://memory', 'w');
        foreach ($array as $line) {
            fputcsv(
                $tempFile,
                array_map(
                    function ($value, $key) use ($forceEnclose, $forceString) {
                        if (
                            in_array($key, $forceEnclose, true) ||
                            in_array('*', $forceEnclose, true)
                        ) {
                            $value .= $forceString;
                        }
                        return $value;
                    },
                    $line,
                    array_keys($line)
                ),
                $separator,
                $enclosure,
                $escape
            );
        }
        rewind($tempFile);
        header('Content-Type: text/csv');
        header('Content-Disposition: attachment; filename="' . $filename . '";');
        echo str_replace(
            $forceString,
            '',
            stream_get_contents($tempFile)
        );
        exit;
    }
Experience answered 2/8, 2023 at 7:2 Comment(0)
N
-1

A "quick and dirty" solution is to add ' ' at the end of all of your fields, if it's acceptable for you:

fputcsv($handle, array_map(fn($v) => $v.' ', $fields));

PS: why it's working? see Volkerk answer ;)

Nigrify answered 11/11, 2020 at 12:36 Comment(2)
that's not a solution, it changes the content of each field.Cq
that's why it's called a "quick and dirty" solutionMusquash

© 2022 - 2024 — McMap. All rights reserved.