您现在的位置是:首页> 编程文章 个人博客模板
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 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
阅读量! (1228)
点击排行
网站公告
- 欢迎来到我的博客
1:欢迎来到我的博客
2:博客免费api接口现已上线
3:博客会定期更新文章
4:欢迎大家来捧场
站点信息
- 建站时间:2019-8-30
- 网站程序:php,laravel-swoole框架
- 今日流量:612(10分钟统计一次)
- 本月流量:9331
- 浏览总量:388258
- 统计方式:中间件,redis消息队列,定时任务