見出し画像

ブラウザからTSVファイルをローカルダウンロードする方法【Symfony/Laravel】

DBのデータを、ブラウザ経由でローカルにダウンロードする方法です。TSVでダウンロードし、エクセルで表示できるようにしました。備忘録として残しておきます。

やりたいこと

このデータを加工してエクセルで表示させたい。

$fruits = [
   [
       "name" => "apple",
       "price" => "120"
   ],
   [
       "name" => "banana",
       "price" => "78"
   ],
   [
       "name" => "orange",
       "price" => "50"
   ]
];

工程としては、ブラウザで https://xxxxxxx.jp/download を叩くとローカルにTSVファイルがダウンロードされ、

TSVファイルをExcelから開くと、このように表示されることを想定。

スクリーンショット 2020-07-07 19.41.38

ルーティングの設定

Route::get('/sample/download', 'Sample\SampleController@download');

StreamedResponseを使ってストリームを作成

StreamedResponseはSymfony(PHPフレームワーク)のクラスです。ダウンロードすることなく、標準でLaravelに入っているようです。

StreamedResponseクラスを使うとクライアントにレスポンスをストリーミングできます。

use Symfony\Component\HttpFoundation\StreamedResponse;

(中略)

public function download() {
   // ストリームを作成
   $streamResponse = new StreamedResponse();
   
   // 実際はDBからデータを取得   
   $fruits = [
       [
           "name" => "apple",
           "price" => "120"
       ],
       [
           "name" => "banana",
           "price" => "78"
       ],
       [
           "name" => "orange",
           "price" => "50"
       ]
   ];
   
   // tsvデータを設定
   $streamResponse->setCallback(function () use($fruits) {
       $this->createTsvData($fruits);
   });
}

setCallbackにtsvにしたいデータを設定します。データの生成はcreateTsvDataメソッドで行います。

createTsvDataメソッドにfruitsのデータを渡します。

TSVデータの生成

TSVファイルに書き込むデータをPHPのファイルシステム関数を使って実装していきます。

private function createTsvData($fruits) {
   $tsvFile = fopen('php://output', 'w');
   // tsv1行目
   $columnNames = [
       '名前',
       '値段'
   ];
   
   // Excelに対応するため文字コード変換
   mb_convert_variables('SJIS-win', 'UTF-8', $columnNames);
   
   // ファイルへ追記
   fputcsv($tsvFile, $columnNames, "\t");
   
   foreach ($fruits as $fruit) {
       $row = [
           $fruit['name'],
           $fruit['price']
       ];
       
       // Excelに対応するため文字コード変換
       mb_convert_variables('SJIS-win', 'UTF-8', $row);
       
       // ファイルへ追記
       fputcsv($tsvFile, $row, "\t");
   }
   
   // ファイルデータ作成終了
   fclose($tsvFile);
}

使用しているPHPの関数はこんな役割があります。

・fopen
URLをオープンにする。'w' は書き出しのみでオープン。
・mb_convert_variables
文字コードを変換。
第一引数・・・変換後の文字コード
第二引数・・・変換前の文字コード
第三引数・・・変換対象
(SJIS-winは、SJISに特殊文字を追加した文字コード)
・fputcsv
行を CSV 形式にフォーマットして、ファイルポインタに書き込む
第一引数・・・対象ファイル
第二引数・・・1行の配列データ
第三引数・・・オプション。区切り文字を指定
( "\t" を指定するとタブ区切りのtsvになる。csvにしたい場合は指定なしでOK)
・fclose
オープンされたファイルポインタをクローズ

ストリームを返す

tsvデータの作成・設定ができたので、ヘッダ・ステータスコードをストリームに設定します。

public function download() {

   (中略)
   
   // 本日の日付をファイル名にする
   $now = Carbon::now();
   $today = $now->format('Ymd');
   $fileName = "fruits_${today}.tsv";
   
   // ヘッダを設定
   $streamResponse->headers->set('Content-Type', 'text/tab-separated-values');
   $streamResponse->headers->set('Content-Disposition', "attachment; filename=${fileName}");
   
   // ステータスコードを設定
   $streamResponse->setStatusCode(200);
   $streamResponse->send();
   
   return $streamResponse;
}

StreamedResponseへの設定方法は下記です。

・ヘッダの設定
$streamResponse->headers->set();
・ステータスコードの設定
$streamResponse->setStatusCode();

参考

ブラウザからURLを叩いてみる

処理が書けたので検証してみます。

スクリーンショット 2020-07-07 20.33.35

作成したtsvファイルをダウンロードできました。

スクリーンショット 2020-07-07 20.34.20

エクセルから開くとこのように表示されます。

スクリーンショット 2020-07-07 20.35.22

完成です🎉

完成したControllerの全コード

<?php

namespace App\Http\Controllers\Sample;

use App\Http\Controllers\Controller;
use Carbon\Carbon;
use Symfony\Component\HttpFoundation\StreamedResponse;

class SampleController extends Controller
{
   /**
    * ブラウザからファイルをダウンロード
    */
   public function download() {
       // ストリームを作成
       $streamResponse = new StreamedResponse();
       $fruits = [
           [
               "name" => "apple",
               "price" => "120"
           ],
           [
               "name" => "banana",
               "price" => "78"
           ],
           [
               "name" => "orange",
               "price" => "50"
           ]
       ];
       
       // tsvデータを設定
       $streamResponse->setCallback(function () use($fruits) {
           $this->createTsvData($fruits);
       });
       
       // 本日の日付をファイル名にする
       $now = Carbon::now();
       $today = $now->format('Ymd');
       $fileName = "fruits_${today}.tsv";
       
       // ヘッダを設定
       $streamResponse->headers->set('Content-Type', 'text/tab-separated-values');
       $streamResponse->headers->set('Content-Disposition', "attachment; filename=${fileName}");
       
       // ステータスコードを設定
       $streamResponse->setStatusCode(200);
       
       $streamResponse->send();
       
       return $streamResponse;
   }
   
   /**
    * tsvファイルのデータを作成
    */
private function createTsvData($fruits) {

   $tsvFile = fopen('php://output', 'w');
   
   // tsv1行目
   $columnNames = [
       '名前',
       '値段'
   ];
   
   // Excelに対応するため文字コード変換
   mb_convert_variables('SJIS-win', 'UTF-8', $columnNames);
   
   // ファイルへ追記
   fputcsv($tsvFile, $columnNames, "\t");
   foreach ($fruits as $fruit) {
       $row = [
           $fruit['name'],
           $fruit['price']
       ];
       
       // Excelに対応するため文字コード変換
       mb_convert_variables('SJIS-win', 'UTF-8', $row);
       
       // ファイルへ追記
       fputcsv($tsvFile, $row, "\t");
   }
   
   // ファイルデータ作成終了
   fclose($tsvFile);
}
}



スキ頂けると嬉しいです〜