Laravelで単一入力値による複数カラムに対する曖昧検索の実装方法、他

# 新規登録や更新でMassAssignmentException

[Laradockで簡単アプリ作成しようとしたら、速攻でDB周り／日本語問題で躓いたけど、チャチャッと解決 - Qiita](http://qiita.com/yamazaki/items/d9d3c56f8058ec1e5f65)に続けて、サンプル通りに実装してから動作確認で、新規作成や更新をしてみたら...

`MassAssignmentException`のエラー

 - [Laravel4のEloquent ORMではまった件 - 終電23時15分って早くね？](http://blue-goheimochi.hatenablog.com/entry/2013/07/26/Laravel4%E3%81%AEEloquent_ORM%E3%81%A7%E3%81%AF%E3%81%BE%E3%81%A3%E3%81%9F%E4%BB%B6)
 - [LaravelのfirstOrNewメソッドでMassAssignmentExceptionエラーが発生した時の対応 - Qiita](http://qiita.com/amymd/items/97d000c81a22d8ef0f01)
 - [初めてのLaravel 5.1 : (11) Eloquent マスアサインメント – ララ帳](https://laravel10.wordpress.com/2015/03/02/%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AElaravel-5-11-eloquent-%E3%83%9E%E3%82%B9%E3%82%A2%E3%82%B5%E3%82%A4%E3%83%B3%E3%83%A1%E3%83%B3%E3%83%88/)

結果的には、モデルで指定する`protected $guarded`と`protected $fillable`の内、`$guarded`を指定していたけれど、スペルをミスっていたというオチ
Orz

# カラム追加などのマイグレーションのお作法

とりあえず、CRUDの一通りができるアプリができたので、カラム追加などの拡張をする前に、そのあたりのお作法について、お勉強

カラム変更の前に、ライブラリの追加が必要

`composer require doctrine/dbal`

 - [データベース：マイグレーション # カラム変更 5.4 Laravel](https://readouble.com/laravel/5.4/ja/migrations.html#column-modifiers)
 - [laravelでのマイグレーション作成手順 - Qiita](http://qiita.com/Thiru0000/items/83964c6ff8d8fecc4cfe)
 - [Laravelのマイグレーション - Qiita](http://qiita.com/shosho/items/a5a5839735dfef9214b1)
 - [LaravelのMigrationについて調べた結果 - Qiita](http://qiita.com/qiita-kurara/items/9a34f97a184a1d8c1c35)
 - [初めてのLaravel 5.1 : (8) マイグレーションの作成 – ララ帳](https://laravel10.wordpress.com/2015/02/26/%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AElaravel-5-8-%E3%83%9E%E3%82%A4%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E4%BD%9C%E6%88%90/)
 - [artisan migrate で dropColumnがエラーになりハマる – ララ帳](https://laravel10.wordpress.com/2015/02/27/dropcolumn%E3%81%8C-class-doctrinedbaldriverpdosqlitedriver-not-found-%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%AB%E3%81%AA%E3%82%8B/)

あと、マイグレーションファイル内に記載されているclass名に関する留意点。

 - [laravelのmigrationファイル命名規則 - Qiita](http://qiita.com/naoqoo2/items/91ca0a8db2401059e56c)

テーブル定義を変更する場合は、class名が被らないようにした方がよいらしい。
コマンド言うと、マイグレーションファイルを作成する際に指定する {migration_class_name} の部分。

`php artisan make:migratoin {migration_class_name} --table=users`

これが、プロジェクト全体で被らないようにした方がよい、とのこと。

都度、php artisan migrate しているときには発生しなくて、新規環境で、一括でmigrateを実行したときに発生するっぽいので、しばらくして、新規でDB作り直すかぁ、とかならないと気づかないってことかしら...

だとすると、開発中は、カラムの抜き差し等が頻繁に発生しそうなので、初回リリースのタイミング、もしくは、ある程度、テーブル構造が固まったタイミングで、一度、まっさらな新規作成のマイグレーションファイルを作成するようにして、それ以降、変更管理をすると良いかもしれないなぁ。

つまり、初回リリース時／ある程度、テーブル構造が固まった時に、開発中に行った追加変更のマイグレーションをすべてマージして、一つのマイグレーションファイルに集約するようなイメージ

# 単一入力値による複数カラムに対する曖昧検索の実装

で、この作業のゴールは、複数カラムに対する全文検索をどう実装すれば良いかを知ることだったので、ざっくりの実装方法を理解できたところで、全文検索についてクリッピング

## アナログな実装

 - [Lravel5で複数Keyword検索+paginate (Eloquent編) - Qiita](http://qiita.com/zaburo/items/fe1947ebcf530e8f3773)
 - [Laravel where条件の使い方 | Webエンジニアブログ](http://dim5.net/laravel/using-conditional-where.html)
 - [【Laravel】複数条件の検索フォームを作る方法【whereRaw】](https://manablog.org/laravel_multiple_search_form/)
 - [【Laravel】ページネーション付き検索フォームを作成する方法](https://manablog.org/laravel-search-with-pagination/)
 - [mysql - Laravel multiple column eloquent search query - Stack Overflow](https://stackoverflow.com/questions/25599540/laravel-multiple-column-eloquent-search-query)

標準的な機能を利用して、実装するなら、検索対象のカラムの数だけ、or検索を指定すれば良さそう。

`Model::where('target_column1', 'LIKE', "%$query_keyword%")->orWhere('target_column2', 'LIKE', "%$query_keyword%")`

## MySQLの生成列とFULLTEXTで実装

もっと今時な？実装をするなら、MySQLの5.7.6から利用できるようになったgenerated columns、日本語訳だと、生成列とか仮想列とか呼ばれているカラム定義で、これにFULLTEXTインデックスを定義すれば、行けそう

 - [まだ日本語全文検索で消耗してるの？ - Qiita](http://qiita.com/ArimaRyunosuke/items/7b74316c03ef02276675)
 - [JSON型にindexも！MySQL 5.7のGenerated Columnsの可能性について探る - UUUM攻殻機動隊](http://system.blog.uuum.jp/entry/mysql-generated-columns)
 - [InnoDBにおける仮想列および効果的な"関数インデックス"(MySQL Server Blogより) | Yakst](https://yakst.com/ja/posts/2881)
 - [日々の覚書: MySQL 5.7.8からInnoDBのgenerated columnは実体を取らずにインデックスを作れるようになった](https://yoku0825.blogspot.jp/2015/08/mysql-578innodbgenerated-column.html)
 - [MySQL 5.7で生成カラムを使って関数INDEXを作成する - Qiita](http://qiita.com/hmatsu47/items/128ece7276e4deac1477)
 - [Migration column modifiers](https://laracasts.com/discuss/channels/laravel/migration-column-modifiers)
 - [laravel - How to add a virtual column with the schema builder? - Stack Overflow](https://stackoverflow.com/questions/27749887/how-to-add-a-virtual-column-with-the-schema-builder)
 - [FULLTEXT Indexes at migrations](https://laracasts.com/discuss/channels/general-discussion/fulltext-indexes-at-migrations)
 - [MySQL :: MySQL 5.7 Reference Manual :: 12.9.8 ngram Full-Text Parser](https://dev.mysql.com/doc/refman/5.7/en/fulltext-search-ngram.html)

留意点としては、generated columnを指定する際、生成列のデータの持ち方が初期値はVIRTUALなので計算後の値が格納されず、FULLTEXTのインデックスを設定できないため、STOREDを設定する必要がある

具体的には、マイグレーションファイルに、以下のような記述をするのかな。
(参考ページのサンプルを丸っと転機して少し修正ｗ)

```
# カラム定義
Schema::create('sales', function (Blueprint $table) {
  $table->string('name');
  $table->double('price_eur');
  $table->integer('amount');
  $table->double('total_eur')->storedAs('price_eur * amount');
  $table->double('total_usd')->storedAs('total_eur * xrate');
  $table->double('xrate');
});

# ALTER TABLE か CREATE INDEX で追加設定
DB::statement('ALTER TABLE `sales` ADD FULLTEXT INDEX `ft_idx_total_eur` (`total_eur`) WITH PARSER ngram');
DB::statement('CREATE FULLTEXT INDEX `ft_idx_total_eur` ON `sales` (`total_eur`) WITH PARSER ngram');
```

generated columnsは、サブクエリに対応していないことによる影響の有無を、事前に検討しておいた方が良さそう

ちょっとクリティカルな問題として、性能に難あり、らしい

 - [What's New in MySQL 5.7 InnoDB : スライド48 : 性能について](https://www.slideshare.net/nippondanji/whats-new-in-mysql-57-innodb/48)
 - [MroongaとPGroonga : スライド10 : 速さ](https://www.slideshare.net/kou/mysql-and-postgresql-and-japanese-full-text-search/10)

性能に課題はありそうだが、数百〜数千レコーデあれば、FULLTEXTを利用して、Laravelから全文検索する方法

 - [[Laravel] FULLTEXTインデックスを使った全文検索 – 端くれプログラマの備忘録](http://www.84kure.com/blog/2016/04/14/laravel-fulltext%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9F%E5%85%A8%E6%96%87%E6%A4%9C%E7%B4%A2/)
 - [Laravel + MySQL5.7 で日本語全文検索をする方法とちょっとした注意点 - Qiita](http://qiita.com/niisan-tokyo/items/33c254bf8c4da3379ad1)

いずれも、`MATCH ... AGAINST ...`で検索する方法

## Laravelが提供している全文検索の機能で実装

LaravelのScoutのことがよくわからなかったんだけど、これ読んで、たぶん、理解できた。

 - [Laravel Scout で AWSのElasticSearch を使う - Qiita](http://qiita.com/kawax/items/abd609ad83964ece508e)
 - [Elastic Driver for Laravel Scout - ErickTamayo/laravel-scout-elastic - GitHub﻿](https://github.com/ErickTamayo/laravel-scout-elastic)
 - [Scout Elasticsearch Driver - babenkoivan/scout-elasticsearch-driver - GitHub﻿](https://github.com/babenkoivan/scout-elasticsearch-driver)
  - [babenkoivan/scout-elasticsearch-driver - Packagist﻿](https://packagist.org/packages/babenkoivan/scout-elasticsearch-driver)

全文検索のためのインデックスは外部で処理して、フレームワークからは気軽にメソッドで検索できるようにしたものってことかな

対象カラムは、各モデルごとに`toSearchableArray`メソッドで定義できるってことかな...。

ただ、これも複数のテーブルにまたがるような検索や、カラムの値はIDでしか持っていないような項目を対象にした場合は、工夫が必要そう...。

# おまけ

ちょっと話が逸れるけど、結合先のテーブルを検索条件に加えるという話も出てきそうなので、メモ

 - [Laravel eloquant の with にて結合先テーブルのカラムを where 条件にしたい - スタック・オーバーフロー](https://ja.stackoverflow.com/questions/14572/laravel-eloquant-%E3%81%AE-with-%E3%81%AB%E3%81%A6%E7%B5%90%E5%90%88%E5%85%88%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%AE%E3%82%AB%E3%83%A9%E3%83%A0%E3%82%92-where-%E6%9D%A1%E4%BB%B6%E3%81%AB%E3%81%97%E3%81%9F%E3%81%84)