Help us understand the problem. What is going on with this article?

PHPでCSVとTSV

More than 1 year has passed since last update.

最近はエクセルばかりでCSVは使っていませんでした。
今まではfgetcsvを使って読み込んでいましたが、久しぶりに使おうと調べてみたらSplFileObjectSplTempFileObjectを使うと圧倒的に楽ということを知りましたのでメモ。
(通常のファイル作成にも使えます)

CSV

読み込み

read.csv
headA,headB,headC
dataA,"data,B","data""C"
$read_file_path = __DIR__.'/read.csv';
$file = new \SplFileObject($read_file_path, 'r');
$file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY | \SplFileObject::READ_AHEAD);// 空行無視はSKIP_EMPTYとREAD_AHEADを共に設定する

foreach ($file as $row)
{
    var_dump($row);
}

dd($file);// Laravelの出力用ヘルパ関数

出力結果

array(3) { [0]=> string(8) "headA" [1]=> string(5) "headB" [2]=> string(5) "headC" }
array(3) { [0]=> string(5) "dataA" [1]=> string(6) "data,B" [2]=> string(6) "data"C" }

SplFileObject {#63632 ▼
  path: "/home/public/app/app/Http/Controllers"
  filename: "read.csv"
  basename: "read.csv"
  pathname: "/home/public/app/app/Http/Controllers/read.csv"
  extension: "csv"
  realPath: "/home/public/app/app/Http/Controllers/read.csv"
  aTime: 2018-02-21 20:15:37
  mTime: 2018-02-21 20:15:37
  cTime: 2018-02-21 20:15:37
  inode: 4407
  size: 48
  perms: 0100777
  owner: 1000
  group: 50
  type: "file"
  writable: true
  readable: true
  executable: true
  file: true
  dir: false
  link: false
  csvControl: array:3 [▶]
  flags: READ_AHEAD|SKIP_EMPTY|READ_CSV
  maxLineLen: 0
  fstat: array:26 [▶]
  eof: false
  key: 0
}

書き込み

$save_file_path = __DIR__.'/save.csv';
$file = new \SplFileObject($save_file_path, 'w');// ファイルは作成してくれます。

$data = [];
$data[] = ['headA', 'headB', 'headC'];
$data[] = ['dataA', 'data,B', 'data"C'];

foreach ($data as $row) {
    $file->fputcsv($row);
}

dd($file);// Laravelの出力用ヘルパ関数
save.csv
headA,headB,headC
dataA,"data,B","data""C"

出力結果

SplFileObject {#63653 ▼
  path: "/home/public/app/app/Http/Controllers"
  filename: "save.csv"
  basename: "save.csv"
  pathname: "/home/public/app/app/Http/Controllers/save.csv"
  extension: "csv"
  realPath: "/home/public/app/app/Http/Controllers/save.csv"
  aTime: 2018-02-21 20:47:28
  mTime: 2018-02-21 20:47:28
  cTime: 2018-02-21 20:47:28
  inode: 4422
  size: 44
  perms: 0100777
  owner: 1000
  group: 50
  type: "file"
  writable: true
  readable: true
  executable: true
  file: true
  dir: false
  link: false
  csvControl: array:3 [▶]
  flags: 0
  maxLineLen: 0
  fstat: array:26 [▶]
  eof: false
  key: 0
}

TSV

読み込み

read.tsv
headA   headB   headC
dataA   "data   B"  "data""C"
$read_file_path = __DIR__.'/read.tsv';
$file = new \SplFileObject($read_file_path, 'r');
$file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY | \SplFileObject::READ_AHEAD);// 空行無視はSKIP_EMPTYとREAD_AHEADを共に設定する
$file->setCsvControl("\t");

foreach ($file as $row)
{
    var_dump($row);
}

dd($file);// Laravelの出力用ヘルパ関数

出力結果

