【Laravel-Excel】表ファイルを読み込んでそのままDBに保存する方法【CSVインポート】

# :pencil: 概要
Laravel 5.1でCSVインポート機能を実装するにあたり、Laravelのライブラリ[Laravel-Excel](https://github.com/Maatwebsite/Laravel-Excel)を使ったので、その方法について紹介したいと思います。

また、「CSV」とタイトルにありますが、Laravel-Excelではその名の通り、xls等の形式にも対応していますので、Excelのシートごとに処理するということもできます。大変便利です。
http://www.maatwebsite.nl/laravel-excel/docs/import

# 1. Laravel-Excelのインストール
まずはじめにLaravel-Excelのインストールを行います。
composer.jsonに以下のようにインストールするライブラリを追加します。

```json:composer.json
"require": {
    "php": ">=5.5.9",
    "laravel/framework": "5.1.*",
    "maatwebsite/excel": "~2.1.0"
}
```

そして、composer updateを実行し、インストールします。

```
$ composer update
```

インストール後は `config/app.php`を下記の一行を追加します。


```php:config/app.php
'providers' => [
    (中略)
    'Maatwebsite\Excel\ExcelServiceProvider',
],
'aliases' => [
    (中略)
    'Excel' => 'Maatwebsite\Excel\Facades\Excel',
],
```

# 2. 保存先のテーブルを用意する

CSVから取り込んだ値を保存するDBとテーブルを用意します。
サンプルとして下記のようなマイグレーションファイルを作成します。


```
$ php artisan make:migration create_tasks_table
```


```php:database/migrations/2016_12_18_050814_create_tasks_table.php

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTasksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('tasks', function (Blueprint $table) {
            $table->increments('id')->comment('ID');
            $table->string('name')->comment('タスク名');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('tasks');
    }
}

```

マイグレーションを実行し、テーブルを作成します。

```
$ php artisan migrate
```

# 3. インポートするファイルを選択する画面の実装

まずルーティング設定をします。
画面の完成形のイメージとしては、DBにインサートしたいCSVファイル選択画面があって、そこでCSVファイルを選択して保存すると、DBに保存され、その結果が表示される、というものを想定します。

```php:app/Http/routes.php
Route::get('/',  [
    'as' => 'index',
    'uses' => 'CsvController@index'
]);

Route::post('/import', [
    'as' => 'import',
    'uses' => 'CsvController@import'
]);
```

ファイル選択画面は下記のように実装しました。
サンプルなのでデザインが手抜き感満載ですが、ご了承ください:bow:
CSVをアップロードすると、このやることリストにCSVに書かれた内容が追加されます。

![image](https://qiita-image-store.s3.amazonaws.com/0/42852/1508de92-4a31-dd36-ca55-14184ad5f59c.png)

ファイルアップロードのフォーム部分は、下記のようなコードになってます。
他にも記述していますが、今回はそこは省略します。

```html:resources/views/index.blade.php
    <h4>CSVファイルを選択してください</h4>
    <form role="form" method="post" action="{{ route('import') }}" enctype="multipart/form-data">
        {{ csrf_field() }}
        <input type="file" name="csv_file" id="csv_file">
        <div class="form-group">
            <button type="submit" class="btn btn-default btn-success">保存</button>
        </div>
    </form>
```


# 4. CSVをパースしたデータをインポートする処理

さていよいよLaravel-Excelを使う処理です。
基本的にはライブラリを使用するコントローラに`use Excel`を追加し、`Excel::load()`でアップロードされたファイルを読み込みます。
また、`$this->task->firstOrNew`を使用しているため、すでに同じ名前のタスクが登録されていた場合は、それを更新するようにしています。

ちなみに冒頭でも記載したとおり、拡張子が`.xls`のExcelファイルでも問題ありません。

```csv:アップロードするファイルの中身例.csv
name
牛乳を買う
本をよむ
Qiitaに投稿する
```

```php:app/Http/Controllers/CsvController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Controllers\Controller;
use App\Task;
use Excel;

class CsvController extends Controller
{
    protected $task = null;

    public function __construct(Task $task)
    {
        $this->task = $task;
    }
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $data = [];
        $data['tasks'] = $this->task->all();
        return view('index', $data);
    }

    /**
     * CSVインポート
     *
     * @param  Request
     * @return \Illuminate\Http\Response
     */
     public function import(Request $request)
     {
         $file = $request->file('csv_file');
         $reader = Excel::load($file->getRealPath());

         $rows = $reader->toArray();

         foreach ($rows as $row){
             if (!isset($row['name'])) {
                 return redirect()->back();
             }

             $record = $this->task->firstOrNew(['name' => $row['name']]);
             $record->name = $row['name'];
             $record->save();
         }
         return redirect()->action('CsvController@index');
     }
}
```

CSVファイルアップロード後、「保存」をクリックするとリストに無事追加されていることが確認できるはずです！
![image](https://qiita-image-store.s3.amazonaws.com/0/42852/5434606b-2831-4a6b-8fa4-760cfe4d91ff.png)

# おまけ：遭遇したエラーとその解決方法

## :warning: tijsverkoyen/css-to-inline-stylesの2.2.0だと再帰処理のエラーが発生
Laravel-ExcelでCSVファイルを読み込もうとすると、下記のエラーが発生しました。

```
Maximum function nesting level of '100' reached, aborting!
```

エラー内容は、再帰処理が100回以上繰り返されてる時に出るエラーなのですが、今回実装した箇所に問題は無さそうでした。

どこでエラーが出てるのかを調べてみたところ、`vendor/symfony/css-selector/XPath/Extension/ExtensionInterface.php`でエラーが発生していることから、
Laravel-Excelが使用してるライブラリ`tijsverkoyen/css-to-inline-styles`が怪しいのではないかと目処を付けました。
https://github.com/tijsverkoyen/CssToInlineStyles

Laravel-Excelの`composer.json`を見てみると、下記のように指定されており、2系の最新版がインストールされるような指定になっていました。

```json:composer.json
"tijsverkoyen/css-to-inline-styles": "~2.0"
```

実際に`composer update`したときのログを遡ってみると、`2.2.0`がインストールされていました。
どうやらこの最新版である`2.2.0`で発生しているようです。

## :white_check_mark: 2.0.0のバージョンを指定することで解決

試しにバージョンを落として、`2.0.0`の`tijsverkoyen/css-to-inline-styles`をインストールしてみたところ、このエラーが無くなりました。
根本的な原因は解決できていないですが、とりあえずは下記のように`composer.json`でバージョンを`2.0.0`に指定してやると良いと思います。

```json:composer.json
"tijsverkoyen/css-to-inline-styles": "2.0.0"
```

# 参考
+ [Laravel Excel Documentation - Maatwebsite](http://www.maatwebsite.nl/laravel-excel/docs)
+ [Maatwebsite/Laravel-Excel: An eloquent way of importing and exporting Excel and CSV files  for Laravel with the power of PHPExcel](https://github.com/Maatwebsite/Laravel-Excel)
+ Laravel-Excelではなく「goodby/csv」というライブラリを使用する方法もあります。それはこちらの記事が参考になります！
    + [[PHP][Laravel]CSVファイルをアップロードしてDBにInsertする](http://atuweb.net/201511_laravel-csv-upload/)