リレーションとキャッシュ
Laravel4のActive Record (Eloquent)では、リレーションを貼ると、初回参照時にデータベースを読みに行き(Lazy Loading)、以後は読みだした値をキャッシュして再利用するようになる。
そして、hasOneやhasManyなどでは、保存/更新を行っても、キャッシュはクリアされない。
このため、hasOneやhasManyで
リレーションの読出し => リレーションの追加(変更) => リレーションの読出し
の一連の動作を行うと、2回目の読出しは、リレーションの追加(変更)が反映されていないものになってしまう。
特に、もともと初期にリレーションがなかった場合(null)の場合は、2回目でもnullのままなので、
呼び出そうとして
Trying to get property of non-object
が出てしまうので注意が必要。
例として、EntryクラスとPageViewクラスがあり、それぞれ
<?php class Entry extends Eloquent { public function pageView() { return $this->hasOne('PageView','entry_id'); } ... } class PageView extends Eloquent { protected $table = 'page_views'; // page_viewsテーブルに、ctというカラムがある public function entry() { return $this->belongsTo('Entry'); } ... } ?>
のように定義されているケースを考える。
このとき、「もしentryにpageViewがなければpageView新規作成する」コードを書こうとして次のように記述すると、
<?php $entry = Entry::find($id); $pageView = $entry->pageView; // この読出しでキャッシュされる if (!isset($pageView)) { $pageView = new PageView(); $pageView->ct = 1; $entry->pageView()->save($pageView); // pageViewのセットしても、キャッシュは更新されない } echo $entry->pageView->ct; // キャッシュされたものを呼び出す。nullの場合、エラーになる。 ?>
最終行で
Trying to get property of non-object
エラーになってしまう。
これは、2行目で一度、$entry->pageViewでpageViewを参照しているため、それがnullであった場合でも
nullであることがキャッシュされてしまい、その後pageViewのセットをしても、キャッシュは更新されないためである。
これを防ぐためには、キャッシュをクリアすればよいので、
<?php $entry = Entry::find($id); $pageView = $entry->pageView; if (!isset($pageView)) { $pageView = new PageView(); $pageView->ct = 1; $entry->pageView()->save($pageView); $entry->setRelations( array() ); // キャッシュのクリア } echo $entry->pageView->ct; ?>
のようにすれば、最終行ではpageViewをデータベースから読み直すので、問題なく動作する。
とはいえ、この仕様は正直ちょっと不便を感じます。
オプションでも良いので、リレーションで子を追加/変更したときは、親にも反映して欲しいです。