多型關聯自定義的型別欄位的處理

歐皇降臨發表於2020-08-04

手冊上是需要這樣子寫:

use Illuminate\Database\Eloquent\Relations\Relation;

Relation::morphMap([
    'posts' => 'App\Post',
    'videos' => 'App\Video',
]);

但是,一般設計資料庫的時候 我們type欄位都用tinyint型別的。

type:0-video(視訊) 1-article(文章) 2-dynamic(動態)

select count(*) as aggregate 
from `tvccf_comments` 
where 
`tvccf_comments`.`article_id` = 6 
and 
`tvccf_comments`.`article_id` is not null 
and 
`tvccf_comments`.`type` = App\Model\Article 
and 
`is_ban` = 1 
and 
`parent_id` = 0

首先看了一個sql,type=App\Model\Article,我們想要的是type=1。
然後檢視原始碼

public function comments()
{
return $this->morphMany('App\Model\Comment', '', 'type', 'id', '');
}

然後可以看到例項化了\Illuminate\Database\Eloquent\Relations\MorphMany物件

public function morphMany($related, $name, $type = null, $id = null, $localKey = null)
{
  $instance = $this->newRelatedInstance($related);

  list($type, $id) = $this->getMorphs($name, $type, $id);

  $table = $instance->getTable();

  $localKey = $localKey ?: $this->getKeyName();

  return $this->newMorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
}

protected function newMorphMany(Builder $query, Model $parent, $type, $id, $localKey)
{
  return new MorphMany($query, $parent, $type, $id, $localKey);
}

然後跟著MorphMany類往上看,找到了Illuminate\Database\Eloquent\Relations\MorphOneOrMany類

public function __construct(Builder $query, Model $parent, $type, $id, $localKey)
{
$this->morphType = $type;

$this->morphClass = $parent->getMorphClass();

parent::__construct($query, $parent, $id, $localKey);
}

看一下MorphOneOrMany類的頂層類Illuminate\Database\Eloquent\Relations\Relation

public function __construct(Builder $query, Model $parent)
{
$this->query = $query;
$this->parent = $parent;
$this->related = $query->getModel();

$this->addConstraints();
}

可以看到呼叫了addConstraints()方法
那麼就找到呼叫處Illuminate\Database\Eloquent\Relations\MorphOneOrMany類

public function addConstraints()
{

    if (static::$constraints) {
        parent::addConstraints();
        $this->query->where($this->morphType, $this->morphClass);
    }
}

這裡自己呼叫toSql()方法,看一下sql語句
$this->query->where($this->morphType, $this->morphClass)->toSql();

select * from `tvccf_comments` where `tvccf_comments`.`content_id` = ? and `tvccf_comments`.`content_id` is not null and `tvccf_comments`.`type` = ?

可以看到type就在這裡將資料繫結的。我們只需要找到$this->morphClass這個在哪裡有設定就好了。
其實在上面已經看到了,在建構函式中就設定了。

public function __construct(Builder $query, Model $parent, $type, $id, $localKey)
{
    $this->morphType = $type;
    $this->morphClass = $parent->getMorphClass();

    parent::__construct($query, $parent, $id, $localKey);
}

而且可以看到getMorphClass()方法是一個public的。我們在model中重寫就好。

// video
public function getMorphClass()
{
  reutn 0;
}

但是在每一個類似內容的model中都寫,很累的。
就做了一個trait。

namespace App\Traits;


trait CommentType
{
    public function getMorphClass()
    {
        $map = [
            'videos' => 0,
            'articles' => 1,
            'dynamics' => 2,
        ];
        return $map[$this->getTable()];
    }
}

然後model裡面直接use就好。

class Dynamic extends Model
{
    use CommentType;
}

呼叫

Video::comments()->paginate();

希望對大家有幫助。
完結!!!!撒花!!!

當新增評論後,需要dynamic表的評論數量+1,下面就直接不支援了。

$comment->content()->increment('comments');

// 這個是評論model裡面的關聯
public function content()
{
  $res = $this->morphTo('', 'type', 'content_id');
  return $res;
}

怎麼辦呢?看了一下原始碼

public function morphTo($name = null, $type = null, $id = null)
 { 
     list($type, $id) = $this->getMorphs(
        Str::snake($name), $type, $id
      );

    // 這裡關鍵
    return empty($class = $this->{$type})
     ? $this->morphEagerTo($name, $type, $id)
     : $this->morphInstanceTo($class, $name, $type, $id);
  }

可以看到$class = $this->{$type},這裡的$type就是評論表裡面區分內容的欄位
type:0-video(視訊) 1-article(文章) 2-dynamic(動態) 這樣子的。
從這裡可以看出來為啥資料庫存app\model\article這種的啦。
怎麼解決呢?非常簡單,直接上程式碼

// 評論模型
public function content($type)
{
  $this->setAttribute('type', $this->modelToType($type));
  $res = $this->morphTo('', 'type', 'content_id');
  $this->setAttribute('type', $type);
  return $res;
}

  public function modelToType($type)
 {
      $map = [
      'App\Model\Video',
      'App\Model\Article',
      'App\Model\Dynamic',
      ];
      return $map[$type];
  }

呼叫

$comment->content($comment->type)->increment('comments');

搞定,工作去了,剩下的你們自己優化吧。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章