OctoberCMSで標準搭載のインポート/エクスポート機能を使ってみた

OctoberCMSには、CSVを使ったデータベースへのインポート・エクスポート機能が標準で備わっています。
ということであんまり日本語の文献がなかったので、備忘も兼ねて設定方法を共有。

基本的には
https://octobercms.com/docs/services/request-input
ここに書いてある通りにやればいいだけなので、英語が読める諸兄はこっち見たほうが早いです。

今回は、ブログの記事中に表示させたい「お客様の声」みたいなものを想定して、xxxx.xxxxというプラグインのHappyCustomerというModelをいじっていきます。ということで、コントローラやディレクトリなどの名前については読み替えながら参照してください。

#手順

###1. コントローラをいじる

/components/controllers配下にあるHappyCustomers.php（コントローラ。ファイル名は各々で違うと思います）を開いたら、

```HappyCustomers.php
public $implement = [
     'Backend.Behaviors.ImportExportController',
];

public $importExportConfig = 'config_import_export.yaml';
```

↑
こんな感じで\$implementと\$importExportConfigを編集してあげてください。
（コントローラの作り方については説明しないので、適宜Builderを使うかartisanコマンドをご利用のこと。）

###2. config_import_export.yamlを作る

/components/controller/happycustomers配下にconfig_import_export.yamlを作ります。これがインポート・エクスポート画面の設定になります。

ここでは必要最低限のものしか設定しないので、他のパラメータについては上述した公式のドキュメントを参照してください。

```config_import_export.yaml
import:
    title: お客様の声インポート
    modelClass: xxxx\xxxx\Models\HappyCustomerImport
    list: $/xxxx/xxxx/models/happycustomer/columns.yaml

export:
    title: お客様の声エクスポート
    modelClass: xxxx\xxxx\Models\HappyCustomerExport
    list: $/xxxx/xxxx/models/happycustomer/columns.yaml
```

###3. インポート・エクスポート画面のhtmを追加する

/components/controller/happycustomer配下にインポート・エクスポートのバックエンド用画面を作っていきます。ここではimport.htm, export.htmとして作っていきます。

```import.htm
<?php Block::put('breadcrumb') ?>
    <ul>
        <li><a href="<?= Backend::url('xxxx/xxxx/happycustomers') ?>">HappyCustomers</a></li>
        <li><?= e($this->pageTitle) ?></li>
    </ul>
<?php Block::endPut() ?>

<?php if (!$this->fatalError): ?>

    <?= Form::open(['class' => 'layout']) ?>

        <div class="layout-row">
            <?= $this->importRender() ?>
        </div>

        <div class="form-buttons">
            <button
                type="submit"
                data-control="popup"
                data-handler="onImportLoadForm"
                data-keyboard="false"
                class="btn btn-primary">
                Import records
            </button>
        </div>

    <?= Form::close() ?>

<?php else: ?>

    <p class="flash-message static error"><?= e($this->fatalError) ?></p>
    <p><a href="<?= Backend::url('xxxx/xxxx/happycustomers') ?>" class="btn btn-default">Return to Happycustomers list</a></p>

<?php endif ?>

```

```export.htm
<?php Block::put('breadcrumb') ?>
    <ul>
        <li><a href="<?= Backend::url('xxxx/xxxx/happycustomers') ?>">HappyCustomers</a></li>
        <li><?= e($this->pageTitle) ?></li>
    </ul>
<?php Block::endPut() ?>

<?php if (!$this->fatalError): ?>

    <?= Form::open(['class' => 'layout']) ?>

        <div class="layout-row">
            <?= $this->exportRender() ?>
        </div>

        <div class="form-buttons">
            <button
                type="submit"
                data-control="popup"
                data-handler="onExportLoadForm"
                data-keyboard="false"
                class="btn btn-primary">
                Export records
            </button>
        </div>

    <?= Form::close() ?>

<?php else: ?>

    <p class="flash-message static error"><?= e($this->fatalError) ?></p>
    <p><a href="<?= Backend::url('xxxx/xxxx/happycustomers') ?>" class="btn btn-default">Return to Happycustomers list</a></p>

<?php endif ?>

```

ほぼ公式ドキュメントのコピペです。適宜コンポーネント名とかはいじってください。

###4. インポート・エクスポート用のモデルを作成する

命名は安直に、ここではHappyCustomerImport.php, HappyCustomerExport.phpという2つのモデルを/models配下に作っていきます。スカフォールドは使わず、普通にファイル作成してしまったほうが早い気がします。

```HappyCustomerImport.php
<?php namespace xxxx\xxxx\Models;

class HappyCustomerImport extends \Backend\Models\ImportModel
{
    /**
     * @var array The rules to be applied to the data.
     */
    public $rules = [];

    public function importData($results, $sessionKey = null)
    {
        foreach ($results as $row => $data) {

            try {
                $happycustomer = new HappyCustomer;
                $happycustomer->fill($data);
                $happycustomer->save(); //インポートの際にデータへロジックを適用したければここで書く

                $this->logCreated();
            }
            catch (\Exception $ex) {
                $this->logError($row, $ex->getMessage());
            }

        }
    }
}
```

```HappyCustomerExport.php
<?php namespace xxxx\xxxx\Models;

class HappyCustomerExport extends \Backend\Models\ExportModel
{
    public function exportData($columns, $sessionKey = null)
    {
        $happycustomers = HappyCustomer::all();
        $happycustomers->each(function($happycustomer) use ($columns) {
            $happycustomer->addVisible($columns);
        });
        return $happycustomers->toArray();
    }
}
```

またも公式ドキュメントのほぼ丸パクリ。
以上でインポート・エクスポートの機能有効化は完了。適当に/happycustomers/importとか/happycustomers/exportとかへBackendからアクセスすればインポート・エクスポート画面が見られます（例: http://localhost/backend/xxxx/xxxx/happycustomers/import ）。

あとは適宜、_list_toolbar.htmをいじってインポート・エクスポートのボタンを表示させたり、必要なら各種ロジックなどをゴリっと書けばいいと思います。

#ハマったポイント
- インポート時に謎エラー。getStackTraceAsStringしたらfill()でエラーが出てることが判明
    - Modelでfillableを定義しておく必要あり。BuilderとかでModel作ってると設定漏れる 

- Windows環境でなぜかCSVのパースがきれいにできない（UTF-8）。謎
    - とりあえず適当にGCEで立てたLAMP環境を使ったら余裕で通った

- （若干余談だけど）Builderを使ってコントローラを作ろうとしたらなぜか権限エラー？みたいなメッセージが吐かれる
    - Plugin.phpが置かれている場所にPlugin.yamlなるファイルを作ったら治った。
    - 参考: https://qiita.com/pikanji/items/dc2f5180c4ffe8829752

#おわりに
OctoberCMS、Laravelベースなのでコード書きやすいし高機能で扱いやすいけど、初学者の私としてはもう少し日本語ドキュメント増えてほしい感があります。