Laravel5.8 Goodby CSV を使ってCSVファイルダウンロードを実装

LaravelとGoodby CSVを使って、CSV出力をする処理を実装する機会があったので内容をまとめたいと思います。

## Goodby CSVとは

Goodby CSVは、PHP5.3以降で使用できるメモリ効率が高く、柔軟で拡張可能なオープンソースのCSVインポート/エクスポートライブラリです。
また、SJIS-win, EUC-JP や UTF-8等の文字コードにも対応してます。

@suin さんと @reoring さんが作ったライブラリに感謝です🙏🙏🙏
GitHubはこちら ⭐️ [goodby/csv](https://github.com/goodby/csv) ⭐️

## 環境

- PHP 7.3.6
- Laravel 5.8.27
- MySQL 8.0.16

※新しくLaravelプロジェクトをインストールしてマイグレーション実行まで済ませた状態から始めています。

## Goodby CSV インストール

```shell
$ composer require goodby/csv
```

## CSVダウンローダサービスクラスを実装

`app/Services/CsvDownloader.php` を新しく作成します。

```php
<?php

namespace App\Services;

use App\User;
use Goodby\CSV\Export\Standard\Exporter;
use Goodby\CSV\Export\Standard\ExporterConfig;
use Illuminate\Database\Eloquent\Collection;
use Symfony\Component\HttpFoundation\StreamedResponse;

final class CsvDownloader
{
    /**
     * @return StreamedResponse
     */
    public function download(): StreamedResponse
    {
        $callback = function () {
            $config = new ExporterConfig();
            $config
                ->setDelimiter(',') // 区切り文字
                ->setEnclosure('"') // 囲み文字
                ->setEscape('\\') // エスケープ文字
                ->setToCharset(null) // 出力ファイルの文字コード
                ->setFromCharset('auto') // 読み込み元の文字コード
                ->setColumnHeaders($this->makeCsvHeader()); // CSVの1列目のヘッダー行
            $exporter = new Exporter($config);
            $exporter->export('php://output', $this->makeCsvBody());
        };

        return response()->streamDownload($callback, $this->makeFilename(), $this->makeResponseHeader());
    }

    /**
     * @return array
     */
    private function makeCsvHeader(): array
    {
        return ['名前', 'メールアドレス', '作成日'];
    }

    /**
     * @return array
     */
    private function makeCsvBody(): array
    {
        $users = $this->getUsers();

        $data = [];
        foreach ($users as $user) {
            $data[] = $this->toCsvFromUser($user);
        }

        return $data;
    }

    /**
     * @param User $user
     * @return array
     */
    private function toCsvFromUser(User $user): array
    {
        return [
            $user->name,
            $user->email,
            $user->created_at,
        ];
    }

    /**
     * @return Collection
     */
    private function getUsers(): Collection
    {
        return User::all();
    }

    /**
     * @return array
     */
    private function makeResponseHeader(): array
    {
        return [
            'Content-type' => 'text/csv',
            'Cache-Control' => 'must-revalidate, no-cache',
            'Expires' => '0',
        ];
    }

    /**
     * @return string
     */
    private function makeFilename(): string
    {
        return 'ユーザー一覧.csv';
    }
}
```

### 補足

長いコードになっちゃいましたが、Goodby CSVを使用してる箇所はここです。

```php
$config = new ExporterConfig();
$config
    ->setDelimiter(',') // 区切り文字
    ->setEnclosure('"') // 囲み文字
    ->setEscape('\\') // エスケープ文字
    ->setToCharset(null) // 出力ファイルの文字コード
    ->setFromCharset('auto') // 読み込み元の文字コード
    ->setColumnHeaders($this->makeCsvHeader()); // CSVの1列目のヘッダー行
$exporter = new Exporter($config);
$exporter->export('php://output', $this->makeCsvBody());
```

出力設定は [`ExporterConfig`](https://github.com/goodby/csv/blob/master/src/Goodby/CSV/Export/Standard/ExporterConfig.php) クラスで行います。
CSVの1行目のヘッダー行(setColumnHeaders)の設定以外はデフォルト値を使用してます。
お好みでカスタマイズしてご使用ください。

また、[README.md](https://github.com/goodby/csv#export-from-array)に書かれてあった配列からエクスポートする方法を使用してます。

### ファイルダウンロード(ストリームダウンロード)

出力するCSVをディスクに書き込ませず、文字列レスポンス通してダウンロードさせたい場合は `streamDownload`メソッドを使用します。

- [streamDownload](https://laravel.com/api/5.8/Illuminate/Routing/ResponseFactory.html#method_streamDownload)
- [ResponseFactory.php](https://github.com/laravel/framework/blob/5.8/src/Illuminate/Routing/ResponseFactory.php#L126-L148)

## ルーティング定義

`routes/web.php` へ下記のルーティングを追記します。

```php
Route::get('/users', 'UsersController@index');
Route::get('/users/download', 'UsersController@download');
```

## コントローラを作成

`src/app/Http/Controllers/UsersController.php` を新たに作成します。

```php
<?php

namespace App\Http\Controllers;

use App\User;
use App\Services\CsvDownloader;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\StreamedResponse;

class UsersController extends Controller
{
    /**
     * @return array
     */
    public function index(): array
    {
        $users = User::all();

        return [
            'users' => $users,
        ];
    }

    /**
     * @return StreamedResponse
     */
    public function download(): StreamedResponse
    {
        return (new CsvDownloader())->download();
    }
}
```

## テスト用のユーザーを作成

```shell
$ php artisan tinker
>>> factory(App\User::class, 10)->create();
```

[モデルファクトリー](https://readouble.com/laravel/5.8/ja/database-testing.html#persisting-models)を使って、10件のテストデータを作成します。

## 動作確認

- http://127.0.0.1:10080/users

<img width="491" alt="スクリーンショット 2019-07-04 15.15.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/e7adb1ca-1f87-b2ca-0cef-a9cb2cf2e834.png">

ユーザー一覧がjsonで返ってきてます。
コントローラで配列を返すとLaravel側でjsonによしなに変換してくれるので確認するときは便利ですね😊

話は全然変わりますが、 [JSON Formatter](https://chrome.google.com/webstore/detail/json-formatter/bcjindcccaagfpapjjmafapmmgkkhgoa?hl=ja) 入れてると便利です。

- http://127.0.0.1:10080/users/download

![スクリーンショット 2019-07-04 15.21.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/348d1dd0-5c76-bddd-4d72-048312ee3ebd.png)

実際にダウンロードされたファイルです。

## さいごに

Laravel と Goodby CSV で簡単にCSV出力処理を実装できました！