Laravel 13 Eloquent 高级特性

Eloquent 是 Laravel 的 ORM(对象关系映射),提供了优雅的 ActiveRecord 实现。本文将深入探讨 Laravel 13 Eloquent 的高级特性。

模型定义

基础模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use App\Observers\UserObserver;

#[ObservedBy(UserObserver::class)]
class User extends Model
{
use SoftDeletes;

protected $fillable = [
'name',
'email',
'password',
];

protected $hidden = [
'password',
'remember_token',
];

protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'settings' => 'array',
'is_admin' => 'boolean',
];
}

自定义主键

1
2
3
4
5
6
class User extends Model
{
protected $primaryKey = 'uuid';
public $incrementing = false;
protected $keyType = 'string';
}

关联关系

一对一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class User extends Model
{
public function profile()
{
return $this->hasOne(Profile::class);
}

public function latestPost()
{
return $this->hasOne(Post::class)->latestOfMany();
}

public function oldestPost()
{
return $this->hasOne(Post::class)->oldestOfMany();
}
}

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

一对多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}
}

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

多对多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class User extends Model
{
public function roles()
{
return $this->belongsToMany(Role::class)
->withPivot('assigned_at', 'assigned_by')
->withTimestamps()
->as('subscription');
}
}

class Role extends Model
{
public function users()
{
return $this->belongsToMany(User::class);
}
}

远程一对一

1
2
3
4
5
6
7
class User extends Model
{
public function country()
{
return $this->hasOneThrough(Country::class, Profile::class);
}
}

远程一对多

1
2
3
4
5
6
7
class User extends Model
{
public function comments()
{
return $this->hasManyThrough(Comment::class, Post::class);
}
}

多态关联

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Post extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}

public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}

class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
}

查询构建

条件查询

1
2
3
4
5
6
7
8
9
10
11
12
13
$users = User::where('active', true)
->where('age', '>=', 18)
->whereIn('role', ['admin', 'editor'])
->whereBetween('created_at', [$start, $end])
->whereNull('deleted_at')
->whereNotNull('email_verified_at')
->whereDate('created_at', '2024-01-01')
->whereYear('created_at', 2024)
->whereMonth('created_at', 1)
->whereDay('created_at', 1)
->whereTime('created_at', '12:00:00')
->whereColumn('updated_at', '>', 'created_at')
->get();

高级条件

1
2
3
4
5
6
7
8
9
10
$users = User::where(function ($query) {
$query->where('active', true)
->orWhere('role', 'admin');
})->where('verified', true)->get();

$users = User::whereRelation('posts', 'published', true)->get();
$users = User::whereDoesntHave('posts')->get();
$users = User::whereHas('posts', function ($query) {
$query->where('views', '>', 1000);
})->get();

聚合查询

1
2
3
4
5
6
7
$count = User::count();
$sum = Order::sum('total');
$avg = Product::avg('price');
$max = Product::max('price');
$min = Product::min('price');
$exists = User::where('email', $email)->exists();
$doesntExist = User::where('email', $email)->doesntExist();

预加载

基本预加载

1
2
3
$posts = Post::with('user')->get();
$posts = Post::with(['user', 'comments'])->get();
$posts = Post::with('user.profile')->get();

约束预加载

1
2
3
$posts = Post::with(['comments' => function ($query) {
$query->where('approved', true)->latest();
}])->get();

延迟预加载

1
2
3
$posts = Post::all();
$posts->load('user', 'comments');
$posts->loadMissing('user');

预加载计数

1
2
3
4
5
$posts = Post::withCount('comments')->get();
$posts = Post::withCount(['comments', 'likes'])->get();
$posts = Post::withCount(['comments as pending_comments' => function ($query) {
$query->where('approved', false);
}])->get();

访问器和修改器

访问器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User extends Model
{
protected function fullName(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes) => $attributes['first_name'] . ' ' . $attributes['last_name']
);
}

protected function avatar(): Attribute
{
return Attribute::make(
get: fn (?string $value) => $value ?: 'https://example.com/default-avatar.png'
);
}
}

