PHPでCSVとTSV

最近はエクセルばかりでCSVは使っていませんでした。
今までは[fgetcsv](http://php.net/manual/ja/function.fgetcsv.php)を使って読み込んでいましたが、久しぶりに使おうと調べてみたら[SplFileObject](http://php.net/manual/ja/class.splfileobject.php)や[SplTempFileObject](https://www.php.net/manual/ja/class.spltempfileobject.php)を使うと圧倒的に楽ということを知りましたのでメモ。
（通常のファイル作成にも使えます）

# CSV

## 読み込み

```:read.csv
headA,headB,headC
dataA,"data,B","data""C"
```

```php
$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
}
```

## 書き込み

```php
$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"
```

```php
$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
}
```

## 書き込み

```php
$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形式の文字列を配列に変換する

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

```php
$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](https://www.php.net/manual/ja/class.spltempfileobject.php)は

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


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

```php
$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](http://php.net/manual/ja/splfileobject.fgetcsv.php)
[SplFileObject::fputcsv](http://php.net/manual/ja/splfileobject.fputcsv.php)
[How to remove the extra line when using PHP SplFileObject and READ_CSV flag?
](https://stackoverflow.com/questions/32806111/how-to-remove-the-extra-line-when-using-php-splfileobject-and-read-csv-flag)[SplFileObject::setCsvControl](http://php.net/manual/ja/splfileobject.setcsvcontrol.php)