21.全局作用域与查询重构

未匹配的标注

本节说明

  • 对应视频第 21 小节:Global Scopes and Further Query Reduction

本节内容

接着上一节的内容,我们可以发现当前页面任然存在的性能问题:
file
这是因为我们在回复区域使用了$reply->isFavorited()用来检查当前登录用户是否进行过点赞行为。我们对isFavorited()方法的定义为:

.
.
public function isFavorited()
{
    return $this->favorites()->where('user_id',auth()->id())->exists();
}
.

所以我们每增加一个回复,sql语句就会增加一条:
file
在我们当前的场景中,每个reply我们都想要获取ownerfavorites关联,所以我们利用模型的$with属性来优化这个问题:
forum\app\Reply.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Reply extends Model
{
    protected $guarded = [];
    protected $with = ['owner','favorites'];  -->注意此处

    public function owner()
    {
        return $this->belongsTo(User::class,'user_id');  // 使用 user_id 字段进行模型关联
    }

    public function favorites()
    {
        return $this->morphMany(Favorite::class,'favorited');
    }

    public function favorite()
    {
        $attributes = ['user_id' => auth()->id()];

        if( ! $this->favorites()->where($attributes)->exists()){
            return $this->favorites()->create($attributes);
        }

    }

    public function isFavorited()
    {
        return !! $this->favorites->where('user_id',auth()->id())->count();  -->注意此处,修改该后返回的类型是 bool 型
    }
}

既然我们已经利用$with属性为Reply模型预加载了ownerfavorites属性,那么我们从Thread模型关联Reply模型时就可以不用再次预加载:
app\Thread.php

    .
    .
    public function replies()
    {
        return $this->hasMany(Reply::class);
    }
    .
    .

再次刷新页面,sql语句数量已经减少到 8 条:
file
如果再增加一个回复,sql语句数量任然是 8 条 :
file
相同的应用场景,每个thread我们都想要获取creator关联。同样的办法:
forum\app\Thread.php

.
.
class Thread extends Model
{
    protected $guarded = [];
    protected $with = ['creator'];
    .
    .

如果你对 查询作用域 有所了解的话,那你应该知道的是,以上我们使用的$with属性来获取模型的关联关系其实就是一个 全局作用域

全局作用域允许我们为给定模型的所有查询添加条件约束,如 Laravel 自带的 软删除功能 就使用了全局作用域来从数据库中拉出所有没有被删除的模型。不过与自定义的全局作用域不同的是,对于自定义的全局作用域,我们可以使用withoutGlobalScope为给定查询移除指定全局作用域。而使用了$with属性,我们总是会获取关联的数据,并且无法移除。

好了,现在我们还剩下一个问题需要解决:
file
由于我们修改了代码,所以我们出现了如上的问题。我们使用getFavoritesCountAttribute()方法来为模型实例添加favorites_count属性:
forum\app\Reply.php

.
.
public function getFavoritesCountAttribute()
{
    return $this->favorites->count();
}
.

再次刷新页面:
file
仔细观察一下,我们发现现在我们在最后还是使用了select * from channels where channels.id = '1' limit 1的语句。思考一下,每次查询thread时,我们也想要查询channel,所以我们可以像处理creator一样处理channel

.
.
protected $with = ['creator','channel'];
.
.

并且,我们不需要在控制器中再次预加载channel
app\Http\Controllers\ThreadsController.php

    .
    .
    protected function getThreads(Channel $channel, ThreadsFilters $filters)
    {
        $threads = Thread::latest()->filter($filters);

        if ($channel->exists) {
            $threads->where('channel_id', $channel->id);
        }

        $threads = $threads->get();
        return $threads;
    }
}

我们为了处理点赞这个动作以及后续的优化,使用了 4 个方法:favoritesfavoriteisFavoritedgetFavoritesCountAttribute。为了方便后期维护,我们将这 4 个方法抽取到trait中封装起来,再在Reply.php引用即可:
forum\app\Favoritable.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

trait Favoritable
{

    public function favorites()
    {
        return $this->morphMany(Favorite::class, 'favorited');
    }

    public function favorite()
    {
        $attributes = ['user_id' => auth()->id()];

        if (!$this->favorites()->where($attributes)->exists()) {
            return $this->favorites()->create($attributes);
        }

    }

    public function isFavorited()
    {
        return !!$this->favorites->where('user_id', auth()->id())->count();
    }

    public function getFavoritesCountAttribute()
    {
        return $this->favorites->count();
    }
}

再引用:
forum\app\Reply.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Reply extends Model
{
    use Favoritable;

    protected $guarded = [];
    protected $with = ['owner','favorites'];

    public function owner()
    {
        return $this->belongsTo(User::class,'user_id');  // 使用 user_id 字段进行模型关联
    }

}

运行一下测试,看功能是否完好:

$ APP_ENV=testing phpunit

file

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 1
发起讨论 只看当前版本


tiroGuang
看视频发现是不是少了两步
3 个点赞 | 2 个回复 | 问答