PHPのお勉強!

PHP TOP

str_getcsv

(PHP 5 >= 5.3.0, PHP 7, PHP 8)

str_getcsv CSV 文字列をパースして配列に格納する

説明

str_getcsv(
    string $string,
    string $separator = ",",
    string $enclosure = "\"",
    string $escape = "\\"
): array

CSV 形式の文字列入力のフィールドをパースして、 読み込んだフィールドの内容を配列で返します。

注意:

この関数はロケール設定を考慮します。もし LC_CTYPE が例えば en_US.UTF-8 の場合、 1 バイトエンコーディングの文字列は間違って読み込まれるかもしれません。

パラメータ

string

パースする文字列。

separator

フィールド区切り文字 (シングルバイト文字 1 文字のみ)。

enclosure

フィールド囲み文字 (シングルバイト文字 1 文字のみ)。

escape

エスケープ文字 (シングルバイト文字 最大1文字)。デフォルトはバックスラッシュ (\)。 空文字列 ("") を指定すると、 (RFC 4180 に準拠していない) 独自仕様のエスケープ機構を無効にします。

注意: 通常、 enclosure の文字は、 フィールドの中では二回出力されることでエスケープされます。 しかし、escape の文字を代わりに使うこともできます。 よって、デフォルト値 "" および \" は同じ意味になります。 enclosure 文字をエスケープすることを許可する以外に、 escape 文字は特別な意味を何ら持ちません; つまり、自分自身をエスケープすることすら意味しません。

警告

escape が空の文字列("")以外に設定されているとき、 » RFC 4180 に準拠しない CSV が生成されたり、PHP の CSV 関数を介してラウンドトリップ(往復変換)でデータが壊れる可能性があります。 escapeのデフォルト値は"\\" なので、明示的に空の文字列を指定することを推奨します。デフォルト値は、PHP 9.0 以降の将来のバージョンで変更予定です。

戻り値

読み込んだフィールドの内容を配列で返します。

変更履歴

バージョン 説明
7.4.0 escape 引数は、 空文字列を、(RFC 4180 に準拠していない) 独自仕様のエスケープ機構を無効にするシグナルとして解釈するようになりました。 これより前のバージョンでは、空文字列はデフォルト値のように扱われていました。

例1 str_getcsv() の例

<?php

$string
= 'PHP,Java,Python,Kotlin,Swift';
$data = str_getcsv($string);

var_dump($data);
?>

上の例の出力は以下となります。

array(5) {
  [0]=>
  string(3) "PHP"
  [1]=>
  string(4) "Java"
  [2]=>
  string(6) "Python"
  [3]=>
  string(6) "Kotlin"
  [4]=>
  string(5) "Swift"
}

例2 str_getcsv() に空文字列を指定した例

警告

この関数に空文字列を指定すると、 空の配列ではなく 値 [null] を返します。

<?php

$string
= '';
$data = str_getcsv($string);

var_dump($data);
?>

上の例の出力は以下となります。

array(1) {
  [0]=>
  NULL
}

参考

  • fgetcsv() - ファイルポインタから行を取得し、CSVフィールドを処理する

add a note

User Contributed Notes 30 notes