修改器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class User extends Model
{
protected function password(): Attribute
{
return Attribute::make(
set: fn (string $value) => bcrypt($value)
);
}

protected function name(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
set: fn (string $value) => strtolower($value)
);
}
}

类型转换

基本类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
class User extends Model
{
protected $casts = [
'is_admin' => 'boolean',
'settings' => 'array',
'options' => 'collection',
'created_at' => 'datetime',
'expires_at' => 'datetime:Y-m-d',
'amount' => 'decimal:2',
'price' => 'encrypted',
'preferences' => 'encrypted:array',
];
}

自定义类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class User extends Model
{
protected $casts = [
'address' => Address::class,
];
}

class Address implements CastsAttributes
{
public function get(Model $model, string $key, mixed $value, array $attributes): ?Address
{
if ($value === null) {
return null;
}

$data = json_decode($value, true);
return new Address($data['street'], $data['city'], $data['zip']);
}

public function set(Model $model, string $key, mixed $value, array $attributes): string
{
return json_encode([
'street' => $value->street,
'city' => $value->city,
'zip' => $value->zip,
]);
}
}

模型事件

事件钩子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class User extends Model
{
protected static function booted(): void
{
static::creating(function (User $user) {
$user->uuid = (string) Str::uuid();
});

static::updating(function (User $user) {
if ($user->isDirty('email')) {
$user->email_verified_at = null;
}
});

static::deleting(function (User $user) {
$user->posts()->delete();
});
}
}

软删除

1
2
3
4
5
6
7
8
9
10
11
12
13
class User extends Model
{
use SoftDeletes;

protected $dates = ['deleted_at'];
}

// 使用
$user->delete();
$user->forceDelete();
$user->restore();
User::withTrashed()->get();
User::onlyTrashed()->get();

查询作用域

全局作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class ActiveScope implements Scope
{
public function apply(Builder $builder, Model $model): void
{
$builder->where('active', true);
}
}

class User extends Model
{
protected static function booted(): void
{
static::addGlobalScope(new ActiveScope);
}
}

// 或使用闭包
class User extends Model
{
protected static function booted(): void
{
static::addGlobalScope('active', function (Builder $builder) {
$builder->where('active', true);
});
}
}

本地作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class User extends Model
{
public function scopeActive($query)
{
return $query->where('active', true);
}

public function scopeAdmin($query)
{
return $query->where('role', 'admin');
}

public function scopeSearch($query, string $term)
{
return $query->where('name', 'like', "%{$term}%")
->orWhere('email', 'like', "%{$term}%");
}
}

// 使用
$users = User::active()->admin()->get();
$users = User::search('john')->get();

集合操作

1
2
3
4
5
6
7
8
$users = User::all();

$active = $users->filter(fn ($user) => $user->active);
$names = $users->map(fn ($user) => $user->name);
$sorted = $users->sortBy('name');
$grouped = $users->groupBy('role');
$first = $users->firstWhere('role', 'admin');
$contains = $users->contains('email', 'john@example.com');

最佳实践

1. 使用批量赋值保护

1
2
3
4
5
// 好的做法
protected $fillable = ['name', 'email'];

// 或
protected $guarded = ['id', 'is_admin'];

2. 使用预加载避免 N+1

1
2
3
4
5
6
7
8
// 好的做法
$posts = Post::with('user')->get();

// 不好的做法
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name; // N+1 查询
}

3. 使用查询作用域

1
2
3
4
5
// 好的做法
User::active()->verified()->get();

// 不好的做法
User::where('active', true)->where('verified', true)->get();

总结

Laravel 13 的 Eloquent 提供了强大而优雅的 ORM 实现。通过合理使用关联关系、预加载、访问器和修改器、类型转换等特性,可以构建出高效、可维护的数据模型。记住使用预加载避免 N+1 查询问题,使用查询作用域封装常用查询条件,并充分利用 Eloquent 的事件系统来实现业务逻辑。