多态关联自定义的类型字段的处理

laravel5.5

手册上是需要这样子写:

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 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 3

如果按手册上写的这样改下行不行呢?随手写的,没有试哈 :warning:

class Post extends Model
{
    const MORPH_TYPE = 0;
}

class Video extends Model
{
    const MORPH_TYPE = 1;
}

use Illuminate\Database\Eloquent\Relations\Relation;

Relation::morphMap([
    Post::MORPH_TYPE => 'App\Post',
    Video::MORPH_TYPE => 'App\Video',
]);
3年前 评论
欧皇降临 (楼主) 3年前

可以,很实用!!!

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!