前提
開発環境 AWS cloud9 (t2.micro)
使用言語 PHP7.0
フレームワーク Laravel 5.1.6
データベース MySQL 5.7.33
前提といたしまして、当方は既存のプロジェクトの改良に携わっているという背景があります。
下記コーディングの殆どは既に記述のあったもので、勉強のために自分でコメントを振り少しずつ解釈しているところです。
最悪下記の手法に捉われることなく一から書き直すことも念頭に置いてはいるのですが、極力既存のコードを修正する方向で問題を解消したいと考えています。
上記ご理解いただける方にご助力いただけますと幸甚です。
実現したい内容
「データベースに登録された情報を用いてCSVファイルを作成・ダウンロードする」
テーブル内容例
my_project.users +----+------+-------------+ | id | name | phone | +----+------+-------------+ | 1 | Nick | 090******** | +----+------+-------------+ | 2 | Mary | 080******** | +----+------+-------------+ | 3 | Kate | 070******** | +----+------+-------------+
発生している問題
CSV出力及びダウンロードは申し分なく行えるのですが、
タイトルにも記載の通り、エクスポートされたCSVの1行目が空白行になってしまいます。
できるならばこれを避けて、1行目からデータ行を書き込みたいと考えています。
sakura editorで確認したところ、先頭にLF改行が含まれているようです。
CSV
1(LF改行) 21,Nick,090******** 32,Mary,080******** 43,Kate,070********
コーディング
- Class (/MyProject/app/Repositories/CSV.php)
php
1<?php 2 3namespace App\Repositories; 4 5use Response; 6use Config; 7use Illuminate\Support\Facades\Log; 8 9class CSV { 10 public function __construct() { 11 } 12 13 public function download($list, $header, $filename) { 14 // ob_start(); *** ① 15 // ヘッダーが指定されていたら、配列の最初に加える 16 if (count($header) > 0) { 17 array_unshift($list, $header); 18 19 } 20 21 // 読込・書出用の一時ファイルをバイナリーモードで開く 22 $stream = fopen('php://temp', 'r+b'); 23 // CSVソースの準備。配列に含まれる配列をひとつづつ挿入する 24 foreach ($list as $row) { 25 fputcsv($stream, $row); 26 } 27 // ファイルポインタをファイル先頭に戻す 28 rewind($stream); 29 // ファイルの文字列を取出し 30 $csv = stream_get_contents($stream); 31 /** Ⅰ **/ 32 // 改行コードをCRLFに置き換える 33 $csv = str_replace(PHP_EOL, "\r\n", $csv); 34 /** Ⅱ **/ 35 // 文字コードをUTF-8からSJISに変換する 36 $csv = mb_convert_encoding($csv, 'SJIS-win', 'UTF-8'); 37 /** Ⅲ **/ 38 // レスポンスのヘッダー情報を設定 39 $headers = array( 40 'Content-Type' => 'text/csv', 41 'Content-Disposition' => "attachment; filename=$filename", 42 ); 43 // $out = ob_get_contents(); ***② 44 // ob_end_clean(); ***③ 45 // trim($out); ***④ 46 // レスポンスを返す 47 return \Response::make($csv, 200, $headers); 48 } 49}
- Provider (/MyProject/app/Providers/CSVServiceProvider.php)
php
1<?php 2 3namespace App\Providers; 4 5use Illuminate\Support\ServiceProvider; 6use App\Repositories\CSV; 7 8class CSVServiceProvider extends ServiceProvider 9{ 10 /** 11 * Bootstrap the application services. 12 * 13 * @return void 14 */ 15 public function boot() 16 { 17 // 18 } 19 20 /** 21 * Register the application services. 22 * 23 * @return void 24 */ 25 public function register() 26 { 27 // クラスとファサードを結合し、サービスコンテナに登録? 28 $this->app->bind( 29 "csv", 30 "App\Repositories\CSV" 31 ); 32 } 33}
- Facade (/MyProject/app/Facades/CSV.php)
php
1<?php 2 3namespace App\Facades; 4use Illuminate\Support\Facades\Facade; 5 6class CSV extends Facade { 7 8 public static function getFacadeAccessor() { 9 return 'csv'; 10 } 11}
- Config (/MyProject/config/app.php)
php
1<?php 2 3return [ 4 // 中略 5 'providers' => [ 6 // 略 7 App\Providers\CSVServiceProvider::class 8 // 略 9 ], 10 'aliases' => [ 11 // 略 12 'CSV' => App\Facades\CSV::class, 13 // 略 14 ], 15];
- Model (/app/User.php)
php
1<?php 2namespace App; 3use Illuminate\Database\Eloquent\Model; 4// 中略 5class User extends Model { 6 // 使用するテーブルの指定 7 protected $table='users'; 8}
- Route (/MyProject/app/Http/routes.php)
php
1<?php 2 // 中略 3 Route::get('/users/index', 'UsersController@index')->name('users.csv'); 4 Route::post('/users/csv', 'UsersController@csv')->name('users.csv');
- Controller (/MyProject/app/Http/Controllers/UsersController.php)
php
1<?php 2namespace App\Http\Controllers; 3 4use Illuminate\Http\Request; 5 6use App\Http\Requests; 7use App\Http\Controllers\Controller; 8 9use CSV; // 追加 10use User; // 追加 11 12class UsersController extends Controller 13{ 14 public function index() 15 { 16 // users全データの取り出し 17 $users = User::all(); 18 // viewを呼び出す 19 return view('users.index', ['users' => $users]); 20 } 21 // 中略 22 23 public function csv() 24 { 25 // usersの全データ取出し 26 $users = User::all(); 27 // 配列の準備 28 $arrayData = []; 29 // Collectionを回してobjectをひとつずつ処理する 30 foreach($users as $user) { 31 // 必要なデータを配列に格納し、その配列を$arrayDataに追加 32 $arrayData[] = [ 33 $user->id, 34 $user->name, 35 $user->phone, 36 ]; 37 } 38 // CSV出力(今回はヘッダーを指定しない) 39 return CSV::download($arrayData, null, 'users.csv'); 40 } 41
- Blade (/MyProject/resources/views/users/index.blade.php)
php
1<!DOCTYPE html> 2<html lang="ja"> 3 <head> 4 <meta charset="utf-8"> 5 <title>MyProject - Users</title> 6 </head> 7 <body> 8 <h1>Users</h1> 9 <table> 10 <tr> 11 <th>id</th> 12 <th>name</th> 13 <th>phone number</th> 14 </tr> 15 @foreach($users as $user) 16 <tr> 17 <td>{{$user->id}}</td> 18 <td>{{$user->name}}</td> 19 <td>{{$user->phone}}</td> 20 </tr> 21 @endforeach 22 </table> 23 24 {!! Form::open(['route' => 'users.csv']) !!} 25 <button type="submit">CSV</button> 26 {!! Form::close() !!} 27 </body> 28</html>
(Controller及びBladeに関しては、こちらでの表示サンプルにつき動作未検証です。ご容赦ください。)
解決策候補
[0] PHP文書の開始タグ及び終了タグ前後のスペースや改行が原因か。
(参考: PHPで出力したCSVファイルの先頭に空行が入ってしまう)
これが関わっているとすると、CSV::download
メソッドの返り値である
\Response::make($csv, 200, $headers);
の処理内部の問題かなと思うのです。
Laravelの根幹部に関わる、Responseクラス(ファサード?)について記述されたファイルが特定できれば精査するのですが…
[1] Classを記述した /MyProject/app/Repositories/CSV.php
に追記。
- ヘッダーディレクティブの削除
(参考: php - fputcsvは.csvの先頭に空の行を挿入します)
具体的には、上記コードの①~④を追記してみました。
そもそもバッファというものについて全く無知なので、検索で見つけたものを書くだけ書いた、という感じです。
元のサイトとはコーディングの動きも違いますし、解消には至りませんでした。
- LF改行を無理やり取り除く
具体的には上記コードのⅠ~Ⅲのいずれかに
preg_replace("/\n/", "", $csv, 1);
を追記しました。最初に現れるLF改行のみを空文字に置き換える、という試みです。
いずれも、レコード間のCRLF改行に影響を与えるのみで、1行目の空白行は残ったままでした。
補足情報
前任者に話を聞くと、コーディング当初このような問題は発生しなかったようです。
いつからか空白行が入るようになったのは観測したと言っていました。具体的にいつ、というのは現状思い出せないそう。
回答2件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/04/03 06:27
2021/04/03 06:29
2021/04/03 06:38
2021/04/06 04:15 編集
2021/04/03 06:46