Laravel 13 Eloquent 改进详解

摘要

Laravel 13 对 Eloquent ORM 进行了多项改进,包括访问器、修改器和关系定义的增强。本文将深入讲解 Laravel 13 的 Eloquent 改进,包括:

  • 访问器与修改器新语法
  • 属性转换增强
  • 关系定义改进
  • 查询优化
  • 实战案例与最佳实践

本文适合希望掌握 Laravel 13 Eloquent 新特性的开发者。

1. 访问器与修改器

1.1 访问器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;

class User extends Model
{
protected function fullName(): Attribute
{
return Attribute::make(
get: fn($value, $attributes) => $attributes['first_name'] . ' ' . $attributes['last_name'],
);
}

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

1.2 修改器

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

protected function email(): Attribute
{
return Attribute::make(
set: fn($value) => strtolower($value),
);
}
}

1.3 访问器与修改器组合

1
2
3
4
5
6
7
8
9
10
class User extends Model
{
protected function name(): Attribute
{
return Attribute::make(
get: fn($value) => ucfirst($value),
set: fn($value) => strtolower($value),
);
}
}

1.4 属性缓存

1
2
3
4
5
6
7
8
9
class User extends Model
{
protected function expensiveComputation(): Attribute
{
return Attribute::make(
get: fn($value) => $this->computeExpensiveValue(),
)->shouldCache();
}
}

2. 属性转换

2.1 基本转换

1
2
3
4
5
6
7
8
9
10
class User extends Model
{
protected $casts = [
'is_admin' => 'boolean',
'options' => 'array',
'settings' => 'collection',
'created_at' => 'datetime',
'expires_at' => 'date',
];
}

2.2 枚举转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum Status: string
{
case Draft = 'draft';
case Published = 'published';
case Archived = 'archived';
}

class Post extends Model
{
protected $casts = [
'status' => Status::class,
];
}

// 使用
$post->status = Status::Published;
echo $post->status->value; // 'published'

2.3 加密转换

1
2
3
4
5
6
7
class User extends Model
{
protected $casts = [
'secret' => 'encrypted',
'api_key' => 'encrypted',
];
}

2.4 自定义转换

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
<?php

namespace App\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class Json implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
return json_decode($value, true);
}

public function set($model, string $key, $value, array $attributes)
{
return json_encode($value);
}
}

class Post extends Model
{
protected $casts = [
'metadata' => Json::class,
];
}

3. 关系定义

3.1 一对一

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

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

3.2 一对多

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 author()
{
return $this->belongsTo(User::class, 'author_id');
}
}

3.3 多对多

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

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

3.4 多态关系

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();
}
}

4. 查询优化

4.1 延迟加载

1
2
3
4
5
// N+1 问题
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name; // 每次查询
}

4.2 预加载

1
2
3
4
5
// 解决 N+1
$posts = Post::with('author')->get();
foreach ($posts as $post) {
echo $post->author->name; // 不额外查询
}

4.3 嵌套预加载

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

4.4 条件预加载

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

4.5 懒加载

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

5. 模型事件

5.1 事件监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User extends Model
{
protected static function booted()
{
static::creating(function ($user) {
$user->api_token = Str::random(60);
});

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

5.2 观察者

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
<?php

namespace App\Observers;

use App\Models\User;

class UserObserver
{
public function created(User $user)
{
// 用户创建后
}

public function updated(User $user)
{
// 用户更新后
}

public function deleted(User $user)
{
// 用户删除后
}
}

// 注册观察者
use App\Models\User;
use App\Observers\UserObserver;

User::observe(UserObserver::class);

5.3 属性观察者

1
2
3
4
5
6
7
use Illuminate\Database\Eloquent\Attributes\ObservedBy;

#[ObservedBy(UserObserver::class)]
class User extends Model
{
// 自动注册观察者
}

6. 实战案例

6.1 完整模型示例

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use App\Observers\UserObserver;
use App\Enums\UserStatus;

#[ObservedBy(UserObserver::class)]
class User extends Model
{
protected $fillable = [
'name',
'email',
'password',
'status',
'settings',
];

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

protected $casts = [
'status' => UserStatus::class,
'settings' => 'array',
'email_verified_at' => 'datetime',
];

protected function password(): Attribute
{
return Attribute::make(
set: fn($value) => bcrypt($value),
);
}

protected function avatar(): Attribute
{
return Attribute::make(
get: fn($value) => $value
? Storage::url($value)
: asset('images/default-avatar.png'),
);
}

public function posts(): HasMany
{
return $this->hasMany(Post::class, 'author_id');
}

public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class)
->withTimestamps();
}

public function scopeActive($query)
{
return $query->where('status', UserStatus::Active);
}

public function scopeAdmins($query)
{
return $query->whereHas('roles', fn($q) => $q->where('name', 'admin'));
}
}

7. 最佳实践

7.1 模型命名

1
2
3
4
5
6
7
8
9
// 推荐:单数形式
User
Post
Comment

// 不推荐
Users
Posts
Comments

7.2 关系命名

1
2
3
4
5
6
7
// 推荐:描述性命名
public function publishedPosts()
public function recentComments()

// 不推荐
public function posts2()
public function commentsNew()

8. 总结

Laravel 13 的 Eloquent 改进提供了更强大的模型定义能力:

  1. 访问器/修改器:简洁的属性定义
  2. 属性转换:支持枚举和自定义转换
  3. 关系定义:灵活的关系类型
  4. 查询优化:预加载解决 N+1

参考资料