九、粉丝关系

九、粉丝关系

一、开发目标

本章我们将构建应用的最后一个功能 - 用户关注功能。

一个优秀的社交网站在于引导用户进行信息分享,与好友相互之间的互动。用户关注功能能够让用户跟好友进行关系绑定,用户彼此之间可以订阅对方的动态。如果你有玩过微博,则会知道微博关注人的产品设计需求如下:

  1. 用户 Summer 访问了 Monkey 的个人页面进行浏览;

  2. Summer 对 Monkey 发布的微博动态内容感兴趣,点击其个人页面的「关注按钮」对他进行关注;

  3. 关注之后 Summer 将出现在 Monkey 的粉丝列表,Monkey 将出现在 Summer 的关注人列表;

  4. Summer 在访问网站主页时,能够看到好友(包括 Monkey)和自己发布的动态;

接下来让我们围绕着这一产品设计需求来开发关注用户的功能。

二、粉丝数据表

让我们跟之前一样,切换到新分支上进行开发:

$ git checkout master
$ git checkout -b following-users

1、【粉丝】表的构建

在 Summer 关注了 Monkey 之后,Summer 将成为 Monkey 的粉丝,Monkey 为 Summer 的关注用户;在 Summer 取消关注 Monkey 之后,Monkey 将从 Summer 的关注人列表中被移除,Summer 则从 Monkey 的粉丝列表中被移除。由此可见,在关注用户功能的整个流程中,最重要的两个主体分别是被关注的用户(user_id)和粉丝(follower_id),我们可以通过被关注用户(user_id)来获取到他所有的粉丝,也能通过一个粉丝(follower_id)来获取到他关注的所有用户。现在,我们可以通过创建一个『粉丝表』来存放用户对应关注的所有粉丝。

我们需要为粉丝关系表生成一个迁移文件。

$ php artisan make:migration create_followers_table --create="followers"

并在该迁移文件中增加两个字段 user_id 和 follower_id 用于接下来的操作。

database/migrations/[timestamp]_create_followers_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateFollowersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('followers', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id')->index();
            $table->integer('follower_id')->index();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('followers');
    }
}

正如我们前面所提到的,在获取用户的关注人列表和粉丝列表时需要用到 user_id 和 follower_id 字段进行数据查找,因此我们需要为其增加索引,以便提高查询效率。

接着让我们执行新建的迁移文件。

2、关注的人和粉丝

在用户关注功能中,一个用户(粉丝)能够关注多个人,而被关注者能够拥有多个粉丝,像这种关系我们称之为「多对多关系」。在 Laravel 中我们使用 belongsToMany 来关联模型之间的多对多关系。以粉丝为例,一个用户能够拥有多个粉丝,因此我们在用户模型中可以像这样定义:

public function followers(){
    return $this->belongsToMany(User::Class);
}

在 Laravel 中会默认将两个关联模型的名称进行合并,并按照字母排序,因此我们生成的关联关系表名称会是 user_user。我们也可以自定义生成的名称,把关联表名改为 followers

public function followers(){
    return $this->belongsToMany(User::Class, 'followers');
}

除了自定义合并数据表的名称,我们也可以通过传递额外参数至 belongsToMany 方法来自定义数据表里的字段名称。如下:

public function followers(){
    return $this->belongsToMany(User::Class, 'followers', 'user_id', 'follower_id');
}

belongsToMany 方法的第三个参数 user_id 是定义在关联中的模型外键名,而第四个参数 follower_id 则是要合并的模型外键名。

因此,最终我们在用户模型中定义的关联如下:

app/Models/User.php

<?php

namespace App\Models;

use function foo\func;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;

/**
 * App\Models\User
 *
 * @property-read \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User newModelQuery()
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User newQuery()
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User query()
 * @mixin \Eloquent
 */
class User extends Authenticatable
{
    use Notifiable;

    protected $table = "users";

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    public function statuses()
    {
        return $this->hasMany(Status::class);
    }

    public static function boot()
    {
        parent::boot(); // TODO: Change the autogenerated stub

        static::creating(function ($user){
            $user->activation_token = str_random(30);
        });
    }

    public function gravatar($size = '100')
    {
        $hash = md5(strtolower(trim($this->attributes['email'])));
        return "http://www.gravatar.com/avatar/$hash?s=$size";
    }

    public function feed()
    {
        return $this->statuses()->orderBy('created_at','desc');
    }

