Laravelでテーブルの内容をcsvにてダウンロード(エクスポート)する方法
642 回閲覧されました
みなさんこんにちは、jonioです。
今回はLaravelでテーブルのカラムの値をcsvとしてダウンロード(エクスポート)する方法を解説します。
かなり調べたのですがもしかしたら説明した内容が間違っているかもしれませんのでご了承下さい。
目次
Laravelのバージョン
8で実装しましたが8以上でも大丈夫な気がします。
データベース
MySQLを使っています。
デモ画像
テーブルの内容を表示してその下にcsvをダウンロードするボタンを設置します。(Login・Registerがありますが今回の実装に関係ないので実装しなくていいです)
「CSVエクスポート」をクリックするとcsvに追加するレコードの期間指定ができます。
エクスポートの開始日と終了日はテーブルに記載されています。(下記赤枠)
エクスポートの開始日と終了日を指定して「ダウンロード」をクリックするとcsvがダウンロードできます。
テーブル
今回は3つのテーブルを用意してリレーションでテーブルを繋ぎますが下記になります。
- contacts
- conditions
- designs
それぞれのテーブルのカラムをざっくり説明します。
contactsテーブル
カラムは以下になります。
- id
- condition_id(conditionsテーブルとのリレーション用)
- design_id(designsテーブルとのリレーション用)
- email(メール)
- tel_number(電話番号)
- zip_code(郵便番号)
- pref(都道府県名)
- city(市名)
- street(町村名)
- surname(お客名)
- user(登録者名)
- memo(メモ)
- private_memo(csvに出力しない他の人に見せたくないメモ)
- status
- updated_at
- created_at
conditionsテーブル
カラムは以下になります。
- id
- name(建物の状態)
- created_at(使用しません)
- updated_at(使用しません)
designsテーブル
カラムは以下になります。
- id
- name(建物のデザイン)
- created_at(使用しません)
- updated_at(使用しません)
1つのテーブルからcsvを作成しない理由
テーブルを3つ用意してcontactsテーブルをメインにしてconditionsテーブル・designsテーブルをleft join(リレーション)で繋ぐのですがcontactsのテーブルのカラムにしてcsvにした方が煩雑にならなくていいと思いませんか?
わざわざテーブルを分けているのは今回はしませんがconditionsテーブル・designsテーブルを他の状況で使う可能性があるのを考慮した上です。
とりあえずcontactsテーブルの内容をcsvにしたかったらリレーションは無視して実装して下さい。
前提条件
Laravelはインストールされているとします。
前置きがかなり長くなりましたがコードの解説をします。
テーブルの作成
まずはcontactsテーブル・designsテーブル・conditionsテーブルを作成します。
下記のコマンドを叩きます。
php artisan make:model Contact -m
php artisan make:model Design -m
php artisan make:model condition -m
マイグレーションファイルに記述します。
下記はcontactsテーブルのマイグレーションファイルです。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateContactsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('contacts', function (Blueprint $table) {
$table->id();
//ここから追加
$table->integer('condition_id')->unsigned();
$table->integer('design_id')->unsigned();
$table->string('email');
$table->string('tel_number');
$table->string('fax_number');
$table->integer('zipcode');
$table->string('pref');
$table->string('city');
$table->string('street');
$table->string('surname');
$table->string('name');
$table->string('memo');
$table->string('private_memo');
$table->tinyinteger('status')->default(1);
//ここまで追加
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('contacts');
}
}
下記はdesignsテーブルのマイグレーションファイルです。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateDesignsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('designs', function (Blueprint $table) {
$table->id();
$table->string('name'); //この行を追加
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('designs');
}
}
下記はconditionsテーブルのマイグレーションファイルです。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateConditionsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('conditions', function (Blueprint $table) {
$table->id();
$table->string('name'); //この行を追加
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('conditions');
}
}
記述が終わったら下記のコマンドでカラムをテーブルに反映させます。
php artisan migrate
テーブルにデータの挿入
フォームを使ってテーブルに値を追加してもいいのですが今回はそこは重要ではないのでSeederでデータを挿入します。
Seederを作成する為に下記のコマンドを叩きます。
php artisan make:seeder ContactsTableSeeder
php artisan make:seeder ConditionsTableSeeder
php artisan make:seeder DesignsTableSeeder
contactsテーブル用のSeederに下記の記述をします。
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class ContactsTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
//ここから追加
DB::table('contacts')->insert([
[
'id' => 1,
'condition_id' => 1,
'design_id' => 1,
'email' => 'test@mail.com',
'tel_number' => '090-1234-5678',
'fax_number' => '1234-4567-7890',
'zipcode'=> 1600001,
'pref'=> '東京都',
'city'=> '新宿区',
'street'=> '片町',
'surname'=> 'ユーザー',
'name'=> '1',
'memo'=> '営業部への伝言',
'private_memo' => 'マーケティング部のメモ'
],
[
'id' => 2,
'condition_id' => 2,
'design_id' => 2,
'email' => 'test@mail.com',
'tel_number' => '090-1234-5678',
'fax_number' => '1234-4567-7890',
'zipcode'=> 1600001,
'pref'=> '東京都',
'city'=> '新宿区',
'street'=> '片町',
'surname'=> 'ユーザー',
'name'=> '2',
'memo'=> '営業部への伝言',
'private_memo' => 'マーケティング部のメモ'
],
[
'id' => 3,
'condition_id' => 3,
'design_id' => 3,
'email' => 'test@mail.com',
'tel_number' => '090-1234-5678',
'fax_number' => '1234-4567-7890',
'zipcode'=> 1600001,
'pref'=> '東京都',
'city'=> '新宿区',
'street'=> '片町',
'surname'=> 'ユーザー',
'name'=> '3',
'memo'=> '営業部への伝言',
'private_memo' => 'マーケティング部のメモ'
],
[
'id' => 4,
'condition_id' => 4,
'design_id' => 4,
'email' => 'test@mail.com',
'tel_number' => '090-1234-5678',
'fax_number' => '1234-4567-7890',
'zipcode'=> 1600001,
'pref'=> '東京都',
'city'=> '新宿区',
'street'=> '片町',
'surname'=> 'ユーザー',
'name'=> '4',
'memo'=> '営業部への伝言',
'private_memo' => 'マーケティング部のメモ'
],
[
'id' => 5,
'condition_id' => 5,
'design_id' => 5,
'email' => 'test@mail.com',
'tel_number' => '090-1234-5678',
'fax_number' => '1234-4567-7890',
'zipcode'=> 1600001,
'pref'=> '東京都',
'city'=> '新宿区',
'street'=> '片町',
'surname'=> 'ユーザー',
'name'=> '5',
'memo'=> '営業部への伝言',
'private_memo' => 'マーケティング部のメモ'
],
]);
//ここまで追加
}
}
conditionsテーブル用のSeederに下記の記述をします。
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class ConditionsTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
//ここから追加
DB::table('conditions')->insert([
[
'id' => 1,
'name' => '平屋',
],
[
'id' => 2,
'name' => '2・3階建て',
],
[
'id' => 3,
'name' => '~40坪台',
],
[
'id' => 4,
'name' => '50~60坪台',
],
[
'id' => 5,
'name' => '70~坪以上',
],
]);
//ここまで追加
}
}
designsテーブル用のSeederに下記の記述をします。
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class DesignsTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
//ここから追加
DB::table('designs')->insert([
[
'id' => 1,
'name' => 'モダン',
],
[
'id' => 2,
'name' => 'エレガント',
],
[
'id' => 3,
'name' => 'ウッディ',
],
[
'id' => 4,
'name' => 'クラシック',
],
[
'id' => 5,
'name' => '和風',
],
]);
//ここまで追加
}
}
DatabeSeeder.phpのコードを下記にします。
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
// \App\Models\User::factory(10)->create();
//ここから追加
$this->call(ContactsTableSeeder::class);
$this->call(ConditionsTableSeeder::class);
$this->call(DesignsTableSeeder::class);
//ここまで追加
}
}
記述が終わったら下記のコマンドを叩きます。
php artisan db:seed
成功したらテーブルにデータが追加されているのを確認できます。
下記はcontactsテーブルです。
下記はconditionsテーブルです。
下記はdesignsテーブルです。
ルーティング・コントローラー
web.phpのコードを下記にします。
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
// Route::get('/', function () {
// return view('welcome');
// });
Auth::routes();
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
Route::get('/', 'ContactController@index')->name('index');
Route::post('csv/export', 'ContactController@csvExport')->name('contact.csv.export');
ContactControllerがないので下記のコマンドで作成します。
php artisan make:controller ContactController
ContactController.phpのコードを下記にします。(csvExportアクションはまだ記述しません)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Contact; //この行を追加
class ContactController extends Controller
{
//ここから追加
public function index(){
$contacts = Contact::select('contacts.*', 'c.name AS condition_name', 'd.name AS design_name')
->where('contacts.status', 1)
->leftjoin('conditions AS c', 'contacts.condition_id', '=', 'c.id')
->leftjoin('designs AS d', 'contacts.design_id', '=', 'd.id')
->orderBy('contacts.created_at', 'DESC')
->get();
return view('index', compact('contacts'));
}
//ここまで追加
}
15行目〜20行目はindex.blade.phpで表示する為のデータを取得しています。
contactsテーブルにconditionsテーブル・designsテーブルをleft joinしていますがそれについて軽く説明します。
テーブルを繋ぐ条件は下記になります。
- conditionsテーブルとの結合 : contactsテーブルのcondition_idカラムとconditionsテーブルのidカラム
- designsテーブルとの結合 : contactsテーブルのdesign_idカラムとdesignsテーブルのidカラム
そしてcontactsテーブルから取得するカラムは下記になります。
- 元々のcontactsテーブルのカラム
- conditionsテーブルのnameカラム(condition_nameに変えている)
- designsテーブルのnameカラム(design_nameに変えている)
それではindexアクションで取得しているcontactsテーブルの情報をトップページで表示します。
トップページの表示
index.blade.phpのコードを下記にします。
@extends('layouts.app')
@section('content')
<div class="container">
<table class="table">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">建物の条件</th>
<th scope="col">建物のデザイン</th>
<th scope="col">名前</th>
<th scope="col">住所</th>
<th scope="col">電話番号</th>
<th scope="col">Fax</th>
</tr>
</thead>
<tbody class="table-stripes-row-tbody">
@foreach($contacts as $c)
<tr>
<td>{{ $c->id }}</td>
<td>{{ $c->condition_name }}</td>
<td>{{ $c->design_name }}</td>
<td>{{ $c->surname }}{{ $c->name }}</td>
<td>{{ $c->zipcode }}<br>{{ $c->pref }}{{ $c->city }}{{ $c->street }}</td>
<td>{{ $c->tel_number }}</td>
<td>{{ $c->fax_number }}</td>
</tr>
@endforeach
</tbody>
</table>
<a href="#modal" class='btn btn-primary csv-modal m-3'>CSVエクスポート</a>
</div>
@endsection
これでトップページにアクセスすると下記の表示になります。
次はcsvをダウンロードできるようにします。
csvのダウンロード(コントローラー)
ContactController.phpのコードを下記にします。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Contact;
use Symfony\Component\HttpFoundation\StreamedResponse; //この行を追加
class ContactController extends Controller
{
public function index(){
$contacts = Contact::select('contacts.*', 'c.name AS condition_name', 'd.name AS design_name')
->where('contacts.status', 1)
->leftjoin('conditions AS c', 'contacts.condition_id', '=', 'c.id')
->leftjoin('designs AS d', 'contacts.design_id', '=', 'd.id')
->orderBy('contacts.created_at', 'DESC')
->get();
return view('index', compact('contacts'));
}
//ここから追加
public function csvExport(Request $request) {
$post = $request->all();
$response = new StreamedResponse(function () use ($request, $post) {
$stream = fopen('php://output','w');
$contact = new Contact();
stream_filter_prepend($stream, 'convert.iconv.utf-8/cp932//TRANSLIT');
fputcsv($stream, $contact->csvHeader());
$results = $contact->getCsvData($post['start_date'], $post['end_date']);
if (empty($results[0])) {
fputcsv($stream, [
'データが存在しませんでした。',
]);
} else {
foreach ($results as $row) {
fputcsv($stream, $contact->csvRow($row));
}
}
fclose($stream);
});
$response->headers->set('Content-Type', 'application/octet-stream');
$response->headers->set('content-disposition', 'attachment; filename='. $post['start_date'] . '〜' . $post['end_date'] . 'お問い合わせ一覧.csv');
return $response;
}
//ここまで追加
}
8行目ですがStreamedResponseクラスを使うことでcsvのダウンロードができるようになります。
27行目で全ての入力データを配列で取得します。
28行目はStreamedResponseクラスを使う時の書き方で引数が2つありますが2つ目にcsvに出力する情報が入ります。
30行目でcsvファイルに書き込みができるようにします。
33行目はテーブルの内容をcsvファイルに書き込んだ時に文字化けするのを防ぐ為にあります。
35行目でcsvのヘッダーの部分の書き込みをします。(下記赤枠)
「csvHeader」はテーブルにアクセスするのであとでContactモデルに記述します。
37行目は期間指定してcsvファイルに書き込む為の記述ですが「getCsvData」はあとでContactモデルに記述します。
「start_date」・「end_date」はindex.blade.phpにあとで記述しますが期間のスタートと終わりのname属性です。
39行目〜47行目は指定期間にデータがある時とない時の記述です。
39行目〜42行目はテーブルに該当するデータがない時で39行目の「$results[0]」は該当するデータがある時は下記の赤枠です。
「$results[0]」は1つ目のレコードという意味ですが該当するデータがない時なので「[0]」の数字の部分は何でもいいです。
ただ1000とか極端な値にすると該当するデータがあっても1001番目のデータがなくて該当するデータがないと判断される可能性があるので一番最初のデータである0にした方がいいです。
ちなみに該当するデータがない時にダウンロードしたcsvは下記の表示になります。
44行目〜46行目は該当するデータがある時で45行目よりテーブルの内容を1レコードずつcsvに書き込みますが「csvRow」はContactモデルに記述します。
48行目はcsvファイルの書き込みを終了しています。
50行目はcsvファイルをダウンロードできるようにします。
51行目はダウンロードするファイル名を指定していて下記はファイル名になります。
filename='. $post['start_date'] . '〜' . $post['end_date'] . 'お問い合わせ一覧.csv'
「start_date」・「end_date」はcsvをダウンロードする時に指定した期間のスタートと終わりの日付です下記の赤枠です。(モーダルの部分はあとで実装します)
csvのダウンロード(モデル)
csvHeader・getCsvData・csvRowをContact.phpに記述します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Contact extends Model
{
use HasFactory;
public function getCsvData($start = null, $end = null)
{
$start_date = str_replace('年', '-', $start);
$start_date = str_replace('月', '-', $start_date);
$start_date = str_replace('日', '', $start_date);
$end_date = str_replace('年', '-', $end);
$end_date = str_replace('月', '-', $end_date);
$end_date = str_replace('日', '', $end_date);
$data = $this->select(
'contacts.id',
'cd.name AS condition_name',
'd.name AS design_name',
'contacts.surname',
'contacts.name',
'contacts.zipcode',
'contacts.pref',
'contacts.city',
'contacts.street',
'contacts.tel_number',
'contacts.fax_number',
'contacts.email',
'contacts.memo'
)
->leftJoin('conditions AS cd', 'contacts.condition_id','=','cd.id')
->leftJoin('designs AS d', 'contacts.design_id','=','d.id')
->where('contacts.status', 1)
->whereBetween('contacts.created_at', [$start_date. ' 00:00:00', $end_date. ' 23:59:59'])
->orderBy('contacts.created_at', 'ASC')
->get();
return $data;
}
public function csvHeader(){
return [
'id',
'建築条件',
'建築物デザイン',
'性',
'名',
'tel',
'fax',
'郵便番号',
'都道府県',
'市区町村',
'以降の住所',
'メールアドレス',
'メモ'
];
}
public function csvRow($row){
return [
$row->id,
$row->condition_name,
$row->design_name,
$row->surname,
$row->name,
$row->tel_number,
$row->fax_number,
$row->zipcode,
$row->pref,
$row->city,
$row->street,
$row->email,
$row->memo,
];
}
}
12行目のgetCsvDataメソッドでテーブルからcsvとして使うデータを取得しています。
csvに表示するデータの期間指定をすると日時が例えば「2023年12月28日」のように「年」・「月」・「日」が付きますがテーブルの日時を見ると「年」・「月」・「日」が付いてなくて「2023-12-28」となっています。
だから今のままではテーブルからデータを取得できないのですが「年」・「月」を「-」に変換して「日」をなくす為に14行目〜19行目があります。
これでテーブルのデータをcsvに出力する機能が実装できたので出力するcsvの期間指定をするモーダル(下記参照)を実装します。
csvのダウンロード(ビュー)
モーダルを開くのに「remodal」というライブラリを使い開始日と終了日を選択するのに「flatpickr」というライブラリをCDNを使って実装します。
app.blade.phpを下記にします。
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
//ここから追加
<link href="https://cdnjs.cloudflare.com/ajax/libs/remodal/1.1.1/remodal-default-theme.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/remodal/1.1.1/remodal.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css" rel="stylesheet">
<script src="http://code.jquery.com/jquery-3.3.1.min.js" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/remodal/1.1.1/remodal.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr" defer></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.9/dist/l10n/ja.js" defer></script>
<script src="{{ asset('js/index.js') }}" defer></script>
//ここまで追加
</head>
<body>
<div id="app">
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav me-auto">
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ms-auto">
<!-- Authentication Links -->
@guest
@if (Route::has('login'))
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
@endif
@if (Route::has('register'))
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
</li>
@endif
@else
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::user()->name }}
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
</div>
</nav>
<main class="py-4">
@yield('content')
</main>
</div>
</body>
</html>
そして「Laravelのプロジェクト > public > js」の下にindex.jsを作成してコードを下記にします。
$(function(){
$('.datepicker').flatpickr(
{
dateFormat: "Y年m月d日",
locale: "ja"
}
);
});
そしてindex.blade.phpのコードを下記にします。
@extends('layouts.app')
@section('content')
<div class="container">
<table class="table">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">建物の条件</th>
<th scope="col">建物のデザイン</th>
<th scope="col">名前</th>
<th scope="col">住所</th>
<th scope="col">電話番号</th>
<th scope="col">Fax</th>
</tr>
</thead>
<tbody class="table-stripes-row-tbody">
@foreach($contacts as $c)
<tr>
<td>{{ $c->id }}</td>
<td>{{ $c->condition_name }}</td>
<td>{{ $c->design_name }}</td>
<td>{{ $c->surname }}{{ $c->name }}</td>
<td>{{ $c->zipcode }}<br>{{ $c->pref }}{{ $c->city }}{{ $c->street }}</td>
<td>{{ $c->tel_number }}</td>
<td>{{ $c->fax_number }}</td>
</tr>
@endforeach
</tbody>
</table>
<a href="#modal" class='btn btn-primary csv-modal m-3'>CSVエクスポート</a>
//ここから追加
<div class="remodal w80" data-remodal-id="modal">
<form action="{{ route('contact.csv.export') }}" method="POST">
@csrf
<div class='row mb-3'>
<div class="col-md-5">
<label class='h4'>エクスポートの開始日</label>
</div>
<div class="col-md-2 h2 d-flex justify-content-center align-items-center"></div>
<div class="col-md-5">
<label class='h4'>エクスポートの終了日</label>
</div>
</div>
<div class='row mb-3'>
<div class="col-md-5">
<input class="form-control" type="datetime" placeholder="出力開始日" name="start_date">
</div>
<div class="col-md-2 h2 d-flex justify-content-center align-items-center">〜</div>
<div class="col-md-5">
<input class="form-control" type="datetime" placeholder="出力終了日" name="end_date">
</div>
</div>
<button data-remodal-action="cancel" class="btn btn-secondary">キャンセル</button>
<button type='submit' class="btn btn-primary">ダウンロード</button>
</form>
</div>
//ここまで追加
</div>
@endsection
32行目と36行目の「modal」ですが同じにしないとモーダルが動作しないので気をつけて下さい。
50行目の「start_date」と54行目の「end_date」はContact.php・ContactController.phpで登場したstart_date・end_dateと同じです。
これで期間指定をしてcsvをダウンロードする機能が完成しました。