array(3) { [0]=> string(5) "headA" [1]=> string(5) "headB" [2]=> string(5) "headC" }
array(3) { [0]=> string(5) "dataA" [1]=> string(6) "data    B" [2]=> string(6) "data"C" }

SplFileObject {#4248 ▼
  path: "/home/public/app/app/Http/Controllers"
  filename: "read.tsv"
  basename: "read.tsv"
  pathname: "/home/public/app/app/Http/Controllers/read.tsv"
  extension: "tsv"
  realPath: "/home/public/app/app/Http/Controllers/read.tsv"
  aTime: 2018-03-01 23:40:18
  mTime: 2018-03-01 23:37:06
  cTime: 2018-03-01 23:37:06
  inode: 751
  size: 43
  perms: 0100777
  owner: 1000
  group: 50
  type: "file"
  writable: true
  readable: true
  executable: true
  file: true
  dir: false
  link: false
  csvControl: array:3 [▶]
  flags: READ_AHEAD|SKIP_EMPTY|READ_CSV
  maxLineLen: 0
  fstat: array:26 [▶]
  eof: true
  key: 2
}

書き込み

$save_file_path = __DIR__.'/save.tsv';
$file = new \SplFileObject($save_file_path, 'w');// ファイルは作成してくれます。
$file->setCsvControl("\t");

$data = [];
$data[] = ['headA', 'headB', 'headC'];
$data[] = ['dataA', "data\tB", 'data"C'];

foreach ($data as $row) {
    $file->fputcsv($row);
}

dd($file);// Laravelの出力用ヘルパ関数
save.tsv
headA   headB   headC
dataA   "data   B"  "data""C"

出力結果

SplFileObject {#4261 ▼
  path: "/home/public/app/app/Http/Controllers"
  filename: "save.tsv"
  basename: "save.tsv"
  pathname: "/home/public/app/app/Http/Controllers/save.tsv"
  extension: "tsv"
  realPath: "/home/public/app/app/Http/Controllers/save.tsv"
  aTime: 2018-03-01 23:56:42
  mTime: 2018-03-01 23:56:42
  cTime: 2018-03-01 23:56:42
  inode: 869
  size: 43
  perms: 0100777
  owner: 1000
  group: 50
  type: "file"
  writable: true
  readable: true
  executable: true
  file: true
  dir: false
  link: false
  csvControl: array:3 [▶]
  flags: 0
  maxLineLen: 0
  fstat: array:26 [▶]
  eof: false
  key: 0
}

CSVやTSV形式の文字列を配列に変換する

一時的なファイルオブジェクトに追記して取得を行えばよい

$csvString = 'dataA,"data,B","data""C"';
$tempFile = new \SplTempFileObject();
$tempFile->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY | \SplFileObject::READ_AHEAD);
$tempFile->fwrite($csvString);// そのまま書き込む
$tempFile->rewind();// fpを先頭へ 

foreach ($tempFile as $row)
{
    var_dump($row);
}
array(3) {
  [0]=>
  string(5) "dataA"
  [1]=>
  string(6) "data,B"
  [2]=>
  string(6) "data"C"
}

但し、SplTempFileObject

result
getPathname() php://temp
getRealPath() false

だったのでメモリを使っているようなので容量が大きくなりそうな場合は/tmpを使った方がよさそう。

$testFilePath = tempnam(sys_get_temp_dir(), 'test_');
$testFile = new \SplFileObject($testFilePath, 'w+');
$tempFile->fwrite($csvString);
$tempFile->rewind();

foreach ($tempFile as $row)
{
    var_dump($row);
}

注意

Text file busyになるので使用し終わったらunset($file)等で開放しておいた方が良さそうです。

参考

SplFileObject::fgetcsv
SplFileObject::fputcsv
How to remove the extra line when using PHP SplFileObject and READ_CSV flag?
SplFileObject::setCsvControl

horikeso
個人的な備忘録ばかりですが、よろしくお願いします。難しいことはよくわかりません!
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away