Laravel5 Eloquentの複数モデルで1つのテーブルを共有する（識別カラムを自動的に読み書きする）

#何がしたい？
## UNIONを使いたくない
例えば、楽天の情報とヤフーショッピングの商品情報を扱う場合。そのまま作ったら、RakutenとYahooShoppingという2つのモデルを作り、rakutensとyahoo_shoppingsという2つのテーブルを割り当てることになります。楽天とヤフーでは情報ソースが違うので、取得ロジックも違い、別々のモデルを持つことは理にかなっています。

では、「楽天とヤフーショッピングの全アイテムを一覧する」といった横断的なクエリが必要になったらどうしますか？

```php
$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ではテーブル名を任意に指定することができます。

```php:Rakuten.php
class Rakuten extends Model
{
	protected $table = 'shopping_items';

```

```php:YahooShopping.php
class YahooShopping extends Model
{
	protected $table = 'shopping_items';

```

でもこうすると、楽天とYahooのレコードはどうやって区別しましょう？ data_src というカラムを持たせて、Rakuten、Yahoo Shopping とデータをもたせるのが確実そうです。でもそうすると、個々のモデルを使うとき、いつもいつもそれを指定しないといけないので、とても煩雑だし、うっかり忘れそうです...。

```php:
$new = new YahooShopping;
$new->data_src = 'Yahoo Shopping';
$new->item_id  = ....;
$new->save();

$records = YahooShopping::where('data_src','Yahoo Shopping')->get();
```

```php:
$new = new YahooShopping;
$new->item_id  = ....;
$new->save(); // data_src 入れ忘れた！

$records = YahooShopping::where('data_src','Yahoo Shoping')->get(); // スペルミスった！
```

# data_src カラムを自動的に使う

前置きが長くなりましたが、本稿は、これを自動化したいという案件です。

## 保存時に自動的に書き込む

保存時に書き込むには、save()をオーバーライドします。

```php:Rakuten.php
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したい！](http://qiita.com/kd9951/items/3f44b2ecb8dc7804a51b)
こちらにも寄稿しましたが、取得時に書き込むには、newQuery()をオーバーライドします。


```php:Rakuten.php
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 に値が入っているかどうかをチェックするのがポイント。値が入っていないと、全レコードを対象とします。


```php:ShoppingItem.php
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;
	}

```

```php:Rakuten.php
class Rakuten extends ShoppingItem 
{
	protected $table = 'shopping_items'; 
	protected $src = 'Rakuten';

```

```php:YahooShopping.php
class YahooShopping extends ShoppingItem 
{
	protected $table = 'shopping_items'; 
	protected $src = 'Yahoo Shopping';

```

# 使用例

以上のようにすると、個々のモデルではデータベースを共通化したことを意識しないで済みます。これは、もともと別のデータベースを見ていたものを1つに統合するときに、呼び出し元のソースコードを変更する必要が無いということ。また、UNIONしなくても、UNIONされたデータベースとして扱えるのでソートも高速です。

## 書く
```php
// 自動的に data_src = 'Rakuten' が追加される
$rakuten = new Rakuten;
$rakuten->item_id = ...;
$rakuten->save();

var_dump($rakuten->data_src); // 'Rakuten';
```

## 読む
```php
// 自動的に data_src = 'Rakuten' でフィルタ
$rakuten = Rakuten::where('item_id',$id)->get(); 

// 自動的に data_src = 'Yahoo Shopping' でフィルタ
$count   = YahooShopping::where('item_id',$id)->count();
```

## UNION

ShoppingItem を使います。

```php
$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 が空のレコード」というよくわからないレコードが追加されることを防ぐためです。