matsudada技術ブログ

日々の雑念と備忘録

Laravel 5.7でCSVをダウンロードするときに内容が画面に表示されてしまう

Laravel 5.7でCSVをダウンロードするときに内容が画面に表示されてしまう

CSVファイルをダウンロードする機能の作成を依頼していて、
質問されたが原因が全然分からなかった。

環境

現象

一定までのサイズならCSVファイルがダウンロードされるが、
一定のサイズを超えると内容が画面に表示される。
とりあえずメモリリークしてね?とは思ったが、今回の問題には関係ないので触れないでおいた。

問題のコード

public function csvDownload(Request $request)
{
    $headers = [
                'Content-Type' => 'text/csv',
                'Content-Disposition' => 'attachment; filename="users.csv"'
               ];
    $stream = csvDownload($request); // 検索してstreamに書き込む

    return \Response::make(stream_get_contents($stream), 200, $headers);
}

原因

下記の参考ページを発見して知ったが、こんな仕様があるらしい。

最後に、Httpヘッダーの作成までに時間がたくさんかかってしまった場合、そもそもファイルのダウンロードに失敗してしまいます。
これは、ブラウザがしばらくレスポンスを待ってもHttpヘッダーが返ってこない場合、デフォルトのヘッダーを使ってブラウザにデータを吐き出してしまうという仕様によるものです。

対応

参考ページに乗っているコードを一部改変し使用した。
変更点

  • BOMの付け方が元のやり方では文字列として出力されるため変更
  • ネストが浅くなるのでchunkからcursorに変更
use \Symfony\Component\HttpFoundation\StreamedResponse;

public function csvDownload(Request $request)
{
    $headers = [
                'Content-Type' => 'text/csv',
                'Content-Disposition' => 'attachment; filename="users.csv"'
               ];
     
    return new StreamedResponse(
        function () {
            $stream = fopen('php://output', 'w');
            // ExcelでUTF-8と認識させるためにBOMを付ける(変更部分)
            fwrite($stream, pack('C*', 0xEF, 0xBB, 0xBF));
            
            // chunkではなくcursorを使用(変更部分)
            $cursor = \DB::table("users")->orderBy("id")->cursor();
            foreach ($cursor as $user) {
                fputcsv($stream, [$user->id, $user->name]);
            }
            fclose($stream);
        },
        200,
        $headers
    );
}

PhpSpreadsheetで入力規則が読み込まれない

PhpSpreadsheetで入力規則が読み込まれない

半日近くはまったので記事にする。

環境

  • CentOS 7.5
  • PHP 7.2.10
  • Composer version 1.7.2
  • phpoffice/phpspreadsheet 1.6.0

現象

入力規則を設定していたExcelファイルを読み込んだ時に入力規則が読み込まれないので、
ファイルを保存すると設定していた入力規則が消える。

$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
$spreadsheet = $reader->load("./template.xlsx");

$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
$writer->save("./export.xlsx");

原因

入力規則が他のシートの値を参照する入力規則であり、バグによって消えていた。

入力規則:[=$I:$I]は消えない
入力規則:[=source!$A:$A]は消える

GitHubのissueを見つけ、まだリリースされていないことが判明した。

参考

Xls and Xlsx readers miss data validation for files saved with Excel #388 https://github.com/PHPOffice/PhpSpreadsheet/issues/388

Xml does not recognize data validations that references another sheet #639 https://github.com/PHPOffice/PhpSpreadsheet/issues/639

対応

入力規則を設定し、その後で保存することにした。

$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
$spreadsheet = $reader->load("./template.xlsx");

# 入力規則を追加
$validation = $spreadsheet->getActiveSheet()->getCell('A1')->getDataValidation();
$validation->setType(\PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_LIST);
$validation->setErrorStyle(\PhpOffice\PhpSpreadsheet\Cell\DataValidation::STYLE_INFORMATION);
$validation->setAllowBlank(false);
$validation->setShowInputMessage(true);
$validation->setShowErrorMessage(true);
$validation->setShowDropDown(true);
$validation->setFormula1('source!$A:$A');


$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
$writer->save("./export.xlsx");

参考

PhpSpreadsheet's documentation - Setting data validation on a cell - https://phpspreadsheet.readthedocs.io/en/latest/topics/recipes/#setting-data-validation-on-a-cell

メインで使うブラウザをBraveに変えてみた

元々Google Chromeをメインで使っていたが、
同じくChromiumをベースとしているBraveに変えてみた。

Brave
brave.com

広告をブロックする機能が標準で付いているので、
使い始めて1日目にして1000以上の広告をブロックしてくれた。

もう1つのウリである表示速度はあまり体感できていないが。

Ajax(jQuery)でselectタグを書き換える

jQueryAjaxリクエストの結果でselectタグを書き換えるときの書き方

数年ぶりに書いたら完全に忘れていたので備忘録として記事にする。

buttonをクリックした時に、selectタグを書き換える。

以下htmlのコード

<button id="trigger">button</button>
<div id="targetContainer">
  <select id="target">
    <option>default</option>
  </select>
</div>

以下javascriptのコード
正確には覚えていないが、optionのみの書き換えだと何らかの問題があり、 場当たり的にselectタグごと書き換えにした。
何故だったんだろうか。

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
  $(function(){
    $("#trigger").on("click", function(){
      $.ajax({
        type: 'get',
        datatype: 'json',
        contentType: 'application/json',
        url: 'url',
        data: { "id" : 1 }
        })
        // 成功時処理
        .done(function(data){
            var selectHtml = "<select id='target'>";

            $.each(data, function (i) {
                selectHtml += "<option value='" +data[i].id + "'>" + data[i].value + "</option>";
            });
            selectHtml += "</select>";
            $("#targetContainer").html(selectHtml);
        })
        // 失敗時処理
        .fail( function(xhr, textStatus, errorThrown) {
            alert(xhr.responseText);
        });
    });
  })
</script>