- 原文地址: 《20 Laravel Eloquent Tips and Tricks》
- 永久地址: blog.kbiao.me/2019/01/03/…
- 译者:KBiao
在其表面简单易用的机制背后,还有不少半隐藏的功能或者少有人知的方法来实现一些颇有用的需求。 在本文中,我将向您展现一些技巧。php
若是你平时是这么作的:laravel
$article = Article::find($article_id);
$article->read_count++;
$article->save();
复制代码
那么你能够试试这样:git
$article = Article::find($article_id);
$article->increment('read_count');
复制代码
或者这样也是能够的:github
Article::find($article_id)->increment('read_count');
Article::find($article_id)->increment('read_count', 10); // +10
Product::find($produce_id)->decrement('stock'); // -1
复制代码
Eloquent有不少方法是两个方法的组合,实现 “请作X,不然作Y”这样的需求。sql
例 1 findOrFail():shell
能够把这样的代码:数据库
$user = User::find($id);
if (!$user) { abort (404); }
复制代码
换成这样:数组
$user = User::findOrFail($id);
复制代码
例 2 firstOrCreate():bash
不须要写这么长:闭包
$user = User::where('email', $email)->first();
if (!$user) {
User::create([
'email' => $email
]);
}
复制代码
这样就够了:
$user = User::firstOrCreate(['email' => $email]);
复制代码
在Eloquent模型中有一个名为boot()的神奇地方,您能够在其中覆盖默认行为:
class User extends Model {
public static function boot() {
parent::boot();
static::updating(function($model) {
// 记录一些日志
// 覆盖或者重写一些属性 好比$model->something = transform($something);
});
}
}
复制代码
可能最多见的例子之一是在建立模型对象时设置一些字段值。比方说你须要在建立对象时候生成UUID字段。
一般定义关系模型的方法是这样的
public function users() {
return $this->hasMany('App\User');
}
复制代码
但你是否知道在定义关系模型的时候就已经能够增长 where 或者 orderBy 的条件了? 好比说你须要定义一个特定类型的用户的关联关系而且用邮箱信息来排序,那你能够这么作:
public function approvedUsers() {
return $this->hasMany('App\User')->where('approved', 1)->orderBy('email');
}
复制代码
Eloquent模型有一些“参数”,会以该类的属性形式出现。 最经常使用的多是这些:
class User extends Model {
protected $table = 'users';
protected $fillable = ['email', 'password']; // 这些字段能够在模型的 create 方法中直接建立
protected $dates = ['created_at', 'deleted_at']; // 这些字段将会转换成 Carbon类型的,能够方便的使用 Carbon 提供的时间方法
protected $appends = ['field1', 'field2']; // 序列化时候附加的额外属性,经过模型中定义 getXXXAttribute 的方式来定义
}
复制代码
可不只仅有这些,还有:
protected $primaryKey = 'uuid'; // 模型的主键名称能够不是默认的 id
public $incrementing = false; // 甚至能够没必要是自增的类型!
protected $perPage = 25; // 是的,你还定义模型集合分页参数(默认是 15)
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at'; // 默认的时间戳字段也是能够改变的
public $timestamps = false; // 或者彻底不用他
复制代码
甚至还有更多,我仅仅列出了最有意思的一部分,更多请查看默认抽象Model类的代码,并查看全部使用的trait 方法。
find()方法想必你们都知道的吧?
$user = User::find(1);
复制代码
我很惊讶不多有人知道它能够接受多个ID做为数组:
$users = User::find([1,2,3]);
复制代码
有一种很优雅的方式能够把下面的代码:
$users = User::where('approved', 1)->get();
复制代码
改为这样:
$users = User::whereApproved(1)->get();
复制代码
是的,你也能够改为任何字段的名称,并将其做为后缀附加到“where”,它将神奇的产生预想的效果(经过魔术方法实现调用)。
此外,Eloquent中还有一些与日期/时间相关的预约义方法:
User::whereDate('created_at', date('Y-m-d'));
User::whereDay('created_at', date('d'));
User::whereMonth('created_at', date('m'));
User::whereYear('created_at', date('Y'));
复制代码
一个更复杂的“技巧”。 若是你有帖子,但要经过最新帖子对它们进行排序? 顶部有最新更新主题的论坛中很是常见的要求,对吧?
首先,定义关于该主题的最新帖子的关系:
public function latestPost() {
return $this->hasOne(\App\Post::class)->latest();
}
复制代码
接下来能够在咱们的控制器中用这个神奇的方法来实现:
$users = Topic::with('latestPost')->get()->sortByDesc('latestPost.created_at');
复制代码
大部分时候咱们用 if-else 来实现按条件查询,相似这样的代码:
if (request('filter_by') == 'likes') {
$query->where('likes', '>', request('likes_amount', 0));
}
if (request('filter_by') == 'date') {
$query->orderBy('created_at', request('ordering_rule', 'desc'));
}
复制代码
可是一个更好的方法是——使用 when()方法
$query = Author::query();
$query->when(request('filter_by') == 'likes', function ($q) {
return $q->where('likes', '>', request('likes_amount', 0));
});
$query->when(request('filter_by') == 'date', function ($q) {
return $q->orderBy('created_at', request('ordering_rule', 'desc'));
});
复制代码
它看起来可能不会更短或更优雅,但最强大的是能够传递参数:
$query = User::query();
$query->when(request('role', false), function ($q, $role) {
return $q->where('role_id', $role);
});
$authors = $query->get();
复制代码
假设有个 Post(帖子) 对象属于 Author (做者)对象,在 Blade 模板中有下面的代码
{{ $post->author->name }}
复制代码
可是若是做者被删除,或者因为某种缘由没有设置呢? 那么就会致使报错,多是“property of non-object(非对象属性)”。
固然你能够用下面的代码来必变这种错误:
{{ $post->author->name ?? '' }}
复制代码
不过你能够再模型定义时候就解决这个问题:
public function author()
{
return $this->belongsTo('App\Author')->withDefault();
}
复制代码
在这个例子中,在这个帖子下没有关联做者的时候,author()关联关系将返回一个空的App\Author 模型。
更进一步,咱们能够设置一些默认属性个这个模型。
public function author() {
return $this->belongsTo('App\Author')->withDefault([
'name' => 'Guest Author'
]);
}
复制代码
假设你有下面的一段代码:
(设定了一个在返回对象时候的附加属性 ‘full_name’参见 tips5 模型属性: 时间戳, 附加属性(appends) 等)
function getFullNameAttribute() {
return $this->attributes['first_name'] . ' ' . $this->attributes['last_name'];
}
复制代码
若是你想要按照 full_name
来排序的话?下面的代码是不行的:
$clients = Client::orderBy('full_name')->get(); //不行滴
复制代码
固然解决方案也是很是简单。 咱们须要在获得结果之后再对他们进行排序。
$clients = Client::get()->sortBy('full_name'); //稳了
复制代码
注意两个方法名字是不同的——不是 orderBy
而是 sortBy
。
(一个是 SQL 语句,自定义属性是数据库没有的字段固然不能直接用。可是查询的返回都是一个 Collection 对象,Laravel 为集合提供了不少方便的操做方法,sortBy 就是其中一个,固然还能够用 filter 等集合操做)
若是你但愿User :: all()始终按名称字段排序,该怎么办? 你能够分配全局的查询做用域。 让咱们回到上面已经提到的boot()方法。
protected static function boot() {
parent::boot();
// 默认按照name 字段升序
static::addGlobalScope('order', function (Builder $builder) {
$builder->orderBy('name', 'asc');
});
}
复制代码
这里还有更多关于请求范围做用域的介绍。
有时咱们须要在Eloquent语句中添加原生查询语句。 幸运的是,它提供了这样的功能。
// 原生 where 语句
$orders = DB::table('orders')
->whereRaw('price > IF(state = "TX", ?, 100)', [200])
->get();
// 原生 having 语句
Product::groupBy('category_id')->havingRaw('COUNT(*) > 1')->get();
// 原生 orderBy 语句
User::where('created_at', '>', '2016-01-01')
->orderByRaw('(updated_at - created_at) desc')
->get();
复制代码
(本质上Eloquent就是对 DB 查询对象的一个封装,因此能够用在 DB 上的原始查询方法,均可以用在继承自 Eloquent 的 model 对象上。)
很简单的一条,不须要太多解释。这是生成数据库条目副本的最佳手段。
$task = Tasks::find(1);
$newTask = $task->replicate();
$newTask->save();
复制代码
不彻底与Eloquent相关,它更可能是Collection 集合类提供的方法,但仍然很强大 —— 处理更大的数据集,你能够将它们分红几块。
不要这么作:
$users = User::all();
foreach ($users as $user) {
// ...
复制代码
而是这样:
User::chunk(100, function ($users) {
foreach ($users as $user) {
// ...
}
});
复制代码
相似于数据分片,减小占用提高性能
咱们都知道这个的 Artisan 的命令:
php artisan make:model Company
复制代码
但你是否知道它还有三个颇有用的参数标记用来生成与这个模型关联的其余文件?
php artisan make:model Company -mcr
复制代码
你知道 - > save()方法是能够接受参数的吗? 所以,咱们能够告诉它“忽略” updated_at默认填充当前时间戳的功能。 看这个例子:
$product = Product::find($id);
$product->updated_at = '2019-01-01 10:00:00';
$product->save(['timestamps' => false]);
复制代码
这里咱们动态的重写的 update_at
字段,而不是预先在模型中定义。
Laravel 默认会给全部实体类配置时间戳,若是不须要通常是在模型中指定
$timestamps = false
你有没有曾想过下面这段代码返回的 result 是什么?
$result = $products->whereNull('category_id')->update(['category_id' => 2]);
复制代码
个人意思是,更新语句是在数据库中正确执行的,但 $ result 变量会包含什么?
答案是受影响的行。 所以,若是您须要检查受影响的行数,则无需再调用任何其余方法 - update()方法将为你返回这个数字。
假设在你的 SQL 查询中 包含了 and / or 这样的关键字,以下:
... WHERE (gender = 'Male' and age >= 18) or (gender = 'Female' and age >= 65)
复制代码
怎么翻译成 Eloquent的查询呢? 这是错误的方法:
$q->where('gender', 'Male');
$q->orWhere('age', '>=', 18);
$q->where('gender', 'Female');
$q->orWhere('age', '>=', 65);
复制代码
这个顺序是有问题的。正确的方法稍微有些复杂,须要用到闭包函数做为子查询:
$q->where(function ($query) {
$query->where('gender', 'Male')
->where('age', '>=', 18);
})->orWhere(function($query) {
$query->where('gender', 'Female')
->where('age', '>=', 65);
})
复制代码
最后一条,你能够个 orWhere 方法传递一个数组。
常规用法是:
$q->where('a', 1);
$q->orWhere('b', 2);
$q->orWhere('c', 3);
复制代码
你也能够用下面的语句实现同样的功能:
$q->where('a', 1);
$q->orWhere(['b' => 2, 'c' => 3]);
复制代码