up
500
james at moss dot io
10 years ago
[Editor's Note (cmb): that does not produce the desired results, if fields contain linebreaks.]

Handy one liner to parse a CSV file into an array

<?php

$csv
= array_map('str_getcsv', file('data.csv'));

?>
up
185
starrychloe at oliveyou dot net
9 years ago
Based on James' line, this will create an array of associative arrays with the first row column headers as the keys.

<?php
$csv
= array_map('str_getcsv', file($file));
array_walk($csv, function(&$a) use ($csv) {
$a = array_combine($csv[0], $a);
});
array_shift($csv); # remove column header
?>

This will yield something like
[2] => Array
(
[Campaign ID] => 295095038
[Ad group ID] => 22460178158
[Keyword ID] => 3993587178
up
142
durik at 3ilab dot net
13 years ago
As the str_getcsv(), unlike to fgetcsv(), does not parse the rows in CSV string, I have found following easy workaround:

<?php
$Data
= str_getcsv($CsvString, "\n"); //parse the rows
foreach($Data as &$Row) $Row = str_getcsv($Row, ";"); //parse the items in rows
?>

Why not use explode() instead of str_getcsv() to parse rows? Because explode() would not treat possible enclosured parts of string or escaped characters correctly.
up
13
sven at e7o dot de
9 years ago
PHP is failing when parsing UTF-8 with Byte Order Mark. Strip it with this one from string before passing it to csv parser:

<?php
$bom
= pack('CCC', 0xEF, 0xBB, 0xBF);
if (
strncmp($yourString, $bom, 3) === 0) {
$body = substr($yourString, 3);
}
?>
up
43
normadize -a- gmail -d- com
11 years ago
Like some other users here noted, str_getcsv() cannot be used if you want to comply with either the RFC or with most spreadsheet tools like Excel or Google Docs.

These tools do not escape commas or new lines, but instead place double-quotes (") around the field. If there are any double-quotes in the field, these are escaped with another double-quote (" becomes ""). All this may look odd, but it is what the RFC and most tools do ...

For instance, try exporting as .csv a Google Docs spreadsheet (File > Download as > .csv) which has new lines and commas as part of the field values and see how the .csv content looks, then try to parse it using str_getcsv() ... it will spectacularly regardless of the arguments you pass to it.

Here is a function that can handle everything correctly, and more:

- doesn't use any for or while loops,
- it allows for any separator (any string of any length),
- option to skip empty lines,
- option to trim fields,
- can handle UTF8 data too (although .csv files are likely non-unicode).

Here is the more human readable version of the function:

<?php

// returns a two-dimensional array or rows and fields

function parse_csv ($csv_string, $delimiter = ",", $skip_empty_lines = true, $trim_fields = true)
{
$enc = preg_replace('/(?<!")""/', '!!Q!!', $csv_string);
$enc = preg_replace_callback(
'/"(.*?)"/s',
function (
$field) {
return
urlencode(utf8_encode($field[1]));
},
$enc
);
$lines = preg_split($skip_empty_lines ? ($trim_fields ? '/( *\R)+/s' : '/\R+/s') : '/\R/s', $enc);
return
array_map(
function (
$line) use ($delimiter, $trim_fields) {
$fields = $trim_fields ? array_map('trim', explode($delimiter, $line)) : explode($delimiter, $line);
return
array_map(
function (
$field) {
return
str_replace('!!Q!!', '"', utf8_decode(urldecode($field)));
},
$fields
);
},
$lines
);
}

?>

Since this is not using any loops, you can actually write it as a one-line statement (one-liner).

Here's the function using just one line of code for the function body, formatted nicely though:

<?php

// returns the same two-dimensional array as above, but with a one-liner code

function parse_csv ($csv_string, $delimiter = ",", $skip_empty_lines = true, $trim_fields = true)
{
return
array_map(
function (
$line) use ($delimiter, $trim_fields) {
return
array_map(
function (
$field) {
return
str_replace('!!Q!!', '"', utf8_decode(urldecode($field)));
},
$trim_fields ? array_map('trim', explode($delimiter, $line)) : explode($delimiter, $line)
);
},
preg_split(
$skip_empty_lines ? ($trim_fields ? '/( *\R)+/s' : '/\R+/s') : '/\R/s',
preg_replace_callback(
'/"(.*?)"/s',
function (
$field) {
return
urlencode(utf8_encode($field[1]));
},
$enc = preg_replace('/(?<!")""/', '!!Q!!', $csv_string)
)
)
);
}

?>

Replace !!Q!! with another placeholder if you wish.

Have fun.
up
85
Jay Williams
14 years ago
Here is a quick and easy way to convert a CSV file to an associated array:

<?php
/**
* @link http://gist.github.com/385876
*/
function csv_to_array($filename='', $delimiter=',')
{
if(!
file_exists($filename) || !is_readable($filename))
return
FALSE;

$header = NULL;
$data = array();
if ((
$handle = fopen($filename, 'r')) !== FALSE)
{
while ((
$row = fgetcsv($handle, 1000, $delimiter)) !== FALSE)
{
if(!
$header)
$header = $row;
else
$data[] = array_combine($header, $row);
}
fclose($handle);
}
return
$data;
}

?>
up
17
dejiakala at gmail dot com
10 years ago
I wanted the best of the 2 solutions by james at moss dot io and Jay Williams (csv_to_array()) - create associative array from a CSV file with a header row.

<?php

$array
= array_map('str_getcsv', file('data.csv'));

$header = array_shift($array);

array_walk($array, '_combine_array', $header);

function
_combine_array(&$row, $key, $header) {
$row = array_combine($header, $row);
}

?>

Then I thought why not try some benchmarking? I grabbed a sample CSV file with 50,000 rows (10 columns each) and Vulcan Logic Disassembler (VLD) which hooks into the Zend Engine and dumps all the opcodes (execution units) of a script - see http://pecl.php.net/package/vld and example here: http://fabien.potencier.org/article/8/print-vs-echo-which-one-is-faster

Result:

array_walk() and array_map() - 39 opcodes
csv_to_array() - 69 opcodes
up
3
daniel dot oconnor at gmail dot com
15 years ago
Don't have this? Ask fgetcsv() to do it for you.

5.1.0+

<?php
if (!function_exists('str_getcsv')) {
function
str_getcsv($input, $delimiter = ",", $enclosure = '"', $escape = "\\") {
$fiveMBs = 5 * 1024 * 1024;
$fp = fopen("php://temp/maxmemory:$fiveMBs", 'r+');
fputs($fp, $input);
rewind($fp);

$data = fgetcsv($fp, 1000, $delimiter, $enclosure); // $escape only got added in 5.3.0

fclose($fp);
return
$data;
}
}
?>
up
25
Ryan Rubley
11 years ago
@normadize - that is a nice start, but it fails on situations where a field is empty but quoted (returning a string with one double quote instead of an empty string) and cases like """""foo""""" that should result in ""foo"" but instead return "foo". I also get a row with 1 empty field at the end because of the final CRLF in the CSV. Plus, I don't really like the !!Q!! magic or urlencoding to get around things. Also, \R doesn't work in pcre on any of my php installations.

Here is my take on this, without anonymous functions (so it works on PHP < 5.3), and without your options (because I believe the only correct way to parse according to the RFC would be $skip_empty_lines = false and $trim_fields = false).

//parse a CSV file into a two-dimensional array
//this seems as simple as splitting a string by lines and commas, but this only works if tricks are performed
//to ensure that you do NOT split on lines and commas that are inside of double quotes.
function parse_csv($str)
{
//match all the non-quoted text and one series of quoted text (or the end of the string)
//each group of matches will be parsed with the callback, with $matches[1] containing all the non-quoted text,
//and $matches[3] containing everything inside the quotes
$str = preg_replace_callback('/([^"]*)("((""|[^"])*)"|$)/s', 'parse_csv_quotes', $str);

//remove the very last newline to prevent a 0-field array for the last line
$str = preg_replace('/\n$/', '', $str);

//split on LF and parse each line with a callback
return array_map('parse_csv_line', explode("\n", $str));
}

//replace all the csv-special characters inside double quotes with markers using an escape sequence
function parse_csv_quotes($matches)
{
//anything inside the quotes that might be used to split the string into lines and fields later,
//needs to be quoted. The only character we can guarantee as safe to use, because it will never appear in the unquoted text, is a CR
//So we're going to use CR as a marker to make escape sequences for CR, LF, Quotes, and Commas.
$str = str_replace("\r", "\rR", $matches[3]);
$str = str_replace("\n", "\rN", $str);
$str = str_replace('""', "\rQ", $str);
$str = str_replace(',', "\rC", $str);

//The unquoted text is where commas and newlines are allowed, and where the splits will happen
//We're going to remove all CRs from the unquoted text, by normalizing all line endings to just LF
//This ensures us that the only place CR is used, is as the escape sequences for quoted text
return preg_replace('/\r\n?/', "\n", $matches[1]) . $str;
}

//split on comma and parse each field with a callback
function parse_csv_line($line)
{
return array_map('parse_csv_field', explode(',', $line));
}

//restore any csv-special characters that are part of the data
function parse_csv_field($field) {
$field = str_replace("\rC", ',', $field);
$field = str_replace("\rQ", '"', $field);
$field = str_replace("\rN", "\n", $field);
$field = str_replace("\rR", "\r", $field);
return $field;
}
up
3
V.Krishn
11 years ago
<?php
Note
: The function trims all values unlike str_getcsv (v5.3).
/**
* @link https://github.com/insteps/phputils (for updated code)
* Parse a CSV string into an array for php 4+.
* @param string $input String
* @param string $delimiter String
* @param string $enclosure String
* @return array
*/
function str_getcsv4($input, $delimiter = ',', $enclosure = '"') {

if( !
preg_match("/[$enclosure]/", $input) ) {
return (array)
preg_replace(array("/^\\s*/", "/\\s*$/"), '', explode($delimiter, $input));
}

$token = "##"; $token2 = "::";
//alternate tokens "\034\034", "\035\035", "%%";
$t1 = preg_replace(array("/\\\[$enclosure]/", "/$enclosure{2}/",
"/[$enclosure]\\s*[$delimiter]\\s*[$enclosure]\\s*/", "/\\s*[$enclosure]\\s*/"),
array(
$token2, $token2, $token, $token), trim(trim(trim($input), $enclosure)));

$a = explode($token, $t1);
foreach(
$a as $k=>$v) {
if (
preg_match("/^{$delimiter}/", $v) || preg_match("/{$delimiter}$/", $v) ) {
$a[$k] = trim($v, $delimiter); $a[$k] = preg_replace("/$delimiter/", "$token", $a[$k]); }
}
$a = explode($token, implode($token, $a));
return (array)
preg_replace(array("/^\\s/", "/\\s$/", "/$token2/"), array('', '', $enclosure), $a);

}

if ( !
function_exists('str_getcsv')) {
function
str_getcsv($input, $delimiter = ',', $enclosure = '"') {
return
str_getcsv4($input, $delimiter, $enclosure);
}
}
?>
up
7
Jeremy
15 years ago
After using several methods in the past to create CSV strings without using files (disk IO sucks), I finally decided it's time to write a function to handle it all. This function could use some cleanup, and the variable type test might be overkill for what is needed, I haven't thought about it too much.

Also, I took the liberty of replacing fields with certain data types with strings which I find much easier to work with. Some of you may not agree with those. Also, please note that the type "double" or float has been coded specifically for two digit precision because if I am using a float, it's most likely for currency.

I am sure some of you out there would appreciate this function.

<?php
function str_putcsv($array, $delimiter = ',', $enclosure = '"', $terminator = "\n") {
# First convert associative array to numeric indexed array
foreach ($array as $key => $value) $workArray[] = $value;

$returnString = ''; # Initialize return string
$arraySize = count($workArray); # Get size of array

for ($i=0; $i<$arraySize; $i++) {
# Nested array, process nest item
if (is_array($workArray[$i])) {
$returnString .= str_putcsv($workArray[$i], $delimiter, $enclosure, $terminator);
} else {
switch (
gettype($workArray[$i])) {
# Manually set some strings
case "NULL": $_spFormat = ''; break;
case
"boolean": $_spFormat = ($workArray[$i] == true) ? 'true': 'false'; break;
# Make sure sprintf has a good datatype to work with
case "integer": $_spFormat = '%i'; break;
case
"double": $_spFormat = '%0.2f'; break;
case
"string": $_spFormat = '%s'; break;
# Unknown or invalid items for a csv - note: the datatype of array is already handled above, assuming the data is nested
case "object":
case
"resource":
default:
$_spFormat = ''; break;
}
$returnString .= sprintf('%2$s'.$_spFormat.'%2$s', $workArray[$i], $enclosure);
$returnString .= ($i < ($arraySize-1)) ? $delimiter : $terminator;
}
}
# Done the workload, return the output information
return $returnString;
}

?>
up
3
keananda at gmail dot com
16 years ago
For those who need this function but not yet installed in their environment, you can use my function bellow.

You can parse your csv file into an associative array (by default) for each lines, or into an object.
<?php
function parse_csv($file, $options = null) {
$delimiter = empty($options['delimiter']) ? "," : $options['delimiter'];
$to_object = empty($options['to_object']) ? false : true;
$str = file_get_contents($file);
$lines = explode("\n", $str);
pr($lines);
$field_names = explode($delimiter, array_shift($lines));
foreach (
$lines as $line) {
// Skip the empty line
if (empty($line)) continue;
$fields = explode($delimiter, $line);
$_res = $to_object ? new stdClass : array();
foreach (
$field_names as $key => $f) {
if (
$to_object) {
$_res->{$f} = $fields[$key];
} else {
$_res[$f] = $fields[$key];
}
}
$res[] = $_res;
}
return
$res;
}
?>

NOTE:
Line number 1 of the csv file will be considered as header (field names).

TODO:
- Enclosure handling
- Escape character handling
- Other features/enhancements as you need

EXAMPLE USE:
Content of /path/to/file.csv:
CODE,COUNTRY
AD,Andorra
AE,United Arab Emirates
AF,Afghanistan
AG,Antigua and Barbuda

<?php
$arr_csv
= parse_csv("/path/to/file.csv");
print_r($arr_csv);
?>
// Output:
Array
(
[0] => Array
(
[CODE] => AD
[COUNTRY] => Andorra
)
[1] => Array
(
[CODE] => AE
[COUNTRY] => United Arab Emirates
)
[2] => Array
(
[CODE] => AF
[COUNTRY] => Afghanistan
)
[3] => Array
(
[CODE] => AG
[COUNTRY] => Antigua and Barbuda
)
)

<?php
$obj_csv
= parse_csv("/path/to/file.csv", array("to_object" => true));
print_r($obj_csv);
?>
// Output:
Array
(
[0] => stdClass Object
(
[CODE] => AD
[COUNTRY] => Andorra
)
[1] => stdClass Object
(
[CODE] => AE
[COUNTRY] => United Arab Emirates
)
[2] => stdClass Object
(
[CODE] => AF
[COUNTRY] => Afghanistan
)
[3] => stdClass Object
(
[CODE] => AG
[COUNTRY] => Antigua and Barbuda
)
[4] => stdClass Object
(
[CODE] =>
[COUNTRY] =>
)
)

// If you use character | (pipe) as delimiter in your csv file, use:
<?php
$arr_csv
= parse_csv("/path/to/file.csv", array("delimiter"=>"|"));
?>

==NSD==
up
7
hpartidas at deuz dot net
14 years ago
I found myself wanting to parse a CSV and didn't have access to str_getcsv, so I wrote substitute for PHP < 5.3, hope it helps someone out there stuck in the same situation.

<?php
if (!function_exists('str_getcsv')) {
function
str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
if (
is_string($input) && !empty($input)) {
$output = array();
$tmp = preg_split("/".$eol."/",$input);
if (
is_array($tmp) && !empty($tmp)) {
while (list(
$line_num, $line) = each($tmp)) {
if (
preg_match("/".$escape.$enclosure."/",$line)) {
while (
$strlen = strlen($line)) {
$pos_delimiter = strpos($line,$delimiter);
$pos_enclosure_start = strpos($line,$enclosure);
if (
is_int($pos_delimiter) && is_int($pos_enclosure_start)
&& (
$pos_enclosure_start < $pos_delimiter)
) {
$enclosed_str = substr($line,1);
$pos_enclosure_end = strpos($enclosed_str,$enclosure);
$enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
$output[$line_num][] = $enclosed_str;
$offset = $pos_enclosure_end+3;
} else {
if (empty(
$pos_delimiter) && empty($pos_enclosure_start)) {
$output[$line_num][] = substr($line,0);
$offset = strlen($line);
} else {
$output[$line_num][] = substr($line,0,$pos_delimiter);
$offset = (
!empty(
$pos_enclosure_start)
&& (
$pos_enclosure_start < $pos_delimiter)
)
?
$pos_enclosure_start
:$pos_delimiter+1;
}
}
$line = substr($line,$offset);
}
} else {
$line = preg_split("/".$delimiter."/",$line);

/*
* Validating against pesky extra line breaks creating false rows.
*/
if (is_array($line) && !empty($line[0])) {
$output[$line_num] = $line;
}
}
}
return
$output;
} else {
return
false;
}
} else {
return
false;
}
}
}
?>