【Laravel】csvエクスポート機能をLaravel Excelで実装してみた(+解説)

最近業務内でアプリケーションのAPI化をしているのですが、既存のcsvエクスポートはPHPの組み込み関数を使ったもので少しわかりにくく拡張性も少ない様に感じ、もう少しLaravelぽくできて拡張性あるパッケージ取り入れたほうが良いなと思ったところLaravel Excelというちょうど良いものがあったので使ってみました。

# 組み込み関数 VS Laravel Excelの使用感比較

どちらも長短ありますが、個人的な感想としてはこんな感じです。

### 組み込み関数

- fputcsv関数を使用したエクスポートになるかと思いますが、使用するのがとても簡単なため学習コストはほとんどかからない。
- 大規模データを扱う場合、自分で複雑なロジックを考える必要があるかもしれない。
- 拡張性がない。

### Laravel Excel

- すこし学習のコストはかかる。
- PHPのコードというよりはLaravelらしいコードになる。
- さまざまな拡張機能が用意されており、直感的にそれらを扱える。
- 一度実装したらcsvだけでなく他のファイル形式にも簡単に対応できる。
- 非同期処理等も簡単にできるので重めの処理にも比較的簡単に対応できる。

簡単に実装したいなら組み込み関数、複雑なロジックを組む可能性があったり、拡張性を高めたいならLaravel Excelを使うといった感じでしょうか。

# csvエクスポート実装

(色々な機能について書きたいのでほんの少し複雑にしています。)

実装コードとコードに関する解説やLaravel Excelの機能についてを織り交ぜて説明します。

## 実現したいこと・想定PJの概要

- ユーザーの情報一覧をcsvにしてPCにダウンロードするエンドポイントを作成。
- ユーザーは何らかのチームに所属しているとする。(UserモデルとTeamモデルが存在)
- 今回ダウンロードするcsvファイルにはチーム情報などのリレーション先の情報も含める。
- エクスポートする内容は、検索機能のようにデータを絞れる。
- また、Laravel Excelの機能にフォーカスしたいので、modelやdatabaseの用意は省く。
- 開発した環境はLaravel version 9(sail)。他のバージョンでもできると思います。
- sail artisan とでてきますが、sailを使っていない場合はphp artisan としてください。

## Laravel Exportのインストール & 下準備

以下のコマンドを実行し、プロジェクト内にLaravel Excel をインストールしてください。

**Laravel10の場合**

```php
composer require maatwebsite/excel:^3.1.48 -W
```

**Laravel9の場合**

```php
composer require psr/simple-cache:^1.0 maatwebsite/excel
```

**Laravel8以前の場合**

```php
composer require maatwebsite/excel
```

インストールが完了したら、config/app.phpのprovidersとailiases部分に以下を追記してください。

```php
'providers' => [
    /*
     * Package Service Providers...
     */
    //　これを追加
    Maatwebsite\Excel\ExcelServiceProvider::class,
]

'aliases' => [
    ...
    // これも追加
    'Excel' => Maatwebsite\Excel\Facades\Excel::class,
]
```

記載が終わったら以下のコマンドを実行してください。実行が完了したらexcel.phpがconfigフォルダ内に生成されます。

```php
sail artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider" --tag=config
```

## ルート定義

api.php内に以下の記載をします。(別に使用しているファイルで大丈夫)

また、api.php内で書く場合、動作確認を簡単にするために、middlewareは使用しないでおきましょう。

```php
Route::get('/users/csv-export', [UserController::class, 'csvExport']);
```

## コントローラ追記

今回の場合、キーワードで出力するユーザーに関する条件を指定する機能をつけているのでRequestにクエリ文字列が渡ってきます。必要ない場合は無視で大丈夫です。

今回はエクスポートする対象のユーザーを所属チームと、ユーザーの名前で検索機能のように絞れる仕様とします。

```php
<?php

namespace App\Http\Controllers\User;

use App\Exports\UserExport;
use Illuminate\Http\Request;
use Carbon\Carbon;
use Excel;

public function csvExport(Request $request): \Symfony\Component\HttpFoundation\BinaryFileResponse
{
    $name = $request->query('name');
    $teamId = (int)$request->query('team_id');

	return Excel::download(
        new UsersExport($name, $teamId),
        Carbon::now()->format('YmdHi') . '_' . 'ユーザー一覧.csv',
    );
}
```

### Laravel Excelの機能解説(コード解説)

**Excelファサード**
Excelファサードの関数としてよく使うのは以下の二つです。

Excel::download: 生成されたファイルをPCにダウンロードする際に使用

```php
/**
* 第一引数: インスタンス化したExportクラスを入れる。
* 第二引数: ダウンロードするcsvファイルにつける名前を入れる。
* 第三引数: ファイルの形式を指定する。(例: \Maatwebsite\Excel\Excel::XLSX)
* ファイルの形式に関しては第二引数の拡張子によって判別されているので基本的に指定しない。
* 第四引数: headerに含めたい情報を配列で指定する
*/
Excel::downdload(new UserExport, 'users.csv', true, ['X-Vapor-Base64-Encode' => 'True']);
```

Excel::store: 生成されたファイルをstorage配下やDB・s3等に保存する際に使用
```php
/**
* 第一引数: インスタンス化したExportクラスを入れる。
* 第二引数: 保存するcsvファイルにつける名前を入れる。
* 第三引数: 保存先を指定する。指定がない場合はデフォルトのdiskが適用される。
* 第四引数: おそらくファイル形式の指定??(少し不明です。多分そこまで使いません。)
* 第五引数: オプションを配列で指定。
*/
Excel::store(new UserExport, 'users.csv', 's3', null, [
  'visibility' => 'private',
]);
```