    public function followers()
    {
        return $this->belongsToMany(User::class,'followers','user_id','follower_id');
    }

    public function followings()
    {
        return $this->belongsToMany(User::class,'followers','user_id','follower_id');
    }
}

我们可以通过 followers 来获取粉丝关系列表,如:

$user->followers();

通过 followings 来获取用户关注人列表,如:

$user->followings();

3、关联动作

在前面的学习中,我们已经建立了 User 模型的多对多关联 followers() 和 followings()。现在我们可以使用 Eloquent 模型为多对多提供的一系列简便的方法。如使用 attach 方法或 sync 方法在中间表上创建一个多对多记录,使用 detach 方法在中间表上移除一个记录,创建和移除操作并不会影响到两个模型各自的数据,所有的数据变动都在 中间表 上进行。attachsyncdetach 这几个方法都允许传入 id 数组参数。

让我们使用 Tinker 来模拟用户的关注和取消关注操作:

$ php artisan tinker

当 id 为 1 的用户去关注 id 为 2 和 id 为 3 的用户时,可使用 attach 方法来进行关注,如下所示:

>>> $user = App\Models\User::find(1)
>>> $user->followings()->attach([2, 3])

对用户进行关注之后,我们可以通过下面方法来输出关联的 id 数组查看创建结果:

>>> $user->followings()->allRelatedIds()->toArray()
=> [
     2,
     3,
   ]

image.png

allRelatedIds 是 Eloquent 关联关系提供的 API,用来获取关联模型的 ID 集合。

可以看到 id 2 和 3 已被成功添加到关联 id 数组中,但 attach 方法有个问题,在我们对同一个 id 进行添加时,则会出现 id 重复的情况。例如,让我们使用 attach 方法接着对 id 为 2 的用户进行关注:

>>> $user->followings()->attach([2])
>>> $user->followings()->allRelatedIds()->toArray()=> [
     2,
     3,
     2,
   ]

可以看到出现两条 user_id 为 2 的重复数据。

为了解决这种问题,我们可以使用 sync 方法。sync 方法会接收两个参数,第一个参数为要进行添加的 id,第二个参数则指明是否要移除其它不包含在关联的 id 数组中的 idtrue 表示移除,false 表示不移除,默认值为 true。由于我们在关注一个新用户的时候,仍然要保持之前已关注用户的关注关系,因此不能对其进行移除,所以在这里我们选用 false。现在让我们尝试使用 sync 方法来关注 id 为 3 的用户:

>>> $user->followings()->sync([3], false)
>>> $user->followings()->allRelatedIds()->toArray()=> [
     2,
     3,
     2,
   ]

可以看到 id 为 3 的数据并不会被重复创建。

最后我们可以借助 detach 来对用户进行取消关注的操作,取消关注 2 号和 3 号用户:

>>> $user->followings()->detach([2,3])
>>> $user->followings()->allRelatedIds()->toArray()
=> []

借助这两个方法可以让我们非常简单的实现用户的「关注」和「取消关注」的相关逻辑,具体在用户模型中定义关注(follow)和取消关注(unfollow)的方法如下:

app/Models/User.php

public function follow($user_ids)
{
    if (!is_array($user_ids)){
        $user_ids = compact('user_ids');
    }
    $this->followings()->sync($user_ids,false);
}

public function unfollow($user_ids)
{
    if (!is_array($user_ids)){
        $user_ids = compact('user_ids');
    }
    $this->followings()->detach($user_ids);
}

is_array 用于判断参数是否为数组,如果已经是数组,则没有必要再使用 compact 方法。我们并没有给 sync和 detach 指定传递参数为用户的 id,这两个方法会自动获取数组中的 id。

我们还需要一个方法用于判断当前登录的用户 A 是否关注了用户 B,代码实现逻辑很简单,我们只需判断用户 B 是否包含在用户 A 的关注人列表上即可。这里我们将用到 contains 方法来做判断。

app/Models/User.php

public function isFollowing($user_id)
{
    return $this->followings()->contains($user_id);
}

Git 代码版本控制

接着让我们将本次更改纳入版本控制中:

$ git add -A
$ git commit -m "粉丝数据表"


三、社交统计信息

  • 关注数

  • 粉丝数

  • 微博数

1、示例数据

目前并没有一个用户拥有大量的粉丝或关注了多个人,接下来我们需要用数据填充的方式,为第一个用户添加假数据,让他拥有一些关注人和粉丝。

