はじめに
こんにちは。カイザーです。今回は、Laravel AdminにCSVインポート機能を追加する方法について、紹介します。
今回は、以下のようなbooksテーブルとBookモデルに対しての、CSVインポート実装を説明します。
なお、booksテーブルのマイグレーションとモデルの作成方法については説明を省略します。
1 2 3 4 5 6 7 | Schema::create('books', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('title'); $table->string('author'); $table->date('published_at'); $table->timestamps(); }); |
完成形はこのようになります。
下準備
Laravel Adminの導入
Laravel Adminを導入しますが、公式ドキュメント通りなので、細かい説明は省略します。
1 2 3 4 5 6 7 8 9 10 11 | # composerを使用してインストール $ composer require encore/laravel-admin # 設定ファイルをconfig配下にコピーする。(このファイルを変更することで、インストールディレクトリやDB接続、テーブル名を変更可能となる。) $ php artisan vendor:publish --provider="Encore\Admin\AdminServiceProvider" # Laravelプロジェクトに、Laravel Adminを追加し、必要なmigrationが実行される。 $ php artisan admin:install # Bookモデルに対して、Laravel Admin用コントローラを生成する $ php artisan admin:make BookController --model=App\\Book |
「app/Admin/routes.php」に対してルーティングを追加するよう指示されるので、指示通りに追記します。
1 2 3 4 5 6 7 8 9 | Route::group([ 'prefix' => config('admin.route.prefix'), 'namespace' => config('admin.route.namespace'), 'middleware' => config('admin.route.middleware'), ], function (Router $router) { $router->get('/', 'HomeController@index')->name('admin.home'); // 追加したBook用の管理画面コントローラへのルーティングを追記 $router->resource('books', BookController::class); }); |
「http://localhost/admin/books」にアクセスし、ログイン後に管理画面が表示されれば準備完了です。
(初期は、ID・パスワード共にadminです。)
Laravel Excelの導入
Laravel Excelは、ExcelやCSVなどといった、スプレッドシートからDBにインポートしたり、逆にエクスポートしたり出来るライブラリです。
モデルと連携しやすいので、簡単にCSVインポートを実装することができます。
Laravel Adminとは全く別のライブラリなので、単体で使用することも可能です。
1 2 3 4 | # composerでインストール $ composer require maatwebsite/excel # 設定ファイルをconfig配下にコピーする。 $ php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider" |
Laravel Excelの導入はこれで完了です。
Laravel Excelの実装
まず先に、Laravel Excelを使用して、CSVをインポートする機能を実装していきます。
Laravel ExcelではImport系もしくはExport系クラスを作成し、そこにインポートもしくはエクスポートに関する設定を実装していきます。
そして、ExcelクラスのスタティックメソッドにImport/Exportクラスのインスタンスを渡すことで、実際にインポートもしくはエクスポートを行います。
今回はインポート機能を実装するため、まずImportクラスを作成します。
Importクラスの作成
Importクラスは、artisanコマンドから生成できます。
1 | $ php artisan make:import BooksImport --model=Book |
生成された、BooksImportクラスを編集します。
初期状態では、
ToModel
が実装されていますが、今回はCSV上のidカラムと、テーブル上のidカラムが一致している場合は更新し、なけければ新規作成する、という形にしたいため、
OnEachRow
とした上で、
public function onRow(Row $row)
を実装しています。
ToModel
では、返却したモデルがLaravel Excel側で自動的にsaveされますが、
OnEachRow
では、保存処理は行われません。
そのため、保存に関わる処理を、開発者側でハンドリングすることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class BooksImport implements OnEachRow, WithHeadingRow { /** * @param Row $row */ public function onRow(Row $row) { $row = $row->toArray(); // booksテーブルのidで、CSVと合致するものがあれば更新し、なければ新規作成する Book::updateOrCreate( [ 'id' => $row['id'] ], [ 'title' => $row['title'], 'author' => $row['author'], 'published_at' => $row['published_at'], ] ); } } |
EloquentのupdateOrCreateの使い方は、公式ドキュメントを参照してください。
モデルのfillable化
Laravel Excelと直接関係はありませんが、
updateOrCreate()
での複数カラム更新を行うため、モデル側の
$fillable
を実装する必要があります。
デフォルトで、ハッシュによる意図しないカラムを更新されないように、複数カラムの更新がデフォルトで不可能となっています。そのため、
$fillable
を実装する場合も、CSVで更新可能とするカラムのみを指定するようにしましょう。
1 2 3 4 5 6 7 8 | class Book extends Model { protected $fillable = [ 'title', 'author', 'published_at', ]; } |
Laravel Adminの実装
次は、Laravel Admin側の実装です。
Laravel Adminのカスタムツールを使用して、インポートボタンを実装し、Laravel Excelを呼び出して実際にCSVインポートします。
CSVインポートボタンの作成
「app>Admin>Extensions>Tools」に
ImportButton.php
を作成します。
(ディレクトリがなければ、作成してください。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | class ImportButton extends AbstractTool { /** * @var string CSVインポートのURL生成に使用するパス */ private $path; /** * ImportButton constructor. * @param $path */ public function __construct(string $path) { $this->path = $path; } /** * @return string インポートボタンのJavaScript */ private function script() { return <<<EOT $('#import-file').on('change', function () { var isConfirmed = confirm("Is this OK?"); if (!isConfirmed) { return; } var formData = new FormData(); formData.append("file", $("#import-file").prop("files")[0]); formData.append("_token", LA.token); // CSVファイルをアップロードする $.ajax({ method: "POST", url: "/admin/$this->path/csv/import", data: formData, processData: false, contentType: false, success: function (response) { console.log(response); $.pjax.reload("#pjax-container"); toastr.success('Upload Successful'); } }) }); EOT; } public function render() { // インポートボタンのビュー生成 Admin::script($this->script()); return view('admin.tools.import_button'); } } |
render()
で、ツールに表示するインポートボタンのViewを返却します。
また、ツールの動作はjQueryでの実装が推奨されているため、
script()
でインポートボタンを押して、CSVファイル選択後にAjaxで送信する処理を実装しています。
この送信先エンドポイントは実装する必要があるため、後で説明します。
render()
で返却するViewの、Bladeファイルも必要です。
「resources>views>admin>tools」に
import_button.blade.php
というファイル名で作成します。
1 2 3 4 5 6 | <label id="input"> <span class="btn btn-sm btn-twitter"> <span><i class="fa fa-upload"></i> Import</span> <input type="file" id="import-file" name="csvfile" style="display:none"> </span> </label> |
作成したCSVインポートボタンをツール表示する
「app>Admin>Controllers」のBookControllerクラスを編集します。
管理画面のCRUDのうち、一覧表示に関する設定は
grid()
で行います。
1番初めのartisanコマンドで、自動生成されているので、そこに追記します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | class BookController extends AdminController { ・・・ /** * Make a grid builder. * * @return Grid */ protected function grid() { $grid = new Grid(new Book); $grid->column('id', __('Id')); $grid->column('title', __('Title')); $grid->column('author', __('Author')); $grid->column('published_at', __('Published at')); $grid->column('created_at', __('Created at')); $grid->column('updated_at', __('Updated at')); // 作成したImportButtonをツールに表示 $grid->tools(function ($tools) { $tools->append(new ImportButton('books')); }); return $grid; } ・・・ } |
CSVインポートのエンドポイント作成
最後に、CSVインポートボタンからAjaxでアップロードされるCSVの受け口を作ります。
ここで、アップロードされたCSVを受け取り、Laravel Excelに渡してインポートさせます。
この時、先ほど作成したインポートクラスのインスタンスを使用します。
1 2 3 4 5 6 7 8 9 10 11 | class BookController extends AdminController { ・・・ protected function importCsv(Content $content, Request $request) { // アップロードされたCSVファイル $file = $request->file('file'); // インポート Excel::import(new BooksImport(), $file); } } |
このエンドポイントにアクセスするためのルーティングを
routes.php
追加します。
1 2 3 4 5 6 7 8 9 10 | Route::group([ 'prefix' => config('admin.route.prefix'), 'namespace' => config('admin.route.namespace'), 'middleware' => config('admin.route.middleware'), ], function (Router $router) { $router->get('/', 'HomeController@index')->name('admin.home'); $router->resource('books', BookController::class); // CSVインポート用エンドポイントのルーティング追加 $router->post('books/csv/import', 'BookController@importCsv'); }); |
これで完了です!
さいごに
管理画面を爆速で構築できるLaravel Adminですが、カスタマイズは意外と大変でした。
ちなみに、CSVエクスポートは何もしなくても、Laravel Admin単体で出来ます。