PHP CSV string to array
Asked Answered
A

10

63

I'm trying to parse a CSV string to an array in PHP. The CSV string has the following attributes:

Delimiter: ,
Enclosure: "
New line: \r\n

Example content:

"12345","Computers","Acer","4","Varta","5.93","1","0.04","27-05-2013"
"12346","Computers","Acer","5","Decra","5.94","1","0.04","27-05-2013"

When I try to parse it like this:

$url = "http://www.url-to-feed.com";
$csv = file_get_contents($url);
$data = str_getcsv($csv);
var_dump($data);

The last and first element are concatenated in one string:

[0]=> string(5) "12345"
...
[7]=> string(4) "0.04"
[8]=> string(19) "27-05-2013
"12346""

How can I fix this? Any help would be appreciated.

Amphiboly answered 20/7, 2013 at 10:18 Comment(1)
[2]=> string(48) "Acer", Acer definitely is not 48 chars long. You may want to inspect your input if it really is in proper format.Steep
K
149

Do this:

$csvData = file_get_contents($fileName);
$lines = explode(PHP_EOL, $csvData);
$array = array();
foreach ($lines as $line) {
    $array[] = str_getcsv($line);
}
print_r($array);

It will give you an output like this:

Array
(
    [0] => Array
        (
            [0] => 12345
            [1] => Computers
            [2] => Acer
            [3] => 4
            [4] => Varta
            [5] => 5.93
            [6] => 1
            [7] => 0.04
            [8] => 27-05-2013
        )

    [1] => Array
        (
            [0] => 12346
            [1] => Computers
            [2] => Acer
            [3] => 5
            [4] => Decra
            [5] => 5.94
            [6] => 1
            [7] => 0.04
            [8] => 27-05-2013
        )

)

I hope this can be of some help.

Kamerad answered 20/7, 2013 at 10:44 Comment(10)
I'm legitimately curious, but is there a reason why this would be better than Gaui's simple one-line solution below, or are people just upvoting this because it's the selected answer?Handler
Thanks, It's also working when My csv file has content with comma "Martin T," and double quotation like => Philipp "Heinzelmann"Futtock
This fails for multiline CSV fieldsDisclaimer
This can cause a rare problem, if you have a newline contained inside a value, then the PHP_EOL splits the value into 2 different array values.Tavel
After searching for an hour, your solution worked! Thank you very much. I tried the array mapping to str_getcsv, but it couldn't handle the new line characters INSIDE the field.Pricilla
fails if there is a comma in any of the fieldsJennee
Can anyone confirm if this fails if there is a comma in any of the fields as @hyphenthis noted?Donofrio
It would not fail if there's a comma inside the field, so long as the field is properly enclosed. If it's not enclosed, then the file is malformed, and no CSV-reader will parse it properly. But it will fail if there is a newline inside a field, as noted by @SebastianMach.Rejuvenate
My answer below will NOT fail on newline inside the field.Berkly
Use fgetcsv() only! Because explode(PHP_EOL, $csvData) don't parse line breaks in csv strings correctly! See the right answer below.Deepdyed
B
63

You should use fgetcsv. Since you cannot import a file as a stream because the csv is a variable, then you should spoof the string as a file by using php://temp or php://memory first:

$fp = fopen("php://temp", 'r+');
fputs($fp, $csvText);
rewind($fp);

Then you will have no problem using fgetcsv:

$csv = [];
while ( ($data = fgetcsv($fp) ) !== FALSE ) {
    $csv[] = $data;
}
fclose($fp)

$data will be an array of a single csv line (which may include line breaks or commas, etc), as it should be.

Caveat: The memory limit of php://temp can be controlled by appending /maxmemory:NN, where NN is the maximum amount of data to keep in memory before using a temporary file, in bytes. (the default is 2 MB) http://www.php.net/manual/en/wrappers.php.php

Berkly answered 9/6, 2017 at 3:43 Comment(7)
This is one of the few answers that actually properly handles newlines inside of fields.. it should have far more upvotes.Rejuvenate
This is the best way! Don't use file('data.csv') or explode(PHP_EOL, $csv) because they don't parse line breakes in csv strings correctly!Deepdyed
How does it distinguish between a newline inside a field and a newline constituting line/record delimiter?Lap
I'm guessing it does this by figuring out if the line break is wrapped in the enclosure (usually ") and treating it appropriately.Electrical
This is the more proper way, the accepted answer is a quick way but doesn't cover multi lines in the fieldsPirozzo
The memory limit of php://temp can be controlled by appending /maxmemory:NN, where NN is the maximum amount of data to keep in memory before using a temporary file, in bytes. (the default is 2 MB) php.net/manual/en/wrappers.php.php - thanks, steven!Berkly
I think fclose($fp) should be added at the end.Industrial
N
36

Handy oneliner:

$csv = array_map('str_getcsv', file('data.csv'));
Natalya answered 10/12, 2014 at 20:44 Comment(8)
Fails for multiline CSV fields, doesn't it?Disclaimer
Yes probably, because file() would mistake it for a new CSV row.Natalya
Won't be able to handle new lines inside fields. The accepted answer solves for that with PHP_EOL. Good if you don't have new lines I suppose. Thanks for your effort.Pricilla
Should credit the author of this handy one liner: php.net/manual/en/function.str-getcsv.php (see first comment)Klepht
@WebmasterG, using PHP_EOL will not solve for the newlines inside of fields issue. The accepted answer fails with this just as if \n was used instead. PHP_EOL is actually the wrong solution here, because it will use the EOL character of the platform running PHP, not of the platform that built the CSV file.Rejuvenate
Use fgetcsv() only! Because file('data.csv') don't parse line breaks in csv strings correctly! See the right answer below.Deepdyed
This is perfect if the csv file is comma separated, but what if the file is semicolon ";" separated?? I use this code and works fine, but just now I have a semicolon separated file and it's not working.Lasky
@Lasky in the documentation of the method secure.php.net/manual/en/function.str-getcsv.php there are details on how to change the delimiter (to use semicolon instead of commas, for instance), as well as the enclosure and escape. So the method is equally valid, only that you need to dig a bit more and read the documentation. PHP documentation is quite nicely written, and has tones of examples.Erek
S
10