首先我们需要生成一个数据填充文件。

$ php artisan make:seeder FollowersTableSeeder

我们会使用第一个用户对除自己以外的用户进行关注,接着再让所有用户去关注第一个用户。假数据的生成代码如下所示:

database/seeds/FollowersTableSeeder.php

<?php

use Illuminate\Database\Seeder;
use App\Models\User;

class FollowersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $users = User::all();
        $user = $users->first();
        $user_id=$user->id;

        //获取去掉ID 为 1 的所有用户ID 数组
        $followers = $users->slice(1);
        $follower_ids = $followers->pluck('id')->toArray();

        //关注除了1号用户以外的所有用户
        $user->follow($follower_ids);

        //除了1号用户以外的所有用户都来关注1号用户
        foreach ($followers as $follower){
            $follower->follow($user_id);
        }

    }
}

接着让我们为假数据的生成顺序进行设定。

database/seeds/DatabaseSeeder.php

<?php

use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        Model::unguard();

        $this->call(UsersTableSeeder::class);
        $this->call(StatusesTableSeeder::class);
        $this->call(FollowersTableSeeder::class);

        Model::reguard();
    }
}

最后,我们还需要对数据库进行重置和填充。

<?php

use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        Model::unguard();

        $this->call(UsersTableSeeder::class);
        $this->call(StatusesTableSeeder::class);
        $this->call(FollowersTableSeeder::class);

        Model::reguard();
    }
}

最后,我们还需要对数据库进行重置和填充。

$ php artisan migrate:refresh --seed

2、局部视图

接着我们还需要再添加一个局部视图,用于展示用户的一些社交的统计信息,如关注人数、粉丝数、微博发布数等。

resources/views/shared/_stats.blade.php

<a href="#">
    <strong id="following" class="stat">
        {{count($user->followings)}}
    </strong>
</a>
<a href="#">
    <strong id="followers" class="stat">
        {{count($user->followers)}}
    </strong>
</a>
<a href="#">
    <strong id="followers" class="stat">
        {{$user->statuses()->count()}}
    </strong>
</a>

我们通过调用 Eloquent 模型的 count 方法来获取用户发布过的微博数,这个做法并不算是最佳实践,因为在大型应用中,为了节省服务器资源,优化数据库查询效率,常会采用的方法是在数据库中添加一个模型计数器字段,在每次对模型进行创建或删除时对该字段进行更新,而由于本书开发的应用只是小型的演示应用,因此在这里我们使用 count 方法来查询即可。

{{ $user->statuses()->count() }}

3、加载子视图

在定义好用户个人信息的统计视图之后,我们需要将视图添加到首页上进行显示。

resources/views/static_pages/home.blade.php

@extends('layouts.default')

@section('content')
  @if(Auth::check())
    <div class="row">
      <div class="col-md-8">
        <section class="status_form">
          @include('shared._status_form')
        </section>
        <h4>微博列表</h4>
        <hr>
        @include('shared._feed')
      </div>
      <aside class="col-md-4">
        <section class="user_info">
          @include('shared._user_info',['user'=>Auth::user()])
        </section>
        <section class="stats mt-2">
          @include('shared._stats')
        </section>
      </aside>
    </div>
  @else
    <div class="jumbotron">
      <h1>Hello Laravel</h1>
      <p class="lead">
        你现在所看到的是 <a href="https://laravel-china.org/courses/laravel-essential-training">Laravel 入门教程</a> 的示例项目主页。
      </p>
      <p>
        一切,将从这里开始。
      </p>
      <p>
        <a class="btn btn-lg btn-success" href="{{ route('signup')}}" role="button">现在注册</a>
      </p>
    </div>
  @endif
@stop

4、样式调整

接着让我们来对样式进行调整,让页面看上去能更加整洁美观。

resources/sass/app.scss

.stats {
  overflow: auto;
  margin-top: 0;
  padding: 0;
  a {
    float: left;
    padding: 0 10px;
    text-align: center;
    width: 33%;
    border-left: 1px solid #eee;
    color: #33383c;
    &:first-child {
      padding-left: 0;
      border: 0;
    }
    &:hover {
      text-decoration: none;
      color: #337ab7;
    }
  }
  strong {
    display: block;
    font-size: 1.2em;
    color: black;
  }
}

在做完这一切之后,我们便能够在首页看到社交统计视图已经能够正常显示出来了:

