#何がしたい?
UNIONを使いたくない
例えば、楽天の情報とヤフーショッピングの商品情報を扱う場合。そのまま作ったら、RakutenとYahooShoppingという2つのモデルを作り、rakutensとyahoo_shoppingsという2つのテーブルを割り当てることになります。楽天とヤフーでは情報ソースが違うので、取得ロジックも違い、別々のモデルを持つことは理にかなっています。
では、「楽天とヤフーショッピングの全アイテムを一覧する」といった横断的なクエリが必要になったらどうしますか?
$rakuten = Rakuten::whereIn('item_id',$idarray)->where('availability','>',0);
$yahoo = YahooShopping::whereIn('item_id',$idarray)->where('availability','>',0);
$union = $rakuten->UnionAll($yahoo);
$records = $union->orderBy('released_at','desc')->paginate(30);
まさにUNIONの教科書的な案件ですが、これ、レコードの数が膨大になるとパフォーマンスが結構厳しくなります(ちょっとソートしたいと思っただけで全レコードをチェックしたり...)。そもそも、取得ロジックは違うけど、取得データ(ストアロジック)はよく似ているのでデータベースはまとめちゃったほうが理にかなっています。
個々のモデルではDBの共通化を意識したくない
ではDBは1つにまとめましょ。Eloquentではテーブル名を任意に指定することができます。
class Rakuten extends Model
{
protected $table = 'shopping_items';
class YahooShopping extends Model
{
protected $table = 'shopping_items';
でもこうすると、楽天とYahooのレコードはどうやって区別しましょう? data_src というカラムを持たせて、Rakuten、Yahoo Shopping とデータをもたせるのが確実そうです。でもそうすると、個々のモデルを使うとき、いつもいつもそれを指定しないといけないので、とても煩雑だし、うっかり忘れそうです...。
$new = new YahooShopping;
$new->data_src = 'Yahoo Shopping';
$new->item_id = ....;
$new->save();
$records = YahooShopping::where('data_src','Yahoo Shopping')->get();
$new = new YahooShopping;
$new->item_id = ....;
$new->save(); // data_src 入れ忘れた!
$records = YahooShopping::where('data_src','Yahoo Shoping')->get(); // スペルミスった!
data_src カラムを自動的に使う
前置きが長くなりましたが、本稿は、これを自動化したいという案件です。
保存時に自動的に書き込む
保存時に書き込むには、save()をオーバーライドします。
class Rakuten extends Model
{
protected $table = 'shopping_items';
protected $src = 'Rakuten';
public function save(array $options = array()){
$this->src_id = $this->src;
parent::save();
}
取得時に自動的にフィルタする
Laravelで常に固定条件で自動的にWhereしたい!
こちらにも寄稿しましたが、取得時に書き込むには、newQuery()をオーバーライドします。
class Rakuten extends Model
{
protected $table = 'shopping_items';
protected $src = 'Rakuten';
public function newQuery($excludeDeleted = true)
{
$query = parent::newQuery($excludeDeleted);
$query = $query->where('src_id',$this->src);
return $query;
}
まとめて親クラスをつくる
オブジェクト指向的に、これを新しい親クラスにまとめると便利で理にかないます。このとき、$src に値が入っているかどうかをチェックするのがポイント。値が入っていないと、全レコードを対象とします。
class ShoppingItem extends Model
{
protected $table = 'shopping_items';
protected $src;
public function save(array $options = array()){
if($this->src) $this->src_id = $this->src;
// if(!$this->src) throw new Exception(...); としたほうがいいかも
parent::save();
}
public function newQuery($excludeDeleted = true)
{
$query = parent::newQuery($excludeDeleted);
if($this->src) $query = $query->where('src_id',$this->src);
return $query;
}
class Rakuten extends ShoppingItem
{
protected $table = 'shopping_items';
protected $src = 'Rakuten';
class YahooShopping extends ShoppingItem
{
protected $table = 'shopping_items';
protected $src = 'Yahoo Shopping';
使用例
以上のようにすると、個々のモデルではデータベースを共通化したことを意識しないで済みます。これは、もともと別のデータベースを見ていたものを1つに統合するときに、呼び出し元のソースコードを変更する必要が無いということ。また、UNIONしなくても、UNIONされたデータベースとして扱えるのでソートも高速です。
書く
// 自動的に data_src = 'Rakuten' が追加される
$rakuten = new Rakuten;
$rakuten->item_id = ...;
$rakuten->save();
var_dump($rakuten->data_src); // 'Rakuten';
読む
// 自動的に data_src = 'Rakuten' でフィルタ
$rakuten = Rakuten::where('item_id',$id)->get();
// 自動的に data_src = 'Yahoo Shopping' でフィルタ
$count = YahooShopping::where('item_id',$id)->count();
UNION
ShoppingItem を使います。
$union = ShoppingItem::whereIn('item_id',$idarray)->where('availability','>',0)
->orderBy('released_at','desc')->paginate(30);
$count = Rakuten::count(); // 200
$count = YahooShopping::count(); // 100
$count = ShoppingItem::count(); // 300
補足
- 最終的に data_src は整数のIDにしたほうが、データベースサイズ的に優しくなります。
-
ShoppingItem::save()
では、src が指定されていなかったら例外を返す方がベターです。これは、「data_src が空のレコード」というよくわからないレコードが追加されることを防ぐためです。