I have used the following function to parse csv string to an associative array

public function csvToArray($file) {
    $rows = array();
    $headers = array();
    if (file_exists($file) && is_readable($file)) {
        $handle = fopen($file, 'r');
        while (!feof($handle)) {
            $row = fgetcsv($handle, 10240, ',', '"');
            if (empty($headers))
                $headers = $row;
            else if (is_array($row)) {
                array_splice($row, count($headers));
                $rows[] = array_combine($headers, $row);
            }
        }
        fclose($handle);
    } else {
        throw new Exception($file . ' doesn`t exist or is not readable.');
    }
    return $rows;
}

if your csv file name is mycsv.csv then you call this function as:

$dataArray = csvToArray("mycsv.csv");

you can get this script also in http://www.scriptville.in/parse-csv-data-to-array/

Stanwinn answered 22/9, 2014 at 15:27 Comment(1)
This is the best solution so far! Since I am using Excel CSV, I have changed this line $row = fgetcsv($handle, 10240, ',', '"'); to $row = fgetcsv($handle, 10240, ';', '"');Respondent
R
3

Slightly shorter version, without unnecessary second variable:

$csv = <<<'ENDLIST'
"12345","Computers","Acer","4","Varta","5.93","1","0.04","27-05-2013"
"12346","Computers","Acer","5","Decra","5.94","1","0.04","27-05-2013"
ENDLIST;

$arr = explode("\n", $csv);
foreach ($arr as &$line) {
  $line = str_getcsv($line);
}
Restitution answered 1/12, 2014 at 19:53 Comment(1)
Fails for multiline fields.Disclaimer
C
3

A modification of previous answers using array_map.
Blow up the CSV data with multiple lines.

$csv = array_map('str_getcsv', explode("\n", $csvData));
Conoscenti answered 11/5, 2018 at 23:8 Comment(0)
A
1

Try this, it's working for me:

$delimiter = ",";
  $enclosure = '"';
  $escape = "\\" ;
  $rows = array_filter(explode(PHP_EOL, $content));
  $header = NULL;
  $data = [];

  foreach($rows as $row)
  {
    $row = str_getcsv ($row, $delimiter, $enclosure , $escape);

    if(!$header) {
      $header = $row;
    } else {
      $data[] = array_combine($header, $row);
    }
  }
Aggarwal answered 9/4, 2020 at 14:54 Comment(1)
array_combine(): Both parameters should have an equal number of elements, how can I correct and checking if I have the same quantity, they are 2 associative arraysAchlamydeous
V
0

If you need a name for the csv columns, you can use this method

 $example= array_map(function($v) {$column = str_getcsv($v, ";");return array("foo" => $column[0],"bar" => $column[1]);},file('file.csv'));
Venessavenetia answered 24/9, 2017 at 10:41 Comment(0)
D
0

If you have carriage return/line feeds within columns, str_getcsv will not work.

Try https://github.com/synappnz/php-csv

Use:

include "csv.php";
$csv = new csv(file_get_contents("filename.csv"));
$rows = $csv->rows();
foreach ($rows as $row)
{
  // do something with $row
}
Deviation answered 4/10, 2018 at 10:20 Comment(0)
L
0

It`s my written flexible solution (Reference), and will work for all separated values strings, concerning the new lines in the fields too...

function ToCells($svString, $delimiter = ',', $enclosure = '"', $eol = "\n") :array {
    $rows = [];
    $length = strlen($svString);
    $index = 0;
    while ($index < $length) {
        $row = [];
        $column = '';
        $inEnclosure = false;
        do {
            $char = $svString[$index++];
            if ($inEnclosure) {
                if ($char == $enclosure) {
                    if ($index < $length) {
                        $char = $svString[$index];
                        if ($char == $enclosure) {
                            $column .= $char;
                            $index++;
                        } else { $inEnclosure = false; }
                    } else {
                        $inEnclosure = false;
                        $row[] = $column;
                        break;
                    }
                } else { $column .= $char; }
            } else if ($char == $enclosure) {
                if($index < $length) {
                    $char = $svString[$index++];
                    if ($char == $enclosure) { $column .= $char; }
                    else {
                        $inEnclosure = true;
                        $column .= $char;
                    }
                } else {
                    $row[] = $column;
                    break;
                }
            } else if ($char == $delimiter) {
                $row[] = $column;
                $column = '';
            } else if ($char == "\r") {
                if ($index < $length) {
                    $char = $svString[$index];
                    if ($char == $eol) { $index++; }
                }
                $row[] = $column;
                break;
            } else if ($char == $eol) {
                $row[] = $column;
                break;
            } else { $column .= $char; }

            if ($index == $length) {
                $row[] = $column;
                break;
            }
        } while ($index < $length);
        $rows[] = $row;
    }
    return $rows;
}

Just call the function and enjoy:

ToCells($csvString);

Enjoy...

Lindyline answered 15/7 at 6:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.