はじめに
Laravel で CSV ダウンロードするときのアーキテクチャはいくつかある。
けど、コントローラからモデルをとって、整形して、CSV にするなら、
標準の JSON API リソースが JSON じゃなくて CSV になるだけでもよいのではないかと思った。
そして意外となかったので、書いてみます。
CSV API リソースを実装する
標準の JSON API リソースの実装を見て、最低限な次の機能を実装しました。
- CSV だと複数のレコードを扱うように思うので、コレクションを扱う
ResourceCollectionのみ - 整形とダウンロードのレスポンス生成のみ(ほかにも機能があるっぽい)
ResourceCollection.php
<?php
namespace App\Http\Resources\Csv;
use Illuminate\Contracts\Support\Responsable;
abstract class ResourceCollection implements Responsable
{
protected $collection;
public function __construct($resource)
{
$this->collection = $resource;
}
public function toResponse($request)
{
return response()->streamDownload(function () {
$file = new \SplFileObject('php://output', 'wb');
$header = $this->attributes();
if (! empty($header)) {
$file->fputcsv($this->convertCharset($header));
}
foreach ($this->toArray() as $fields) {
$file->fputcsv($this->convertCharset($fields));
}
}, $this->fileName());
}
private function convertCharset($fields)
{
return collect($fields)
->map(function ($item) {
return mb_convert_encoding($item, 'SJIS-win', mb_internal_encoding());
})
->all();
}
/**
* 見出しがあれば返します
*/
protected function attributes()
{
return [];
}
/**
* 整形した配列を返します
*/
abstract protected function toArray();
/**
* ダウンロードファイルの名前があれば返します
*/
protected function fileName()
{
return 'download.csv';
}
}
使ってみる
注文情報を CSV に書き出すことを考えてみましょう。
コントローラ
ほとんど JSON のときと同じです。
OrderController.php
<?php
namespace App\Http\Controllers;
use App\Order;
use App\Http\Resources\ReportResource;
class OrderController extends Controller
{
public function report()
{
return new ReportResource(
Order::query()
->where('is_canceled', false)
->get()
);
}
}
API リソース
JSON のときと同じく、toArray をオーバライドして整形します。
加えて、attributes で見出しを、fileName でダウンロードファイル名を、オーバライドして指定することもできます。
ReportResource.php
<?php
namespace App\Http\Resources;
use App\Http\Resources\Csv\ResourceCollection;
class ReportResource extends ResourceCollection
{
protected function toArray()
{
$this->collection->load(['user', 'products']);
return $this->collection
->map(function ($order) {
return [
$order->id,
$order->user->id,
$order->user->name,
$order->products->sum('price'),
];
})
->all();
}
protected function attributes()
{
return [
'注文ID',
'会員ID',
'会員名',
'注文金額合計',
];
}
protected function fileName()
{
return '注文一覧.csv';
}
}
ダウンロード例
注文一覧.csv
注文ID,会員ID,会員名,注文金額合計
1,1,ほげほげ,1000
2,1,ほげほげ,1500
3,2,ふがふが,1000
4,2,ふがふが,2000
おわりに
JSON のときと同じ使い方ができるので、覚えることが少ないし、標準にも近いかもしれない。
すっきり!