さいとブログ

LaravelでCSVをエクスポートする機能を実装した話

LaravelでDBのデータをCSV形式でダウンロードする機能を実装したので、まとめておきます。
splfileObjectというphpのクラスを使って実装します。

環境

Laravel: 6.8
MySQL: 8.0


ディレクトリ構成
一応app配下のディレクトリ構成を載せます。

app
├── Http
│  └── Controllers
│    └── DummyController.php
├── Models
│  ├── Dummy.php
├── Providers
│  ├── AppServiceProvider.php
├── Services
│  └── DummyService.php
└── User.php


最近、自分が扱えるようになった、サービスクラスを使い、モデル・サービス・コントローラーに処理を分けて実装します。

  • モデル: DBとのやり取り
  • サービス: CSV出力のためのロジック
  • コントローラー: サービスクラスを呼び、結果をViewに返すだけのシンプルな役割


準備

まず、DBにデータを登録します。

public function up()
  {
    Schema::create('dummies', function (Blueprint $table) {
      $table->bigIncrements('id');
      $table->string('name', 50);
      $table->string('user_name', 50);
      $table->string('sex', 1);
      $table->string('post_code', 7);
      $table->string('address');
      $table->string('email');
      $table->string('tel');
      $table->string('password');
      $table->string('text', 100);
      $table->integer('number');
      $table->timestamps();
    });
  }


use App\Models\Dummy;
use Faker\Generator as Faker;

$factory->define(Dummy::class, function (Faker $faker) {
  return [
    // 名前 姓名
    'name' => $faker->name(),
    // ユーザー名
    'user_name' => $faker->unique()->userName(),
    // 配列内の文字をランダムに出力 今回は男or女
    'sex' => $faker->randomElement($array=['男', '女']),
    // 正規表現で郵便番号7桁を出力
    'post_code' => $faker->regexify('[1-9]'),
    // 住所 郵便番号も
    'address' => $faker->address(),
    // メールアドレス
    'email' => $faker->email(),
    // 電話番号
    'tel' => $faker->phoneNumber(),
    // パスワード
    'password' => $faker->password(),
    // ランダムに日本語文を20文字で出力
    'text' => $faker->realText(20),
    // 10~100の数字をランダムに出力
    'number' => $faker->numberBetween(10, 100)
  ];
});


use Illuminate\Database\Seeder;
use App\Models\Dummy;

class DummyTableSeeder extends Seeder
{
  public function run()
  {
    factory(Dummy::class, 50)->create();
  }
}


最後にseederを登録して、php artisan migrate:fresh --seedを実行します。
これで100件のダミーデータが登録されました。

手順

ここからが本題です。

モデルの作成

DB から全件取得します。

use Illuminate\Database\Eloquent\Model;

class Dummy extends Model
{
  public static function getDummyAll()
  {
    $allData = self::all();

    return $allData;
  }
}


CSV出力の処理

サービスクラスにCSV処理を記述します。

use App\Models\Dummy;
use Illuminate\Support\Arr;

class DummyService
{
  public function getDummyCsvAll()
  {
    $allData = Dummy::getDummyAll();

    /**
     * CSVフォーマット
     * 文字コード:SJIS
     * 改行コード:CRLF
     * 囲い文字:ダブルクォート
     * 区切り文字:カンマ
     */
    $stream = fopen('php://temp', 'w');
    // ヘッダータイトル
    $columnHeader = implode(",", [
        '"ID"',
        '"氏名"',
        '"ユーザー名"',
        '"性別"',
        '"郵便番号"',
        '"住所"',
        '"メールアドレス"',
        '"電話番号"',
        '"パスワード"',
        '"テキスト"',
        '"数字"',
        '"作成日時"',
        '"更新日時"',
      ]);

    foreach ($allData as $data) {
      // 囲み文字の""付きの文字列を1行ずつ生成
      $outputText = implode(",", [
        '"' . $data->id . '"',
        '"' . $data->name . '"',
        '"' . $data->user_name . '"',
        '"' . $data->sex . '"',
        '"' . $data->post_code . '"',
        '"' . $data->address . '"',
        '"' . $data->email . '"',
        '"' . $data->tel . '"',
        '"' . $data->password . '"',
        '"' . $data->text . '"',
        '"' . $data->number . '"',
        '"' . $data->created_at . '"',
        '"' . $data->updated_at . '"',
      ]);

      // csv形式にフォーマット
      fputcsv($stream, [
        $outputText
      ], ",", "\n", "");
    }

    // ファイルポインタの位置を先頭に戻す
    rewind($stream);
    // CRLF変換
    $csv = str_replace(PHP_EOL, "\r\n", stream_get_contents($stream));
    // 不要な改行コードを削除
    $csv = str_replace("\r\n\r\n", '', $csv);
    // CSVのヘッダータイトルを追加
    $csv = $columnHeader . $csv;
    // SJIS変換
    $csv = mb_convert_encoding($csv, 'SJIS');

    return $csv;
  }
}


サービスクラスの登録

作成したサービスクラスをプロバイダに登録します。
これでコントローラから呼べるようになります。

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
  public function register()
  {
    // サービスクラスの登録
    $this->app->bind('App\Services\DummyService');
  }
}


コントローラー

コントローラー内でサービスクラスを呼び、その結果をViewに返しています。

use Illuminate\Http\Request;
use App\Services\DummyService;

class DummyController extends Controller
{
  protected $dummyService;

  public function __construct(DummyService $dummyService)
  {
    $this->dummyService = $dummyService;
  }

  public function csv(Request $request)
  {
    $csvData = $this->dummyService->getDummyCsvAll();

    // ヘッダー設定
    $headers = array(
      'Content-Type' => 'text/csv',
      'Content-Disposition' => 'attachment; filename="テスト.csv"',
    );

    return response($csvData, 200, $headers);
  }
}


あとは、Viewにダウンロード用ボタンを設置して、ルーティング(web.php)で CSV処理用のコントローラに飛ばすだけで使えるようになります。

参考


プロフィール

profile icon

saitoです。
ソフトウェアエンジニアとして働いています。
web開発に関する学びを当ブログに書き残しています。