Laravel已经是众所周知的“优雅”、“简洁”、“实用”、“敏捷”、“特性丰富”等的代名词,尤其是对PHP开发者来说。当然了,也正是因为它特性过于“丰富”,导致很多特性并没有列在文档里,或者曾经列出来过,但是出于某些原因,后来又移除了。很多时候,我们给新手学习建议的时候,都不会建议他直接去看文档,因为文档体系的展开,往往并不是新手能hold住的。我们往往会推荐他们从一些过来人的、真正优雅规范的教程开始上手,上手以后,再在实际当中自行查阅文档。
本文里,将列出一些截至laravel 5.7的最佳实践与使用建议,不论新手与否,相信都可以帮你将laravel用得更好,将laravel的优雅进行到底,同时更充分地利用起laravel的特性来。
更好的阅读体验,请直接移步原文出处:www.pilishen.com/posts/pushi…
(一)将一些常用的数据库查询,放到local scopes中
$orders = Order::where('status', 'delivered')->where('paid', true)->get();
复制代码
我们经常会写些类似的逻辑,但是当条件查询啥的多了以后,也会变得繁琐,而且期间的很多条件限定与查询,就不能复用,每次都得重复写,这个时候local scopes就登场了。local scopes你可以理解成本地的查询域,或者更直白一些,就是一个常用的查询条件片段。我们可以在model里这样:
class Order extends Model
{
...
public function scopeDelivered($query) {
return $query->where('status', 'delivered');
}
public function scopePaid($query) {
return $query->where('paid', true);
}
}
复制代码
可以看到这些查询域,都是以scope开头的,后面接上你想要的名字,然后在调用的时候,只需要调用scope后面的实际名字即可:
$orders = Order::delivered()->paid()->get();
复制代码
所以这个查询域,就是laravel query builder的一个个片段,或者说数据库条件查询的片段,然后它们可以拼接起来,让我们终端的代码更加简洁直观。
当然,我们可以更进一步,给这个查询域传上参数,形成动态的条件查询,比如下面这样:
class Order extends Model
{
...
public function scopeStatus($query, string $status) {
return $query->where('status', $status);
}
}
$orders = Order::status('delivered')->paid()->get();
复制代码
(二)尽可能地使用自定义Form Request
在很多不那么规范的教程里,你经常会看到如下的代码:
public function store(Request $request)
{
$validatedData = $request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
// The blog post is valid...
}
复制代码
不规范的原因,就是他们把所有能想到的东西,都丢在controller里就完事了,但这样触犯的忌讳就太多了,违背的原则也就太多了,更重要的,随着项目的进行,很容易让代码变成大花脸,变得越来越没法维护和扩展,更不用说别人甚至自己回头阅读了。那么上面的这个例子,就是让controller去管了它本不应该负责的数据验证逻辑,而这块,laravel有更优雅的方式来解放controller,也即是我们的自定义form request:
php artisan make:request StoreBlogPost
复制代码
执行了以后,在app/Http/Requests/
文件夹下,就会多出下面这么个单独的类:
class StoreBlogPostRequest extends FormRequest
{
public function authorize()
{
return $this->user()->can('create.posts');
}
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
}
复制代码
这样了以后,在controller里,就不需要传Request
了,而是传上我们刚才定义的这个StoreBlogPostRequest
:
use App\Http\Requests\StoreBlogPostRequest;
public function store(StoreBlogPostRequest $request)
{
// 到这儿的时候,post的相关数据就已经验证好了
}
复制代码
当然在这个自定义的request类里,我们可以做的文章不止这一点,比如我们还可以具体定义每一个字段验证失败时的错误信息,这个可以通过定义messages()
方法来实现,我们也可以将request类里的错误信息,在blade当中去相应地返回。这些呢,在我们的《Laravel5.7优雅实战入门:第二版》课程里都已经详细说了,这里就实在懒得啰嗦了。
(三)将一些自带的魔术查询方法用起来
- 按照
created_at
一栏,进行降序排列(‘desc’)
User::latest()->get();
复制代码
- 按照任意字段,降序排列查询
User::latest('last_login_at')->get();
复制代码
- 以随机的顺序获取数据
User::inRandomOrder()->get();
复制代码
- 只有当特定条件为
true
时,才执行相应查询
// 假设用户在news页面,想通过下面的url进行最新消息的排序
// mydomain.com/news?sort=new
User::when($request->query('sort'), function ($query, $sort) {
if ($sort == 'new') {
return $query->latest();
}
return $query;
})->get();
复制代码
与这个when()
方法相对应的,还有一个unless()
, 它是when()
的反面,也即只有为false的时候,才执行某个查询
(四)使用Model关系,来避免大段的查询语句,甚至糟糕的查询
时至今日,你是否还在写一些大段的、难写难读又难复用的SQL查询语句呢?比如为了获取更多的信息,使用了大量的join
。即使你利用laravel的Query Builder,这些语句也非常难写,每次写一个是否得花半天呢?而且如果你不是那么懂,本来一个很快的查询,可能让你写糟糕了,反而非常非常慢。
但是如果你懂得laravel里的Model关系,懂得ORM这种更现代的查询方式,那么往往几乎没有什么难的查询,写的时候根本无需动脑,而且很多逻辑都可以复用,并且避免了因不懂背后原理而导致性能反而下降的问题。
这一方面,我们pilishen.com已经说得足够多了,比如可以看看我们之前的一篇相关帖子《laravel框架中的Model操作数据库 , 相比DB类有什么明显的优越性吗?》
当然这些,在我们的《Laravel5.7优雅实战入门:第二版》课程里也都详尽演示了。
(五)使用队列job来处理耗时操作
原来我们课程里老说,“高性能离不开异步,异步离不开队列”。这方面,laravel的队列job,在处理耗时的、可以放在后台运行的任务上,是一个强大的、必须要掌握的工具。
想发送邮件?队列job。 想发送广播消息?队列job。 想进行较多的图片操作?队列job。 。。。
类似的耗时操作,队列可以让你的前台用户无需等待,背后处理即可。这期间,你甚至可以设置队列的频道或名字,设置优先级,设置超时时间,设置重试次数,等等等等。
(六)善用属性获取器(accessor)与修改器(mutator)
假设你只有first_name
和 last_name
两个字段,然后你想着每次都能轻松地取得user的全名name,那么可以:
class User extends Model
{
...
public function getNameAttribute(): string
{
return $this->first_name.' '.$this->last_name;
}
}
复制代码
这样当你获取$user->name
时,就能自动拼接起全名来。默认的这个$user->name
属性,并不会自动附加到你的user实例上,也即这个时候你dd($user)
,并不会显示出name
属性来,那么怎么样让这个name
属性自动附加到$user
实例上呢?可以如下:
class User extends Model
{
protected $appends = [
'name',
];
...
public function getNameAttribute(): string
{
return $this->first_name.' '.$this->last_name;
}
}
复制代码
这个时候,每次我们dd($user)
,或者在blade视图以及js前端调用中,就自然会带上这个拼接出来的属性了,当然了,这个属性,并没有实际存在数据库中。
(七)不要将model相关的静态属性或内容,放到config当中
假设有个BettingOdds.php
:
class BettingOdds extends Model
{
...
}
复制代码
可能有的人会将一些静态内容放到比如说config/bettingOdds.php
中:
return [
'sports' => [
'soccer' => 'sport:1',
'tennis' => 'sport:2',
'basketball' => 'sport:3',
...
],
];
复制代码
然后呢,获取的时候这样config(’bettingOdds.sports.soccer’);
,但是这样并不好,存在一些隐患,不如直接放到model当中:
class BettingOdds extends Model
{
protected static $sports = [
'soccer' => 'sport:1',
'tennis' => 'sport:2',
'basketball' => 'sport:3',
...
];
}
复制代码
然后这样来获取BettingOdds::$sports['soccer'];
而且这样,在进一步的数据调用中,更方便,比如:
class BettingOdds extends Model
{
protected static $sports = [
'soccer' => 'sport:1',
'tennis' => 'sport:2',
'basketball' => 'sport:3',
...
];
public function scopeSport($query, string $sport)
{
if (! isset(self::$sports[$sport])) {
return $query;
}
return $query->where('sport_id', self::$sports[$sport]);
}
}
复制代码
那么这个时候,我们就可以像前面说到的,BettingOdds::sport('soccer')->get();
(八)多使用集合collection,而不是原始的array来操作数据
$fruits = ['apple', 'pear', 'banana', 'strawberry'];
foreach ($fruits as $fruit) {
echo 'I have '. $fruit;
}
复制代码
以前,我们都习惯直接用array来操作相关数据,但是在laravel里,更优雅的方式是多使用collection及相关方法
$fruits = collect($fruits);
$fruits = $fruits->reject(function ($fruit) {
return $fruit === 'apple';
})->toArray();
['pear', 'banana', 'strawberry']
复制代码
这样我们就可以方便地利用上一些filter、map、transform、reject等等方法,既简单,又优雅
我们知道laravel的Query Builders,当在它后面最终调用->get()
方法时,返回的是一个集合Collection实例。但是这里,不要将单纯的Collection和Query builder混淆起来:
- 在Query Builder中,在你最终调用
->get()
或first()
这些方法前,我们实际上并没有获取任何数据,比如当我们用orderBy()
、where()
这些查询方法时 - 当我们调用了
->get()
或first()
方法时,数据才真的去获取,相应的内存才真的占用,然后返回一个集合实例
所以,如果能在Query Builder的环节,就进行数据的过滤或条件限定,那就要在这一环节做。而不要说先获取了数据,返回了Collection实例以后,再用Collection相关的方法去过滤或操作,这样的话耗费的内存资源就容易多得多。当然了,这期间也要限定好查询,利用好数据库索引,关于数据库索引,如果你还不够精通,记得看我们的国际IT专场:《每个程序猿必须且一定要懂的“数据库索引”》