LoginSignup
26
21

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-02-26

何がしたい?

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

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

YahooShopping.php
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()をオーバーライドします。

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したい!
こちらにも寄稿しましたが、取得時に書き込むには、newQuery()をオーバーライドします。

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

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;
    }

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
21