源码分析 - PHPHub 的 Vote 功能与 Laravel 多态数据关系 (Polymorphic Relationship)

教程 duke-lee ⋅ 于 3年前 ⋅ 最后回复由 long1774341213 2个月前 ⋅ 7751 阅读

用例 Use Case

  • 会员可以对某个主题投票;
  • 会员可以对某个回复进行投票;

Laravel 的 ORM Eloquent 对这一类型的业务逻辑有很好的支持, 如下面几个例子

一般 CMS 通用评论功能

  • 用户可以对文章进行评论;
  • 用户可以对用户进行评论;
  • 用户可以对专题进行评论;

标签功能

在某知名系统里面, 有 post 表和 page 表, 分别对应文章和页面.

  • 管理员可以给 post 打标签;
  • 管理员可以对 page 打标签;

这中类型的业务逻辑使用 Laravel 的 多态数据关系 (Polymorphic Relationship) 来解决那是最好不过了.

开始代码

1. 添加 Route

# ------------------ Votes ------------------------

Route::get('/topics/{id}/upvote', [
    'as' => 'topics.upvote',
    'uses' => 'TopicsController@upvote',
    'before' => 'auth' // 需要登录用户才能访问.
]);

Route::get('/topics/{id}/downvote', [
    'as' => 'topics.downvote',
    'uses' => 'TopicsController@downvote',
    'before' => 'auth'
]);

Route::get('/replies/{id}/vote', [
    'as' => 'replies.vote',
    'uses' => 'RepliesController@vote',
    'before' => 'auth'
]);

我们使用了 name route, 这样在页面里面, 使用帮助函数

route('topics.upvote', $topic->id)

就可以生成类似以下的链接了.

http://phphub.org/topics/20/upvote

2. 添加页面入口

此代码省略..., 下面是效果图

3. 创建 Migration

php artisan generate:migration create_votes_table

上面命令会在 app/database/migrations 文件夹下生成类似于 2014_08_20_130447_create_votes_table.php 的文件.

up 方法里面填入以下

Schema::create('votes', function(Blueprint $table)
{
    $table->increments('id');
    $table->integer('user_id')->unsigned()->index();
    $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');

    $table->integer('votable_id')->index();
    $table->string('votable_type')->index();
    $table->enum('is', ['upvote', 'downvote']);

    $table->timestamps();
});

可读性很好的代码, 这里不解释.

4. 创建 Model 文件

php artisan generate:model Vote

上面命令会在 app/model/ 文件夹下生成 Vote.php 文件, 在里面添加以下几行方法

public function votable()
{
   return $this->morphTo();
}

5. 开始链接 Topic 和 Reply

app/model/Topic.phpapp/model/Reply 文件中加入相同的方法

public function votes()
{
    return $this->morphMany('Vote', 'votable');
}

6. 开始调用

Topic 话题

以下是给 Topic 话题 投票的时候的调用, TopicsController 里面

public function upvote($id)
{
    $topic = Topic::find($id);
    $topic->votes()->create(['user_id' => Auth::user()->id, 'is' => 'upvote']);
    return Redirect::back();
}

通过上面的 create 方法, 创建了一个 vote, 看下数据库里面的内容, 注意 votable 字段:

Reply 评论

以下是给 Reply 评论 投票的时候的调用, RepliesController 里面

public function vote($id)
{
   $reply = Reply::find($id);
   $reply->votes()->create(['user_id' => Auth::user()->id, 'is' => 'upvote']);
   return Redirect::back();
}

通过上面的 create 方法, 创建了一个 vote, 看下数据库里面的内容, 同样注意 votable 字段:

总结

介绍完了, 很神奇吧, 最少的代码, 兼顾可读性, 把一个复杂的逻辑轻轻松松的解决. 这就是 Laravel4. :+1:

PHPHub 的 Vote 功能具体的代码请参照 源码 .

本帖已被设为精华帖!
回复数量: 16
  • wutong
    3年前

    不明觉厉 哈哈 :100:

  • AaronGo
    3年前

    用这种ORM我很想知道后期分库分表要怎么搞.

  • monkey 最重要的事,永远只有一件
    3年前

    很不错介绍

  • lovecn
    3年前

    关注和收藏为什么不用Ajax呢

  • Summer MOD A Life-long learner.
    3年前

    @lovecn 今天修改.

  • hye
    3年前

    $table->string('is')->index();为啥不用enum来定义? $table->enum('is', array('upvote', 'downvote'));

  • zhuzhichao Lalala Demacian !
    3年前

    @hye 如果数据库用的是mysql的话可以用枚举类型的,但是如果使用sqlite的话就没有枚举类型了。我个人认为如果只用mysql 的话这里可以用enum的,没什么不好的。:smiling_imp:

  • bopo
    3年前

    收藏+关注,没话说

  • xing393939
    3年前

    @AaronGo 同感兴趣

  • joshle
    3年前

    非常好的文章,不知道为什么列表里面没有找到,找了好几天,最后在无人问津里面找到了,顶起来!列表显示机制是不是有问题?

  • duke-lee
    3年前

    Thanx!

    默认的 社区帖子列表 是有时间限制的, 防止挖坟贴出现, 可以在 精华主题列表 里面找到.

  • duke-lee
    3年前

    @hye 哎呦, 多谢指出. 向你学习了.

  • joshle
    3年前

    @duke-lee 效率的话,那 votable_type 是不是也可以考虑用 enum 来定义

  • szyulian
    2年前

    不知道laravel框架有没有集成类似于Cog之类的code generator,碰到上面说的只有mysql才支持的enum,我们可以退一步,考虑使用自动生成的代码hard code这一部分。网站大了以后,效率是很重要的。

  • 331164885
    2年前

    @hye sm?

  • long1774341213
    2个月前

    更新删除怎么做

暂无评论~~
  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!
Ctrl+Enter