GLUGの小林です。初めてなので軽めのネタで。 弊社の子会社が運営しているEATASというメディアで、記事にカテゴリがついており、元のカテゴリ部分は以下でした。
class Article extends Model { public function category() { return $this->belongsTo('App\Models\Category'); }
以下新しい要件が出てきました。
- カテゴリをネストしたい
- カテゴリを複数をつけたい。
- パンくずは流入経路を優先
- それ以外は優先順位に従って
- カテゴリページが欲しい
そこで以下に変更
class Article extends Model { public function categories() { return $this->belongsToMany('App\Models\Category')->withPivot('priority'); }
class Category extends Model { public function articles() { return $this->belongsToMany('App\Models\Article'); } public function parentCategory() { return $this->belongsTo(self::class,'parent_category_id'); } public function childCategories() { return $this->hasMany(self::class,'parent_category_id'); }
ここまでは問題なかったのですが、以下の新しい要件でちょっと問題が
- NEWSカテゴリについてはトップページに表示しない
今のままだとSQLで表現するとこうなるので、ちょっとパフォーマンス的に厳しい
SELECT * FROM articles INNER JOIN article_category ON articles.id = article_category.areticle_id WHERE article_category.category_id = 1 AND 他の条件
なので、articlesテーブルにis_newsカラム作ってそれで絞り込もう、と考えましたが、
$article->categories()->attach(Category::NEWS_CATEGORY_ID);
のときにいちいち
$article->is_news = 1; $article->save();
するわけにもいかないので、attach/detach/syncのときにイベント発生しないかあと考えました。 割とやりたい人いそうな気がしたのですが、ぐぐってもやり方出てこず。意外といないものなのですね。 ということで、
public function attach($id, array $attributes = [], $touch = true) { // Here we will insert the attachment records into the pivot table. Once we have // inserted the records, we will touch the relationships if necessary and the // function will return. We can parse the IDs before inserting the records. $this->newPivotStatement()->insert($this->formatAttachRecords( $this->parseIds($id), $attributes )); if ($touch) { $this->touchIfTouching(); } }
public function touchIfTouching() { if ($this->touchingParent()) { $this->getParent()->touch(); } if ($this->getParent()->touches($this->relationName)) { $this->touch(); } }
touch()呼んでるみたいなので、以下対応でカテゴリ追加・削除時にis_newsが変わるようになりました。
class Article { public function touch(){ $article = $this->fresh(); if ($article->categories != null && $article->categories->find(Category::NEWS_CATEGORY_ID)){ $article->is_news = 1; }else{ $article->is_news = 0; } if ($this->is_news == $article->is_news){ parent::touch(); }else{ $article->save(); } } }
class Category { protected $touches = ['articles']; }