您现在的位置是:首页> 编程文章 个人博客模板

Laravel Blade 中使用 Eloquent 模型 6 个须知

无痕小Q个人博客 2020-02-05 21:21:33主页 1228人已围观

简介在接触 Laravel 过程中,最常见的关于性能问题之一就是在 Blade 模板中使用 Eloquent 方法和模型关系查询,从而导致多了一些不必要的查询和循环。在这篇文章里,我将会介绍一些不同的情况下如何进行更高效的处理。

情况 1、使用 belongsTo () 加载关联关系时,最好使用预加载的方式。

大家最常见的情况就是使用 @foreach 来循环每条记录,甚至某些要展示的列还要来自于父模型中的字段值。

@foreach ($sessions as $session)
<tr>
  <td>{{ $session->created_at }}</td>
  <td>{{ $session->user->name }}</td>
</tr>
@endforeach

很明显,这个例子中 session 从属于 user。在模型 app/Session.php 中定义一个 user() 方法: 

public function user()
{
    return $this->belongsTo(User::class);
}

现在的代码是没有问题的,但是在控制器中不同的代码会带来不同的性能问题。

控制器中性能低下的写法:

public function index()
{
    $sessions = Session::all();
    return view('sessions.index', compact('sessions');
}

控制器中性能高效的写法:

public function index()
{
    $sessions = Session::with('user')->get();
    return view('sessions.index', compact('sessions');
}

注意到两种写法的不同了吗?在查询的时候就提前加载关联关系,就是 预加载 .

如果你不这么做,当我们在模板中进行循环的时候,每次都要从数据库中查询一次当前 session 所属用户的信息。因此,如果表中有 100 条 session 记录的话,那么最终将会产生 101 次查询,其中 1 次用于查询 session 列表,另外 100 次用于查询每个相关的用户。

所以,千万要时刻牢记预加载的重要性。


情况 2. 加载 hasMany () 关系

另一种典型的情况是 您需要在父记录循环中列出所有子 数据。

@foreach ($posts as $post)
<tr>
  <td>{{ $post->title }}</td>
  <td>
    @foreach ($post->tags as $tag)
      <span class="tag">{{ $tag->name }}</span>
    @endforeach
  </td>
</tr>
@endforeach

猜猜看 – 在这里也同样适用。如果您不使用 “预加载” , 那么每条数据都会直接请求数据库.

所以,在您的控制器中,您应该这么写 :

public function index()
{
    $posts = Post::with('tags')->get(); // 不使用 Post::all()!
    return view('posts.index', compact('posts'));
}

情况 3. 在 hasMany () 的关系中不使用括号

假设您有一个带有投票的民意调查,并且想显示所有民意测验的投票数。
当然,您正在控制器中进行 预加载 :

public function index()
{
    $polls = Poll::with('votes')->get();
    return view('polls', compact('polls'));
}

然后 在 Blade 文件中 您这样写:

@foreach ($polls as $poll)
    <b>{{ $poll->question }}</b> 
    ({{ $poll->votes()->count() }})
    <br />
@endforeach

看起来没有什么问题 ,但是还是要请您注意  ->votes() 中的(), 使用 () 则会在每次轮询后将会有一个查询。因为它没有去获取预加载数据, 所以他将再次从 Eloquent 中进行操作

所以 您应该这么写: {{ $poll->votes->count() }}. 去除掉 ().

顺便说一句,这同样适用于 belongsTo 关系。在 Blade 中加载关系时请勿使用 ()。

说句题外话: 我之前在 StackOverflow 中,看到过更糟糕的例子。
例如 {{$ poll-> votes()-> get()-> count()}} 或者 @ foreach($ poll-> votes()-> get()as $ vote)... 

请使用 Laravel Debugbar
这种方式进行验证 ,并查看 SQL 查询的数量。


情况 4、关联模型对应记录不存在怎么办?

在 Laravel 中最常见的一个错误就是 “trying to get property of non-object”,相信你之前也一定遇到过这个错误信息。

通常情况下该错误都是类似下面这种代码导致的:

<td>{{ $payment->user->name }}</td>

原因是无法保证 payment 所属用户一定存在。可能用户已经被软删除,也可能是数据库中缺少对应的外键。

解决方法取决于你所使用 Laravel/PHP 的版本。在 Laravel 5.7 之前, 为了避免这种错误,可以设定默认值,语法如下:

{{ $payment->user->name or 'Anonymous' }}

自从 Laravel 5.7 开始, 新语法 遵循 PHP7 中引入的常见 PHP 运算符:

{{ $payment->user->name ?? 'Anonymous' }}

另外,你也可以在模型级别上设定默认值。

public function user()
{
    return $this->belongsTo(User::class)->withDefault();
}

调用 withDefault() 方法,在关联模型实例不存在时,会返回一个空模型。

不仅如此,你也可以在调用 withDefault() 方法的同时设置模型属性的默认值。

public function user()
{
    return $this->belongsTo(User::class)
      ->withDefault(['name' => 'Anonymous']);
}

情况 5、 在 Blade 模板中使用关联关系时,尽量避免使 where 语句。

你有没有在模板中见过这样的代码呢?

@foreach ($posts as $post)
    @foreach ($post->comments->where('approved', 1) as $comment)
        {{ $comment->comment_text }}
    @endforeach
@endforeach

使用 where(‘approved’, 1) 条件来过滤已经预加载的 comments 数据。

确实可以达到效果并且不会引起任何的性能问题。但是个人建议最好遵循 MVC 原则,逻辑应该在 View 外部,也就是 “逻辑” 层中的某个位置。比如 Eloquent 模型中,可以在 app/Post.php 中单独定义一种关联关系。

public function comments()
{
    return $this->hasMany(Comment::class);
}

public function approved_comments()
{
    return $this->hasMany(Comment::class)->where('approved', 1);
}

接下来你就可以在 Controller/Blade 中调用自定义的特殊方法。就像这样:$posts = Post::with(‘approved_comments’)->get();


方案 6. 避免访问器发生非常复杂的情况

最近,在一个项目中,我有一个任务:用信封图标表示消息,用任务的价格表示任务,该任务应该从包含该价格的最后一条消息中获取。听起来很复杂,确实如此。

我先是这样写的:

@foreach ($jobs as $job)
    ...
    @if ($job->messages->where('price is not null')->count())
        {{ $job->messages->where('price is not null')->sortByDesc('id')->first()->price }}
    @endif
@endforeach

当然,需要检查价格是否存在,然后获取该价格的最后一条消息,但是这个操作不应该出现在 blade 模板中。

所以我最终在 Eloquent 上使用 访问器 并在 app/Job.php 定义了以下内容:

public function getPriceAttribute()
{
    $price = $this->messages
        ->where('price is not null')
        ->sortByDesc('id')
        ->first();
    if (!$price) return 0;

    return $price->price;
}

当然,在这种复杂情况下,也很容易陷入 N + 1 查询问题,或者只是偶然地多次启动查询。因此,请使用 Laravel Debugbar 查找缺陷。

另外,我可以推荐一个名为 Laravel N+1 查询检测器 的第三方包。


额外说一点。在研究此主题时,我在 Laracasts 上看到一个糟糕的代码示例。有人给出以下代码,并寻求意见。不幸的是,现在的项目中经常看到这样的代码。因为它可以正常运行... 

@foreach($user->payments()->get() as $payment)
<tr>
    <td>{{$payment->type}}</td>
    <td>{{$payment->amount}}$</td>
    <td>{{$payment->created_at}}</td>
    <td>
        @if($payment->method()->first()->type == 'PayPal')
            <div><strong>Paypal: </strong> 
            {{ $payment->method()->first()->paypal_email }}</div>
        @else
            <div><strong>Card: </strong>
            {{ $payment->payment_method()->first()->card_brand }} **** **** **** 
            {{ $payment->payment_method()->first()->card_last_four }}</div>
        @endif
    </td>
</tr>
@foreach
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接 
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://laraveldaily.com/calling-eloquen...

译文地址:https://learnku.com/laravel/t/39449

阅读量! (1228)

关于本站

昵称:无痕小Q

职业:php-go-web开发工程师

现居:北京

Email:1838638884@qq.com

点击排行

路难行,行路难,一身汗水,满心长。脚下百里路,头顶艳阳天。坚定如磐石,信念似火烧。好男儿,人穷志不缺,天道也酬勤。四方走,走四方,一身是胆,豪气壮。前方坎坷途,千难万般阻。心若有明灯,身似般若行。好男 路难行,行路难,一身汗水,满心长。脚下百里路,头顶艳阳天。坚定如磐石,信念似火烧。好男儿,人穷志不缺,天道也酬勤。四方走,走四方,一身是胆,豪气壮。前方坎坷途,千难万般阻。心若有明灯,身似般若行。好男
    山花烂漫, 你是昨夜晓凤.

网站公告

  • 欢迎来到我的博客

  • 1:欢迎来到我的博客


    2:博客免费api接口现已上线


    3:博客会定期更新文章


    4:欢迎大家来捧场


    死生契阔,与子成说。执子之手,与子偕老。

站点信息

听闻远方有你,动身跋涉千里,我吹过你吹过的风,这算不算相拥,我喜欢你,从一而终,认真且怂。 海纳百川,有容乃大;壁立千仞,无欲则刚。
  • 建站时间:2019-8-30
  • 网站程序:php,laravel-swoole框架
  • 今日流量:612(10分钟统计一次)
  • 本月流量:9331
  • 浏览总量:388258
  • 统计方式:中间件,redis消息队列,定时任务
    甫昔少年日,早充观国宾。  读书破万卷,下笔如有神。