image.png

5、个人页面

个人页面也需要显示社交统计信息,修改用户个人中心页面视图:

resources/views/users/show.blade.php

@extends('layouts.default')
@section('title',$user->name)

@section('content')
    <div class="row">
        <div class="offset-md-2 col-md-8">
            <section class="user_info">
                @include('shared._user_info',['user'=>$user])
            </section>
            <section class="stats mt-2">
                @include('shared._stats',['user'=>$user])
            </section>
            <hr>
            <section class="status">
                @if($statuses->count() > 0)
                    <ul class="list-unstyled">
                        @foreach($statuses as $status)
                            @include('statuses._status')
                        @endforeach
                    </ul>
                    <div class="mt-5">
                        {!! $statuses->render() !!}
                    </div>
                @else
                    <p>没有数据!</p>
                @endif
            </section>
        </div>
    </div>
@stop

由顶部导航栏进入第一个用户的个人中心:

image.png


Git 代码版本控制

接着让我们将本次更改纳入版本控制中:

$ git add -A
$ git commit -m "社交统计信息"

四、粉丝页面

本小节中,我们将制作『关注的人』列表页面和『粉丝』列表页面。这两个页面的入口链接是在社交统计信息里,一开始我们会设置路由,建立好链接,然后再制作具体页面。

1、路由

接下来让我们接着定义用户关注者列表和粉丝列表的路由,用于对接下来的关注人列表和粉丝列表进行显示。

routes/web.php

Route::get('/users/{user}/followings','UsersController@followings')->name('users.followings');
Route::get('/users/{user}/followers','UsersController@followers')->name('users.followers');

2、入口链接

目前『社交统计信息』分别在主页和个人页面里显示,接下来我们要为其加上对应的链接:

resources/views/shared/_stats.blade.php

<a href="{{route('users.followings',$user->id)}}">
    <strong id="following" class="stat">
        {{ count($user->followings) }}
    </strong>
    关注
</a>
<a href="{{route('users.followers',$user->id)}}">
    <strong id="followers" class="stat">
        {{ count($user->followers) }}
    </strong>
    粉丝
</a>
<a href="{{route('users.show',$user->id)}}">
    <strong id="statuses" class="stat">
        {{ $user->statuses()->count() }}
    </strong>
    微博
</a>

Chrome 浏览器下右键『查看页面元素』可以看到链接:

image.png

3、控制器

上面设置的路由器对应控制器信息如下所示:

HTTP 请求URL动作作用
GET/users/{user}/followingsUsersController@followings显示用户的关注人列表
GET/users/{user}/followersUsersController@followers显示用户的粉丝列表

可以看到两个新增的路由都被映射到用户控制器上,因此我们接下来需要对控制器加入两个方法,一个是用于显示用户关注人列表视图的 followings 方法,另一个则是用户显示粉丝列表的 followers 方法。接着我们还需要对这两个动作的请求进行过滤,只允许登录用户访问,得益于我们使用了 auth 中间件的 except 选项,这里我们不需要做任何修改,此控制器默认安全。代码如下:

app/Http/Controllers/UsersController.php

public function followings(User $user)
{
    $users = $user->followings()->paginate(30);
    $title = $user->name . '关注的人';
    return view('users.show_follow',compact('users','title'));
}

public function followers(User $user)
{
    $users = $user->followers()->paginate(30);
    $title = $user->name . '粉丝';
    return view('users.show_follow',compact('users','title'));
}

由上面代码可知,我们在新增的两个动作中添加了 title 变量用于网页标题的显示,这是因为我们最终构建出来的关注人列表视图和粉丝视图除了显示的动态数据和标题可能有些不同,其它的页面元素基本一致,为了避免写重复代码,我们将对这两份数据使用同一套视图进行展示。

4、构建视图

接下来需要新增一个 users/show_follow 视图来用于数据展示。该视图的页面结构如下。

resources/views/users/show_follow.blade.php

@extends('layouts.default')
@section('title',$title)

@section('content')
<div class="offset-md-2 col-md-8">
   <h2 class="mb-4 text-center">{{$title}}</h2>

   <div class="list-group list-group-flush">
       @foreach($users as $user)
           <div class="list-group-item">
               <img src="{{$user->ravatar()}}" alt="{{$user->name}}" class="mr-3" width="32">
               <a href="{{route('users.show',$user)}}">{{$user->name}}</a>
           </div>
       @endforeach
   </div>

   <div class="mt-3">
       {!! $users->render() !!}
   </div>
