Eloquent 模型提供了两个强大的特性来简化代码:作用域(Scopes)用于封装常用的查询约束,访问器(Accessors)和修改器(Mutators)则用于处理属性的格式化和转换。 本章将系统讲解这些功能,让你的模型代码更加简洁、可复用。
作用域允许你定义通用的查询约束,并在模型查询时重复使用。作用域分为两类:
本地作用域是在模型中以 scope 为前缀定义的方法,返回查询构建器实例。
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public function scopePublished($query)
{
return $query->where('is_published', true);
}
public function scopeOfUser($query, $userId)
{
return $query->where('user_id', $userId);
}
public function scopePopular($query, $minComments = 10)
{
return $query->where('comments_count', '>=', $minComments);
}
}
调用作用域时,直接使用方法名(去掉 scope 前缀):
$posts = Post::published()->get();
$userPosts = Post::published()->ofUser(1)->get();
$popularPosts = Post::published()->popular(5)->get();
作用域可以链式调用,也可以与其他查询构建器方法结合使用。
作用域可以接受参数,如上例中的 $userId 和 $minComments。
全局作用域自动应用于模型的所有查询,无需手动调用。常用于「软删除」、「多租户」等场景。
首先创建一个实现 Illuminate\Database\Eloquent\Scope 接口的类:
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class ActiveUserScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$builder->where('active', 1);
}
}
在模型的 booted 方法中注册:
namespace App\Models;
use App\Scopes\ActiveUserScope;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected static function booted()
{
static::addGlobalScope(new ActiveUserScope);
}
}
也可以直接在 booted 中使用闭包:
protected static function booted()
{
static::addGlobalScope('active', function (Builder $builder) {
$builder->where('active', 1);
});
}
在查询时临时移除全局作用域:
// 移除所有全局作用域
$users = User::withoutGlobalScopes()->get();
// 移除指定作用域(使用闭包注册时需指定名称)
$users = User::withoutGlobalScope('active')->get();
// 移除指定作用域类
$users = User::withoutGlobalScope(ActiveUserScope::class)->get();
访问器用于在获取模型属性时自动格式化其值。定义方式是在模型中添加 get{Attribute}Attribute 方法。
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
public function getAvatarUrlAttribute()
{
return $this->avatar ? asset('storage/' . $this->avatar) : 'https://www.gravatar.com/avatar/' . md5($this->email);
}
}
访问器通过属性名(蛇形命名)访问:
$user = User::find(1);
echo $user->full_name; // 输出 "John Doe"
echo $user->avatar_url; // 输出头像 URL
getFullNameAttribute,对应的属性名是蛇形 full_name。Laravel 会自动转换。
在访问器内部可以通过 $this->attributes 访问原始数据库值:
public function getNameAttribute($value)
{
return ucfirst($value);
}
访问器可以接收一个参数 $value,即数据库原始值,方便处理。
修改器用于在设置模型属性时自动转换值。定义方式是在模型中添加 set{Attribute}Attribute 方法。
public function setFirstNameAttribute($value)
{
$this->attributes['first_name'] = strtolower($value);
}
public function setPasswordAttribute($value)
{
$this->attributes['password'] = bcrypt($value);
}
当赋值时,修改器会自动触发:
$user = new User;
$user->first_name = 'JOHN'; // 自动转为小写 'john'
$user->password = 'secret'; // 自动加密后存储
fill() 或 create() 批量赋值同样会触发。
Laravel 提供了便捷的日期转换,也可以结合访问器/修改器实现更复杂的逻辑。
protected $casts = [
'created_at' => 'datetime',
'published_at' => 'datetime:Y-m-d',
];
使用 $castsCarbon 实例或指定格式。
通过访问器可以创建不存在的虚拟属性,例如上面的 full_name。
结合作用域和访问器,构建一个功能完善的 Post 模型:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $fillable = ['title', 'content', 'user_id', 'is_published', 'published_at'];
protected $casts = [
'is_published' => 'boolean',
'published_at' => 'datetime',
];
// 本地作用域
public function scopePublished($query)
{
return $query->where('is_published', true);
}
public function scopeDraft($query)
{
return $query->where('is_published', false);
}
public function scopeRecent($query)
{
return $query->orderBy('created_at', 'desc');
}
// 访问器
public function getExcerptAttribute()
{
return \Str::limit(strip_tags($this->content), 100);
}
public function getPublishedAtFormattedAttribute()
{
return $this->published_at ? $this->published_at->format('Y年m月d日') : null;
}
// 修改器
public function setTitleAttribute($value)
{
$this->attributes['title'] = ucfirst($value);
$this->attributes['slug'] = \Str::slug($value);
}
}
使用示例:
// 获取最近发布的10篇文章
$posts = Post::published()->recent()->take(10)->get();
foreach ($posts as $post) {
echo $post->title; // 首字母大写
echo $post->excerpt; // 摘要
echo $post->published_at_formatted; // 格式化日期
}
// 创建新文章
$post = Post::create([
'title' => 'laravel scopes',
'content' => '...',
'user_id' => 1,
'is_published' => false,
]);
// title 自动转为 'Laravel scopes',slug 自动生成
$this->getOriginal('attribute') 获取。
create、update 还是 fill 方法,修改器都会在赋值时触发。
作用域与访问器是 Laravel 模型的两大得力助手,它们让代码更加整洁、可维护。 作用域封装查询逻辑,访问器与修改器统一数据格式,合理运用这些特性可以大幅提升开发效率和代码质量。 下一章我们将学习模型事件与观察者,掌握模型生命周期的精细控制。