コードで出てくるUsersExportは次のセクションで作成するので、そちらで概要を解説します。

## Exportファイルを生成

以下のコマンドを用いてUserモデル用のExportファイルを作ります。

```php
sail artisan make:export UsersExport --model=User
```

コマンドが実行されるとapp/Exports配下に以下のようなファイルが生成されると思います。

```php
<?php

namespace App\Exports;

use App\Models\User;
use Maatwebsite\Excel\Concerns\FromCollection;

class UsersExport implements FromCollection
{
    public function collection()
    {
        return User::all();
    }
}
```

このファイルの内容を以下のように変更します。

```php
<?php

namespace App\Exports;

use App\Models\User;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;

class RespondentListExport implements FromCollection, WithHeadings
{
    protected $name;
    protected $teamId;

    public function __construct(?string $name=null, ?int $teamId=null)
    {
        $this->name = $name;
        $this->status = $teamId;
    }

    /**
     * @return \Illuminate\Support\Collection
     */
    public function collection()
    {
       　return User::with(['teams'])
            ->when($this->name, function ($query) {
                $query->where('name', 'like', "%{$this->name}%");
            })
            ->when($this->teamId, function ($query) {
                $query->where('team_id', $this->teamId);
            })
            ->get();
    }

    public function headings(): array
    {
        return [
            'id',
            '名前',
            'メールアドレス',
            '所属チーム',
            '所属チームコーチ',
        ];
    }

    public function map($row): array
    {
        return [
            $row->id,
            $row->name,
            $row->email,
            optional($row->team)->name,
            optional($row->team)->coach,
        ];
    }
}
```

### Laravel Excelの機能解説(コード解説)

Exportクラスで行われる処理を簡単に説明すると、collectionメソッドの中で呼び出されているデータの集合を元にエクスポートするファイルの生成を行うといった具合です。(arrayメソッドを使用してarray型で指定することもできます。)またその際に、返すデータの値を動的に変更したりしたい場合には__constructメソッドを定義して、その中でcontrollerから値を受けっとてあげることで条件指定なども含めたcsvエクスポトートをすることができます。

Laravel Excelで用意されている使用頻度が高そうなメソッドについて以下にいくつかまとめておきます。詳しく知りたい場合は公式のドキュメントを参照してください。

#### collectionメソッド
collection型のものをcsvエクスポートのデータとする場合に使用します。

```php
// 1. 以下を追加でuseする
use Maatwebsite\Excel\Concerns\FromCollection;

// 2. useしたインターフェースを使用
class UserExport implements FromCollection

// 3. エクスポートしたいデータを返す
public function collection()
{
  return User::all();
}
```

#### arrayメソッド
collectionメソッドのarrayバージョンです。

```php
use Maatwebsite\Excel\Concerns\FromArray;

class UserExport implements FromArray

public function array()
{
  return [
    [1, 2, 3],
    [4, 5, 6]
  ];
}
```

#### headingsメソッド
ヘッダー(ラベル名)を独自で指定したい場合に使用します。

```php
// 1. 以下を追加でuseする
use Maatwebsite\Excel\Concerns\WithHeadings;

// 2. useしたインターフェースを使用
class UserExport implements FromCollection, WithHeadings

// 3. headingsを定義し、ラベル名を配列で返す
public function headings(): array
{
    return [
        'id',
        '名前',
        'メールアドレス',
        '所属チーム',
        '所属チームコーチ',
    ];
}
```

#### mapメソッド
collection等で取得したデータ個別に対して何のデータを返却するかを定義する際に使用します。

```php
// 1. 以下を追加でuseする
use Maatwebsite\Excel\Concerns\WithMapping;

// 2. useしたインターフェースを使用
class UserExport implements FromCollection, WithMapping

// 3. mapを定義し、collectionに格納されている個々のデータから返す値を定義する。
public function map($row): array
{
      return [
          $row->id,
          $row->name,
          $row->email,
          optional($row->team)->name,
          optional($row->team)->coach,
      ];
}
```

#### startCellメソッド
表のスタートするセルがデフォルトではA1の位置ですが、こちらを使用するとそれをカスタマイズすることができます。(collectionのみ対応しています。)

```php
// 1. 以下を追加でuseする
use Maatwebsite\Excel\Concerns\WithCustomStartCell;

// 2. useしたインターフェースを使用
class UserExport implements FromCollection, WithCustomStartCell

// 3. headingsを定義し、ラベル名を配列で返す
public function startCell(): string
{
    return 'B2';
}
```

## 動作確認
webのルートで定義した場合もapiで定義した場合もエンドポイントにブラウザからアクセスすることで動作を確認することができます。

今回紹介したようなコードだと、アクセスすることで自分のPCにダウンロードされる＆別ウィンドウでcsvファイルが展開されるのが確認できると思います。

# 最後に

Laravel Excelを用いたcsv-exportの方法を書きました。
拡張性もあり、他のファイルタイプも扱えるのでとっても良いなと思いました。
コードに間違い等ありましたらご指摘ください！

もっと詳しいことに関しては以下の記事にまとめているので確認してみてください！
https://qiita.com/yuta-2001/items/b6fd9b63739dac55043b

# 参考
[Supercharged Excel exports and imports in Laravel | Laravel Excel](https://laravel-excel.com/)
[Laravel Excelをマスターしよう | アールエフェクト](https://reffect.co.jp/laravel/laravel_excel_master/)