カテゴリー: BackEnd

Laravel Admin でCSVインポートを実装する

はじめに

こんにちは。カイザーです。今回は、Laravel AdminにCSVインポート機能を追加する方法について、紹介します。

今回は、以下のようなbooksテーブルとBookモデルに対しての、CSVインポート実装を説明します。
なお、booksテーブルのマイグレーションとモデルの作成方法については説明を省略します。

        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を導入しますが、公式ドキュメント通りなので、細かい説明は省略します。

# 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」に対してルーティングを追加するよう指示されるので、指示通りに追記します。

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とは全く別のライブラリなので、単体で使用することも可能です。

# 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コマンドから生成できます。

$ php artisan make:import BooksImport --model=Book

生成された、BooksImportクラスを編集します。
初期状態では、 ToModelが実装されていますが、今回はCSV上のidカラムと、テーブル上のidカラムが一致している場合は更新し、なけければ新規作成する、という形にしたいため、 OnEachRowとした上で、 public function onRow(Row $row)を実装しています。
ToModel では、返却したモデルがLaravel Excel側で自動的にsaveされますが、 OnEachRowでは、保存処理は行われません。
そのため、保存に関わる処理を、開発者側でハンドリングすることができます。

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で更新可能とするカラムのみを指定するようにしましょう。

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を作成します。
(ディレクトリがなければ、作成してください。)

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というファイル名で作成します。

<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コマンドで、自動生成されているので、そこに追記します。

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に渡してインポートさせます。
この時、先ほど作成したインポートクラスのインスタンスを使用します。

class BookController extends AdminController
{
   ・・・
    protected function importCsv(Content $content, Request $request)
    {
        // アップロードされたCSVファイル
        $file = $request->file('file');
        // インポート
        Excel::import(new BooksImport(), $file);
    }
}

このエンドポイントにアクセスするためのルーティングをroutes.php追加します。

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単体で出来ます。

おすすめ書籍

カイザー

シェア
執筆者:
カイザー
タグ: phplaravel

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

3週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

1か月 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前