</div>
@stop

这一步操作完成之后,我们便能够在页面上查看用户的 关注人列表 和 粉丝列表

image.png

Git 代码版本控制

接着让我们将本次更改纳入版本控制中:

$ git add -A
$ git commit -m "关注和粉丝列表页面"

五、关注按钮

1、关注表单

本小节我们将开发『关注按钮』,允许用户间关注和取消关注。先设置好路由、页面嵌入关注表单,最后在控制器里编写业务处理的逻辑代码。

2、注册路由

接下来让我们针对前面开发的「关注用户」和「取消用户」的功能,加入路由定义:

routes/web.php

Route::post('/users/followers/{user}','FollowersController@store')->name('followers.store');
Route::delete('/users/followers/{user}','FollowersController@destroy')->name('followers.destroy');

对应的路由信息如下:

HTTP 请求URL动作作用
POST/users/followers/{user}FollowersController@store关注用户
DELETE/users/followers/{user}FollowersController@destroy取消关注用户

可以看到,两个路由的动作都映射到了 FollowersController 控制器上,目前该控制器还没有创建,让我们运行下面命令进行创建。

$ php artisan make:controller FollowersController

3、授权策略

在开发一个功能前,需仔细考虑下此功能的授权策略。在我们的场景中,什么人不能关注用户?是的,自己不能关注自己。故新增授权策略方法取名 follow()

app/Policies/UserPolicy.php

public function follow(User $currentUser , User $user)
{
   return $currentUser->id !==$user->id;
}

在后面的开发中,关注表单只有在授权策略通过时才显示,并且控制器关注和取消关注方法里,都需要做授权判断。

4、关注表单

接下来我们在用户个人页面上增加一个关注表单,表单以按钮的形式展现,点击按钮即可对用户进行关注。关注表单的页面结构如下:

resources/views/users/_follow_form.blade.php

@can('follow',$user)
   <div class="text-center mt-2 mb-4">
       @if(Auth::user()->isFollowing($user->id))
           <form action="{{route('followers.destroy',$user->id)}}" method="post">
               {{csrf_field()}}
               {{method_field('DELETE')}}
               <button type="submit" class="btn btn-sm btn-outline-primary">取消关注</button>
           </form>
       @else
           <form action="{{route('followers.store')}}" method="post">
               {{csrf_field()}}
               <button type="submit" class="btn btn-sm btn-primary">关注</button>
           </form>
       @endif
   </div>
@endcan

代码详解:

当用户访问的是自己的个人页面时,关注表单不应该被显示出来,因此我们加了下面这行代码用于判断:

@can('follow',$user)

接着,关注表单需要分为两种状态进行显示:

  1. 当用户已被关注时,显示的是取消关注的按钮;

  2. 未被关注时,使用的则是关注按钮。

我们可以通过在用户模型中定义的 isFollowing 方法来判断用户是否已被关注。

uth::user()->isFollowing($user->id)

5、加载子视图

现在让我们将创建好的关注表单添加到用户的个人页面上,另外用户的社交信息统计视图也需要在其个人页面上得到展示。

为用户个人页面添加关注表单和信息统计视图:

resources/views/users/show.blade.php

@extends('layouts.default')
@section('title',$user->name)

@section('content')
   <div class="row">
       <div class="offset-md-2 col-md-8">
           <section class="user_info">
               @include('shared._user_info',['user'=>$user])
           </section>

           @if(Auth::check())
               @include('users._follow_form')
           @endif

           <section class="stats mt-2">
               @include('shared._stats',['user'=>$user])
           </section>

           <hr>
           
           <section class="status">
               @if($statuses->count() > 0)
                   <ul class="list-unstyled">
                       @foreach($statuses as $status)
                           @include('statuses._status')
                       @endforeach
                   </ul>
                   <div class="mt-5">
                       {!! $statuses->render() !!}
                   </div>
               @else
                   <p>没有数据!</p>
               @endif
           </section>
       </div>
   </div>
@stop

代码解析:

由于我们并没有对用户个人页面对应的请求进行过滤,因此未登录的用户也能查看其它用户的个人信息,当未登录用户访问了个人页面时,则不需要渲染关注表单。通过前面所学的知识可知,我们能通过 Auth::check() 方法来判断用户是否已登录。

6、控制器动作

现在的用户关注表单已经构建完毕,接下来让我们为 FollowersController 加上 store 和 destroy 动作,分别用于处理「关注」和「取消关注」用户的请求。

app/Http/Controllers/FollowersController.php

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Auth;

class FollowersController extends Controller
{
   public function __construct()
   {
       $this->middleware('auth');
   }

   public function store(User $user)
   {
       $this->authorize('follow',$user);

       if (!Auth::user()->isFollowing($user->id)){
           Auth::user()->follow($user->id);
       }

       return redirect()->route('users.show',$user->id);
   }

   public function destory(User $user)
   {
       $this->authorize('follow',$user);

       if (Auth::user()->isFollowing($user->id)){
           Auth::user()->unfollow($user->id);
       }

       return redirect()->route('users.show',$user->id);
   }
}

由于这两个动作都需要用户登录之后才能进行操作,因此我们在 __construct 方法里为这两个动作都加上请求过滤。由于用户不能对自己进行关注和取消关注,因此我们在 store 和 destroy 方法中都对用户身份做了授权判断:

$this->authorize('follow',$user);

为了使代码逻辑更加严谨,在进行关注和取消关注操作之前,我们还会利用 isFollowing 方法来判断当前用户是否已关注了要进行操作的用户。

至此用户的关注功能开发完毕:

image.png


Git 代码版本控制

接着让我们将本次更改纳入版本控制中:

$ git add -A
$ git commit -m "关注按钮"

六、动态流

接下来让我们完成本教程演示应用的最后一个功能 - 在主页上显示所有关注用户的微博动态。

我们在前面章节中已经为用户定义了 Feed 动态流方法,只是该方法比较粗略,只是显示当前登录用户的个人微博状态而已。现在我们要对该方法进行完善,加入关注人的微博动态数据。

app/Models/User.php

public function feed()
{
   $user_ids = $this->followings->pluck('id')->toArray();
   array_push($user_ids,$this->id);
   return Status::query()->whereIn('user_id',$user_ids)->with('user')->orderBy('create_at','desc');
}

上面的方法做了以下几个事情:

  1. 通过 followings 方法取出所有关注用户的信息,再借助 pluck 方法将 id 进行分离并赋值给 user_ids

  2. 将当前用户的 id 加入到 user_ids 数组中;

  3. 使用 Laravel 提供的 查询构造器 whereIn 方法取出所有用户的微博动态并进行倒序排序;

  4. 我们使用了 Eloquent 关联的 预加载 with 方法,预加载避免了 N+1 查找的问题,大大提高了查询效率。N+1 问题 的例子可以阅读此文档 Eloquent 模型关系预加载 。

这里需要注意的是 Auth::user()->followings 的用法。我们在 User 模型里定义了关联方法 followings(),关联关系定义好后,我们就可以通过访问 followings 属性直接获取到关注用户的 集合。这是 Laravel Eloquent 提供的「动态属性」属性功能,我们可以像在访问模型中定义的属性一样,来访问所有的关联方法。

还有一点需要注意的是 $user->followings 与 $user->followings() 调用时返回的数据是不一样的, $user->followings 返回的是 Eloquent:集合 。而 $user->followings() 返回的是 数据库请求构建器 ,followings()的情况下,你需要使用:

$user->followings()->get()

或者 :

$user->followings()->paginate()

方法才能获取到最终数据。可以简单理解为 followings 返回的是数据集合,而 followings() 返回的是数据库查询语句。如果使用 get() 方法的话:

$user->followings == $user->followings()->get() // 等于 true

整个项目的开发到此完成:

image.png


让我们对代码改动进行提交,并切换到主分支上进行合并。

$ git add -A
$ git commit -m "关注用户动态流"
$ git checkout master
$ git merge following-users

将代码推送到 GitHub 和 Heroku 上。

$ git push
$ git push heroku master

最后,由于我们新建了 followers 表,因此需要在 Heroku 上也执行迁移。

$ heroku run php artisan migrate

测试下线上应用:

  1. 注册 Monkey 用户;

  2. 访问 Summer 主页,并关注他

七、小结

经过本章节的学习,我们学到了以下内容:

  • 多对多关系的应用;

  • 新增或销毁多对多关联;

  • 首页动态流的具体实现;

  • 使用 with 来避免 N+1 问题


















回复列表



回复操作

正在加载验证码......

请先拖动验证码到相应位置