跳至内容

Eloquent:入门

介绍

Laravel 包含 Eloquent,这是一个对象关系映射器 (ORM),它使你与数据库的交互变得轻松愉快。使用 Eloquent 时,每个数据库表都有一个对应的“模型”,用于与该表交互。除了从数据库表中检索记录之外,Eloquent 模型还允许你在表中插入、更新和删除记录。

开始之前,请务必在应用程序的config/database.php配置文件中配置数据库连接。有关配置数据库的更多信息,请参阅数据库配置文档

生成模型类

首先,让我们创建一个 Eloquent 模型。模型通常位于app\Models目录中并扩展Illuminate\Database\Eloquent\Model类。您可以使用make:model Artisan 命令生成新模型:

1php artisan make:model Flight

如果您想在生成模型时生成数据库迁移--migration,您可以使用或-m选项:

1php artisan make:model Flight --migration

生成模型时,你可以生成各种其他类型的类,例如Factories、种子、策略、控制器和表单请求。此外,这些选项可以组合起来,一次创建多个类:

1# Generate a model and a FlightFactory class...
2php artisan make:model Flight --factory
3php artisan make:model Flight -f
4 
5# Generate a model and a FlightSeeder class...
6php artisan make:model Flight --seed
7php artisan make:model Flight -s
8 
9# Generate a model and a FlightController class...
10php artisan make:model Flight --controller
11php artisan make:model Flight -c
12 
13# Generate a model, FlightController resource class, and form request classes...
14php artisan make:model Flight --controller --resource --requests
15php artisan make:model Flight -crR
16 
17# Generate a model and a FlightPolicy class...
18php artisan make:model Flight --policy
19 
20# Generate a model and a migration, factory, seeder, and controller...
21php artisan make:model Flight -mfsc
22 
23# Shortcut to generate a model, migration, factory, seeder, policy, controller, and form requests...
24php artisan make:model Flight --all
25php artisan make:model Flight -a
26 
27# Generate a pivot model...
28php artisan make:model Member --pivot
29php artisan make:model Member -p

检查模型

有时,仅通过浏览模型代码可能很难确定其所有可用属性和关系。您可以尝试使用model:showArtisan 命令,它能方便地概览所有模型的属性和关系:

1php artisan model:show Flight

Eloquent 模型约定

该命令生成的模型make:model将被放置在app/Models目录中。让我们研究一个基本的模型类,并讨论一些 Eloquent 的关键约定:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 // ...
10}

表名

浏览上面的示例后,您可能已经注意到,我们并没有告诉 Eloquent 我们的Flight模型对应的数据库表是哪个。按照惯例,除非明确指定其他名称,否则类的“蛇形命名法”(复数形式)名称将用作表名。因此,在这种情况下,Eloquent 会假定Flight模型将记录存储在flights表中,而AirTrafficController模型会将记录存储在air_traffic_controllers表中。

如果您的模型对应的数据库表不符合此约定,您可以通过table在模型上定义属性来手动指定模型的表名:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The table associated with the model.
11 *
12 * @var string
13 */
14 protected $table = 'my_flights';
15}

主键

Eloquent 还会假定每个模型对应的数据库表都有一个名为 的主键列id。如有必要,你可以在模型上定义一个 protected$primaryKey属性,指定另一个列作为模型的主键:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The primary key associated with the table.
11 *
12 * @var string
13 */
14 protected $primaryKey = 'flight_id';
15}

此外,Eloquent 假定主键是一个自增的整数值,这意味着 Eloquent 会自动将主键转换为整数。如果您希望使用非自增或非数字的主键,则必须$incrementing在模型上定义一个公共属性,并将其设置为false

1<?php
2 
3class Flight extends Model
4{
5 /**
6 * Indicates if the model's ID is auto-incrementing.
7 *
8 * @var bool
9 */
10 public $incrementing = false;
11}

如果模型的主键不是整数,则应$keyType在模型上定义一个受保护的属性。此属性的值应为string

1<?php
2 
3class Flight extends Model
4{
5 /**
6 * The data type of the primary key ID.
7 *
8 * @var string
9 */
10 protected $keyType = 'string';
11}

“复合”主键

Eloquent 要求每个模型至少有一个唯一标识的“ID”,可以作为其主键。Eloquent 模型不支持“复合”主键。但是,除了表的唯一标识主键之外,您还可以自由地向数据库表添加其他多列唯一索引。

UUID 和 ULID 键

您可以选择使用 UUID,而不是使用自增整数作为 Eloquent 模型的主键。UUID 是通用唯一的字母数字标识符,长度为 36 个字符。

如果你希望模型使用 UUID 键而不是自增整数键,你可以Illuminate\Database\Eloquent\Concerns\HasUuids在模型上使用 trait。当然,你应该确保模型有一个与 UUID 等效的主键列

1use Illuminate\Database\Eloquent\Concerns\HasUuids;
2use Illuminate\Database\Eloquent\Model;
3 
4class Article extends Model
5{
6 use HasUuids;
7 
8 // ...
9}
10 
11$article = Article::create(['title' => 'Traveling to Europe']);
12 
13$article->id; // "8f8e8478-9035-4d23-b9a7-62f4d2612ce5"

默认情况下,该HasUuids特性将为你的模型生成“有序”的 UUID。这些 UUID 对于索引数据库存储来说更高效,因为它们可以按字典顺序排序。

您可以通过在模型上定义方法来覆盖给定模型的 UUID 生成过程newUniqueId。此外,您还可以通过uniqueIds在模型上定义方法来指定哪些列应该接收 UUID:

1use Ramsey\Uuid\Uuid;
2 
3/**
4 * Generate a new UUID for the model.
5 */
6public function newUniqueId(): string
7{
8 return (string) Uuid::uuid4();
9}
10 
11/**
12 * Get the columns that should receive a unique identifier.
13 *
14 * @return array<int, string>
15 */
16public function uniqueIds(): array
17{
18 return ['id', 'discount_code'];
19}

如果您愿意,可以选择使用“ULID”而不是 UUID。ULID 与 UUID 类似,但它们的长度只有 26 个字符。与有序 UUID 一样,ULID 也按字典顺序排序,以便于高效的数据库索引。要使用 ULID,您需要Illuminate\Database\Eloquent\Concerns\HasUlids在模型上使用 trait。您还应确保模型具有与ULID 等效的主键列

1use Illuminate\Database\Eloquent\Concerns\HasUlids;
2use Illuminate\Database\Eloquent\Model;
3 
4class Article extends Model
5{
6 use HasUlids;
7 
8 // ...
9}
10 
11$article = Article::create(['title' => 'Traveling to Asia']);
12 
13$article->id; // "01gd4d3tgrrfqeda94gdbtdk5c"

时间戳

默认情况下,Eloquent 期望模型对应的数据库表中存在created_at和列。Eloquent 会在创建或更新模型时自动设置这些列的值。如果您不希望 Eloquent 自动管理这些列,则应在模型上定义一个属性,其值为updated_at$timestampsfalse

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * Indicates if the model should be timestamped.
11 *
12 * @var bool
13 */
14 public $timestamps = false;
15}

如果需要自定义模型时间戳的格式,请$dateFormat在模型上设置该属性。此属性决定了日期属性在数据库中的存储方式,以及模型序列化为数组或 JSON 时的格式:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The storage format of the model's date columns.
11 *
12 * @var string
13 */
14 protected $dateFormat = 'U';
15}

如果您需要自定义用于存储时间戳的列的名称,您可以在模型上定义CREATED_AT和常量:UPDATED_AT

1<?php
2 
3class Flight extends Model
4{
5 const CREATED_AT = 'creation_date';
6 const UPDATED_AT = 'updated_date';
7}

如果您希望在不updated_at修改模型时间戳的情况下执行模型操作,则可以在方法的闭包中对模型进行操作withoutTimestamps

1Model::withoutTimestamps(fn () => $post->increment('reads'));

数据库连接

默认情况下,所有 Eloquent 模型都会使用应用程序配置的默认数据库连接。如果你想指定与特定模型交互时使用的其他连接,则应$connection在模型上定义一个属性:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The database connection that should be used by the model.
11 *
12 * @var string
13 */
14 protected $connection = 'mysql';
15}

默认属性值

默认情况下,新实例化的模型实例不包含任何属性值。如果您想为模型的某些属性定义默认值,可以$attributes在模型上定义一个属性。放置在$attributes数组中的属性值应采用其原始的“可存储”格式,就像刚从数据库中读取一样:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The model's default values for attributes.
11 *
12 * @var array
13 */
14 protected $attributes = [
15 'options' => '[]',
16 'delayed' => false,
17 ];
18}

配置 Eloquent 严格性

Laravel 提供了多种方法,允许您在各种情况下配置 Eloquent 的行为和“严格性”。

首先,该preventLazyLoading方法接受一个可选的布尔参数,用于指示是否应阻止延迟加载。例如,您可能希望仅在非生产环境中禁用延迟加载,这样即使生产代码中意外存在延迟加载关系,生产环境也能继续正常运行。通常,此方法应在boot应用程序的以下方法中调用AppServiceProvider

1use Illuminate\Database\Eloquent\Model;
2 
3/**
4 * Bootstrap any application services.
5 */
6public function boot(): void
7{
8 Model::preventLazyLoading(! $this->app->isProduction());
9}

另外,你还可以通过调用该方法,让 Laravel 在尝试填充无法填充的属性时抛出异常preventSilentlyDiscardingAttributes。这可以帮助防止在本地开发过程中尝试设置尚未添加到模型fillable数组的属性时出现意外错误:

1Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());

检索模型

创建模型及其关联的数据库表后,就可以开始从数据库中检索数据了。您可以将每个 Eloquent 模型视为一个强大的查询构建器,使您可以流畅地查询与模型关联的数据库表。该模型的all方法将从模型关联的数据库表中检索所有记录:

1use App\Models\Flight;
2 
3foreach (Flight::all() as $flight) {
4 echo $flight->name;
5}

构建查询

Eloquentall方法将返回模型表中的所有结果。但是,由于每个 Eloquent 模型都充当查询构建器,因此您可以为查询添加其他约束,然后调用该get方法来检索结果:

1$flights = Flight::where('active', 1)
2 ->orderBy('name')
3 ->take(10)
4 ->get();

由于 Eloquent 模型本身就是查询构建器,因此你应该仔细查看 Laravel查询构建器 提供的所有方法。你可以在编写 Eloquent 查询时使用其中任何一种方法。

刷新模型

如果您已经有一个从数据库检索到的 Eloquent 模型实例,则可以使用freshrefresh方法“刷新”该模型。该fresh方法将重新从数据库检索模型。现有的模型实例不会受到影响:

1$flight = Flight::where('number', 'FR 900')->first();
2 
3$freshFlight = $flight->fresh();

refresh方法将使用数据库中的新数据重新填充现有模型。此外,所有已加载的关系也将被刷新:

1$flight = Flight::where('number', 'FR 900')->first();
2 
3$flight->number = 'FR 456';
4 
5$flight->refresh();
6 
7$flight->number; // "FR 900"

Collections

正如我们所见,Eloquent 方法像all和 一样get从数据库中检索多条记录。但是,这些方法不会返回普通的 PHP 数组,而是Illuminate\Database\Eloquent\Collection返回 的实例。

EloquentCollection类扩展了 Laravel 的基Illuminate\Support\Collection类,它提供了多种与数据集合交互的实用方法。例如,reject可以使用该方法根据调用闭包的结果从集合中移除模型:

1$flights = Flight::where('destination', 'Paris')->get();
2 
3$flights = $flights->reject(function (Flight $flight) {
4 return $flight->cancelled;
5});

除了 Laravel 基集合类提供的方法之外,Eloquent 集合类还提供了一些专门用于与 Eloquent 模型集合交互的额外方法。

由于 Laravel 的所有集合都实现了 PHP 的可迭代接口,因此您可以像循环数组一样循环集合:

1foreach ($flights as $flight) {
2 echo $flight->name;
3}

分块结果

all如果您尝试通过或方法加载数万条 Eloquent 记录,您的应用程序可能会耗尽内存get。与其使用这些方法,chunk不如使用 方法来更高效地处理大量模型。

chunk方法将检索 Eloquent 模型的子集,并将其传递给闭包进行处理。由于每次只检索当前的 Eloquent 模型块,因此chunk在处理大量模型时,该方法将显著减少内存占用:

1use App\Models\Flight;
2use Illuminate\Database\Eloquent\Collection;
3 
4Flight::chunk(200, function (Collection $flights) {
5 foreach ($flights as $flight) {
6 // ...
7 }
8});

传递给该方法的第一个参数chunk是您希望每个“块”接收的记录数。作为第二个参数传递的闭包将针对从数据库检索的每个块调用。将执行数据库查询以检索传递给闭包的每个记录块。

如果您要根据某个列来过滤该方法的结果,chunk并且在迭代结果时需要更新该列,则应该使用该chunkById方法。在这些情况下使用该chunk方法可能会导致意外且不一致的结果。在内部,该chunkById方法将始终检索列id大于前一个块中最后一个模型的模型:

1Flight::where('departed', true)
2 ->chunkById(200, function (Collection $flights) {
3 $flights->each->update(['departed' => false]);
4 }, column: 'id');

由于chunkByIdandlazyById方法将它们自己的“where”条件添加到正在执行的查询中,因此您通常应该在闭包内对您自己的条件进行逻辑分组:

1Flight::where(function ($query) {
2 $query->where('delayed', true)->orWhere('cancelled', true);
3})->chunkById(200, function (Collection $flights) {
4 $flights->each->update([
5 'departed' => false,
6 'cancelled' => true
7 ]);
8}, column: 'id');

使用惰性集合进行分块

方法lazy与 类似,在后台以分块方式执行查询。但是,该方法不是将每个块直接传入回调函数,而是返回一个扁平化的Eloquent 模型的LazyCollection,这样您就可以以单个流的形式与结果进行交互:chunklazy

1use App\Models\Flight;
2 
3foreach (Flight::lazy() as $flight) {
4 // ...
5}

如果您要根据某个列来过滤该方法的结果lazy,并且在迭代结果时需要更新该列,则应该使用 该lazyById方法。在内部,该lazyById方法将始终检索id列值大于前一个块中最后一个模型的模型:

1Flight::where('departed', true)
2 ->lazyById(200, column: 'id')
3 ->each->update(['departed' => false]);

id您可以使用该方法按照降序过滤结果lazyByIdDesc

游标

与方法类似lazy,该cursor方法可用于在迭代数万个 Eloquent 模型记录时显著减少应用程序的内存消耗。

cursor方法只会执行一次数据库查询;然而,各个 Eloquent 模型只有在实际迭代时才会被“hydrated”。因此,在迭代游标时,内存中只会保留一个 Eloquent 模型。

由于该cursor方法每次只能在内存中保存一个 Eloquent 模型,因此无法预先加载关联关系。如果您需要预先加载关联关系,请考虑使用lazy方法

在内部,该cursor方法使用 PHP生成器来实现此功能:

1use App\Models\Flight;
2 
3foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) {
4 // ...
5}

返回cursor一个Illuminate\Support\LazyCollection实例。惰性集合允许你使用典型 Laravel 集合中提供的许多集合方法,同时每次仅将一个模型加载到内存中:

1use App\Models\User;
2 
3$users = User::cursor()->filter(function (User $user) {
4 return $user->id > 500;
5});
6 
7foreach ($users as $user) {
8 echo $user->id;
9}

虽然该cursor方法比常规查询占用的内存少得多(因为每次只在内存中保存一个 Eloquent 模型),但最终仍会耗尽内存。这是因为 PHP 的 PDO 驱动程序会将所有原始查询结果缓存在其缓冲区中。如果您要处理大量 Eloquent 记录,请考虑使用lazy方法

高级子查询

子查询选择

Eloquent 还提供高级子查询支持,允许您在单个查询中从相关表中提取信息。例如,假设我们有一张航班表destinations和一张目的地表flights。该flights表包含一arrived_at列,指示航班何时抵达目的地。

select使用查询生成器和方法可用的子查询功能addSelect,我们可以使用单个查询选择所有destinations最近到达该目的地的航班及其名称:

1use App\Models\Destination;
2use App\Models\Flight;
3 
4return Destination::addSelect(['last_flight' => Flight::select('name')
5 ->whereColumn('destination_id', 'destinations.id')
6 ->orderByDesc('arrived_at')
7 ->limit(1)
8])->get();

子查询排序

此外,查询构建器的orderBy功能还支持子查询。继续使用我们的航班示例,我们可以利用此功能根据最后一班航班抵达目的地的时间对所有目的地进行排序。同样,这可以在执行单个数据库查询时完成:

1return Destination::orderByDesc(
2 Flight::select('arrived_at')
3 ->whereColumn('destination_id', 'destinations.id')
4 ->orderByDesc('arrived_at')
5 ->limit(1)
6)->get();

检索单个模型/聚合

除了检索与给定查询匹配的所有记录之外,您还可以使用findfirstfirstWhere方法检索单个记录。这些方法不返回模型集合,而是返回单个模型实例:

1use App\Models\Flight;
2 
3// Retrieve a model by its primary key...
4$flight = Flight::find(1);
5 
6// Retrieve the first model matching the query constraints...
7$flight = Flight::where('active', 1)->first();
8 
9// Alternative to retrieving the first model matching the query constraints...
10$flight = Flight::firstWhere('active', 1);

有时,如果未找到结果,您可能希望执行其他操作。findOrfirstOr方法将返回单个模型实例,或者,如果未找到结果,则执行给定的闭包。闭包返回的值将被视为该方法的结果:

1$flight = Flight::findOr(1, function () {
2 // ...
3});
4 
5$flight = Flight::where('legs', '>', 3)->firstOr(function () {
6 // ...
7});

未找到异常

有时你可能希望在找不到模型时抛出异常。这在路由或控制器中特别有用。findOrFailfirstOrFail方法将检索查询的第一个结果;但是,如果没有找到结果,Illuminate\Database\Eloquent\ModelNotFoundException则会抛出:

1$flight = Flight::findOrFail(1);
2 
3$flight = Flight::where('legs', '>', 3)->firstOrFail();

如果ModelNotFoundException未捕获,则会自动向客户端发回 404 HTTP 响应:

1use App\Models\Flight;
2 
3Route::get('/api/flights/{id}', function (string $id) {
4 return Flight::findOrFail($id);
5});

检索或创建模型

firstOrCreate方法将尝试使用给定的列/值对来定位数据库记录。如果在数据库中找不到该模型,则将插入一条记录,该记录的属性将由第一个数组参数与可选的第二个数组参数合并而成:

firstOrNew方法与 类似firstOrCreate,会尝试在数据库中查找与给定属性匹配的记录。但是,如果未找到模型,则会返回一个新的模型实例。请注意, 返回的模型firstOrNew尚未持久化到数据库。您需要手动调用save方法来持久化它:

1use App\Models\Flight;
2 
3// Retrieve flight by name or create it if it doesn't exist...
4$flight = Flight::firstOrCreate([
5 'name' => 'London to Paris'
6]);
7 
8// Retrieve flight by name or create it with the name, delayed, and arrival_time attributes...
9$flight = Flight::firstOrCreate(
10 ['name' => 'London to Paris'],
11 ['delayed' => 1, 'arrival_time' => '11:30']
12);
13 
14// Retrieve flight by name or instantiate a new Flight instance...
15$flight = Flight::firstOrNew([
16 'name' => 'London to Paris'
17]);
18 
19// Retrieve flight by name or instantiate with the name, delayed, and arrival_time attributes...
20$flight = Flight::firstOrNew(
21 ['name' => 'Tokyo to Sydney'],
22 ['delayed' => 1, 'arrival_time' => '11:30']
23);

检索聚合

与 Eloquent 模型交互时,你还可以使用Laravel查询构建器提供的countsummax和其他聚合方法。正如你所料,这些方法返回的是标量值,而不是 Eloquent 模型实例:

1$count = Flight::where('active', 1)->count();
2 
3$max = Flight::where('active', 1)->max('price');

插入和更新模型

插入件

当然,使用 Eloquent 时,我们不仅需要从数据库中检索模型,还需要插入新记录。值得庆幸的是,Eloquent 让这一切变得简单。要将新记录插入数据库,只需实例化一个新的模型实例并设置该模型的属性。然后,调用save该模型实例上的方法:

1<?php
2 
3namespace App\Http\Controllers;
4 
5use App\Models\Flight;
6use Illuminate\Http\RedirectResponse;
7use Illuminate\Http\Request;
8 
9class FlightController extends Controller
10{
11 /**
12 * Store a new flight in the database.
13 */
14 public function store(Request $request): RedirectResponse
15 {
16 // Validate the request...
17 
18 $flight = new Flight;
19 
20 $flight->name = $request->name;
21 
22 $flight->save();
23 
24 return redirect('/flights');
25 }
26}

在此示例中,我们将name传入 HTTP 请求中的字段赋值给模型实例name的属性App\Models\Flight。调用该save方法时,一条记录将被插入数据库。调用该方法时,模型的created_at时间戳updated_at将自动设置save,因此无需手动设置。

或者,您可以使用该create方法通过一条 PHP 语句“保存”一个新模型。该方法将返回插入的模型实例create

1use App\Models\Flight;
2 
3$flight = Flight::create([
4 'name' => 'London to Paris',
5]);

但是,在使用该create方法之前,您需要在模型类中指定fillableguarded属性。这些属性是必需的,因为所有 Eloquent 模型都默认受到针对批量赋值漏洞的保护。要了解有关批量赋值的更多信息,请参阅批量赋值文档

更新

save方法也可用于更新数据库中已存在的模型。要更新模型,您需要检索该模型并设置要更新的任何属性。然后,调用该模型的save方法。同样,updated_at时间戳将自动更新,因此无需手动设置其值:

1use App\Models\Flight;
2 
3$flight = Flight::find(1);
4 
5$flight->name = 'Paris to London';
6 
7$flight->save();

有时,您可能需要更新现有模型,或者在没有匹配模型的情况下创建新模型。与firstOrCreate方法一样,该updateOrCreate方法会持久化模型,因此无需手动调用该save方法。

在下面的示例中,如果存在一个航班,其位置departure列将被更新。如果不存在这样的航班,则会创建一个新航班,其属性将由第一个参数数组与第二个参数数组合并而成:OaklanddestinationSan Diegopricediscounted

1$flight = Flight::updateOrCreate(
2 ['departure' => 'Oakland', 'destination' => 'San Diego'],
3 ['price' => 99, 'discounted' => 1]
4);

批量更新

还可以针对与给定查询匹配的模型执行更新。在此示例中,所有 且activedestination航班都San Diego将被标记为延误:

1Flight::where('active', 1)
2 ->where('destination', 'San Diego')
3 ->update(['delayed' => 1]);

update方法需要一个列和值对的数组,表示需要更新的列。该update方法返回受影响的行数。

通过 Eloquent 执行批量更新时,已更新的模型不会触发savingsavedupdating和model 事件。这是因为批量更新时并不会真正检索模型。updated

检查属性变化

Eloquent 提供了isDirtyisCleanwasChanged方法来检查模型的内部状态,并确定其属性与最初检索模型时相比发生了怎样的变化。

isDirty方法用于判断自检索模型以来,模型的任何属性是否已更改。您可以向该isDirty方法传递特定的属性名称或属性数组,以判断是否有任何属性“脏”。该isClean方法将判断自检索模型以来,某个属性是否保持不变。此方法还接受一个可选的属性参数:

1use App\Models\User;
2 
3$user = User::create([
4 'first_name' => 'Taylor',
5 'last_name' => 'Otwell',
6 'title' => 'Developer',
7]);
8 
9$user->title = 'Painter';
10 
11$user->isDirty(); // true
12$user->isDirty('title'); // true
13$user->isDirty('first_name'); // false
14$user->isDirty(['first_name', 'title']); // true
15 
16$user->isClean(); // false
17$user->isClean('title'); // false
18$user->isClean('first_name'); // true
19$user->isClean(['first_name', 'title']); // false
20 
21$user->save();
22 
23$user->isDirty(); // false
24$user->isClean(); // true

wasChanged方法用于判断当前请求周期内模型上次保存时是否有任何属性被更改。如有需要,您可以传递属性名称来查看特定属性是否被更改:

1$user = User::create([
2 'first_name' => 'Taylor',
3 'last_name' => 'Otwell',
4 'title' => 'Developer',
5]);
6 
7$user->title = 'Painter';
8 
9$user->save();
10 
11$user->wasChanged(); // true
12$user->wasChanged('title'); // true
13$user->wasChanged(['title', 'slug']); // true
14$user->wasChanged('first_name'); // false
15$user->wasChanged(['first_name', 'title']); // true

getOriginal方法返回一个包含模型原始属性的数组,无论模型自检索以来发生了哪些更改。如果需要,您可以传递特定的属性名称来获取特定属性的原始值:

1$user = User::find(1);
2 
3$user->name; // John
4$user->email; // john@example.com
5 
6$user->name = 'Jack';
7$user->name; // Jack
8 
9$user->getOriginal('name'); // John
10$user->getOriginal(); // Array of original attributes...

getChanges方法返回一个数组,其中包含模型上次保存时发生更改的属性,而该getPrevious方法返回一个数组,其中包含模型上次保存之前的原始属性值:

1$user = User::find(1);
2 
3$user->name; // John
4$user->email; // john@example.com
5 
6$user->update([
7 'name' => 'Jack',
8 'email' => 'jack@example.com',
9]);
10 
11$user->getChanges();
12 
13/*
14 [
15 'name' => 'Jack',
16 'email' => 'jack@example.com',
17 ]
18*/
19 
20$user->getPrevious();
21 
22/*
23 [
24 'name' => 'John',
25 'email' => 'john@example.com',
26 ]
27*/

批量赋值

您可以使用该create方法通过一条 PHP 语句“保存”一个新模型。该方法将返回插入的模型实例:

1use App\Models\Flight;
2 
3$flight = Flight::create([
4 'name' => 'London to Paris',
5]);

但是,在使用该create方法之前,您需要在模型类上指定fillableguarded属性。这些属性是必需的,因为所有 Eloquent 模型都默认受到针对批量赋值漏洞的保护。

当用户传递意外的 HTTP 请求字段,而该字段更改了数据库中您未预料到的列时,就会发生批量分配漏洞。例如,恶意用户可能会is_admin通过 HTTP 请求发送参数,该参数随后会传递给模型的create方法,从而允许用户将自身权限上报给管理员。

首先,你需要定义哪些模型属性可以批量赋值。你可以使用$fillable模型上的属性来实现。例如,让我们将模型name的属性设置Flight为批量赋值:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The attributes that are mass assignable.
11 *
12 * @var array<int, string>
13 */
14 protected $fillable = ['name'];
15}

一旦指定了哪些属性可以批量赋值,就可以使用该create方法在数据库中插入一条新记录。该create方法返回新创建的模型实例:

1$flight = Flight::create(['name' => 'London to Paris']);

如果您已经有模型实例,则可以使用该fill方法用属性数组填充它:

1$flight->fill(['name' => 'Amsterdam to Frankfurt']);

批量赋值和 JSON 列

在分配 JSON 列时,必须在模型$fillable数组中指定每列的可批量分配键。出于安全考虑,Laravel 不支持在使​​用以下guarded属性时更新嵌套的 JSON 属性:

1/**
2 * The attributes that are mass assignable.
3 *
4 * @var array<int, string>
5 */
6protected $fillable = [
7 'options->enabled',
8];

允许批量分配

如果您想让所有属性都可批量赋值,可以将模型的$guarded属性定义为空数组。如果您选择取消保护模型,则应特别注意始终手动创建传递给 Eloquent 的fillcreateupdate方法的数组:

1/**
2 * The attributes that aren't mass assignable.
3 *
4 * @var array<string>|bool
5 */
6protected $guarded = [];

批量赋值异常

默认情况下,执行批量赋值操作时,未包含在$fillable数组中的属性会被静默丢弃。在生产环境中,这是预期行为;然而,在本地开发过程中,这可能会导致混淆,导致模型更改无法生效。

如果你愿意,可以通过调用该方法,让 Laravel 在尝试填充不可填充的属性时抛出异常preventSilentlyDiscardingAttributes。通常,此方法应该在boot应用程序AppServiceProvider类的方法中调用:

1use Illuminate\Database\Eloquent\Model;
2 
3/**
4 * Bootstrap any application services.
5 */
6public function boot(): void
7{
8 Model::preventSilentlyDiscardingAttributes($this->app->isLocal());
9}

更新插入

Eloquent 的upsert方法可用于在单个原子操作中更新或创建记录。该方法的第一个参数包含要插入或更新的值,第二个参数列出关联表中唯一标识记录的列。该方法的第三个也是最后一个参数是一个数组,其中包含当数据库中已存在匹配记录时应更新的列。如果模型上启用了时间戳,该upsert方法将自动设置created_at和时间戳:updated_at

1Flight::upsert([
2 ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
3 ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
4], uniqueBy: ['departure', 'destination'], update: ['price']);

除 SQL Server 外,所有数据库都要求该方法第二个参数中的列upsert具有“主”或“唯一”索引。此外,MariaDB 和 MySQL 数据库驱动程序会忽略该upsert方法的第二个参数,并始终使用表的“主”和“唯一”索引来检测现有记录。

删除模型

要删除模型,您可以调用delete模型实例上的方法:

1use App\Models\Flight;
2 
3$flight = Flight::find(1);
4 
5$flight->delete();

通过主键删除现有模型

在上面的示例中,我们在调用该方法之前从数据库中检索了模型delete。但是,如果您知道模型的主键,则可以调用该方法删除模型,而无需显式检索它destroy。除了接受单个主键外,该destroy方法还接受多个主键、主键数组或主键集合:

1Flight::destroy(1);
2 
3Flight::destroy(1, 2, 3);
4 
5Flight::destroy([1, 2, 3]);
6 
7Flight::destroy(collect([1, 2, 3]));

如果您使用软删除模型,则可以通过以下步骤永久删除模型forceDestroy

1Flight::forceDestroy(1);

destroy方法单独加载每个模型并调用该delete方法,以便为每个模型正确调度deleting和事件。deleted

使用查询删除模型

当然,您可以构建一个 Eloquent 查询来删除所有符合查询条件的模型。在本例中,我们将删除所有标记为“非Events”的航班。与批量更新类似,批量删除不会为已删除的模型派发模型事件:

1$deleted = Flight::where('active', 0)->delete();

要删除表中的所有模型,您应该执行查询而不添加任何条件:

1$deleted = Flight::query()->delete();

当通过 Eloquent 执行批量删除语句时,已删除的模型不会触发deleting和model 事件。这是因为执行删除语句时,模型实际上并不会被检索。deleted

软删除

除了从数据库中实际删除记录之外,Eloquent 还可以“软删除”模型。当模型被软删除时,它们实际上并没有从数据库中删除。而是deleted_at在模型上设置一个属性,指示模型被“删除”的日期和时间。要为模型启用软删除,请Illuminate\Database\Eloquent\SoftDeletes向模型添加以下特征:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\SoftDeletes;
7 
8class Flight extends Model
9{
10 use SoftDeletes;
11}

SoftDeletes特征将自动为您把deleted_at属性转换为DateTime/实例。Carbon

您还应该将该deleted_at列添加到数据库表中。Laravel架构构建器包含一个辅助方法来创建此列:

1use Illuminate\Database\Schema\Blueprint;
2use Illuminate\Support\Facades\Schema;
3 
4Schema::table('flights', function (Blueprint $table) {
5 $table->softDeletes();
6});
7 
8Schema::table('flights', function (Blueprint $table) {
9 $table->dropSoftDeletes();
10});

现在,当您delete在模型上调用该方法时,该deleted_at列将被设置为当前日期和时间。但是,该模型的数据库记录将保留在表中。当查询使用软删除的模型时,软删除的模型将自动从所有查询结果中排除。

要确定给定的模型实例是否已被软删除,您可以使用该trashed方法:

1if ($flight->trashed()) {
2 // ...
3}

恢复软删除的模型

有时你可能希望“恢复”已软删除的模型。要恢复软删除的模型,你可以restore在模型实例上调用该方法。该restore方法会将模型的deleted_at列设置为null

1$flight->restore();

您还可以restore在查询中使用该方法来恢复多个模型。同样,与其他“批量”操作一样,这不会为恢复的模型调度任何模型事件:

1Flight::withTrashed()
2 ->where('airline_id', 1)
3 ->restore();

该方法也可以在建立关系restore查询时使用

1$flight->history()->restore();

永久删除模型

有时你可能需要真正地从数据库中删除一个模型。你可以使用如下forceDelete方法从数据库表中永久删除一个被软删除的模型:

1$flight->forceDelete();

您还可以forceDelete在构建 Eloquent 关系查询时使用该方法:

1$flight->history()->forceDelete();

查询软删除模型

包括软删除模型

如上所述,软删除的模型将自动从查询结果中排除。但是,您可以通过withTrashed在查询中调用以下方法强制将软删除的模型包含在查询结果中:

1use App\Models\Flight;
2 
3$flights = Flight::withTrashed()
4 ->where('account_id', 1)
5 ->get();

在建立关系withTrashed查询时也可以调用该方法

1$flight->history()->withTrashed()->get();

仅检索软删除的模型

该方法将onlyTrashed检索软删除的模型:

1$flights = Flight::onlyTrashed()
2 ->where('airline_id', 1)
3 ->get();

剪枝模型

有时您可能希望定期删除不再需要的模型。为此,您可以向要定期修剪的模型添加Illuminate\Database\Eloquent\Prunable或特征。将其中一个特征添加到模型后,实现一个方法,该方法返回一个 Eloquent 查询构建器,用于解析不再需要的模型:Illuminate\Database\Eloquent\MassPrunableprunable

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7use Illuminate\Database\Eloquent\Prunable;
8 
9class Flight extends Model
10{
11 use Prunable;
12 
13 /**
14 * Get the prunable model query.
15 */
16 public function prunable(): Builder
17 {
18 return static::where('created_at', '<=', now()->subMonth());
19 }
20}

当将模型标记为 时Prunable,您还可以pruning在模型上定义一个方法。此方法将在模型被删除之前调用。此方法可用于在模型从数据库中永久删除之前删除与模型关联的任何其他资源(例如存储的文件):

1/**
2 * Prepare the model for pruning.
3 */
4protected function pruning(): void
5{
6 // ...
7}

配置可修剪模型后,您应该model:prune在应用程序routes/console.php文件中安排 Artisan 命令的运行时间。您可以自由选择此命令的适当运行间隔:

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::command('model:prune')->daily();

在后台,该model:prune命令会自动检测应用程序app/Models目录中的“可修剪”模型。如果您的模型位于其他位置,您可以使用以下--model选项指定模型类名:

1Schedule::command('model:prune', [
2 '--model' => [Address::class, Flight::class],
3])->daily();

如果您希望在修剪所有其他检测到的模型的同时排除某些模型被修剪,则可以使用以下--except选项:

1Schedule::command('model:prune', [
2 '--except' => [Address::class, Flight::class],
3])->daily();

您可以prunable通过执行model:prune带有--pretend选项的命令来测试查询。在模拟执行时,该model:prune命令将仅报告如果命令实际运行,将删除多少条记录:

1php artisan model:prune --pretend

forceDelete如果软删除模型与可修剪查询匹配, 则将被永久删除( )。

批量修剪

当模型被标记为该Illuminate\Database\Eloquent\MassPrunable特征时,系统会使用批量删除查询从数据库中删除模型。因此,该pruning方法不会被调用,并且deletingdeletedmodel 事件也不会被调度。这是因为在删除之前模型实际上不会被检索,从而使修剪过程更加高效:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7use Illuminate\Database\Eloquent\MassPrunable;
8 
9class Flight extends Model
10{
11 use MassPrunable;
12 
13 /**
14 * Get the prunable model query.
15 */
16 public function prunable(): Builder
17 {
18 return static::where('created_at', '<=', now()->subMonth());
19 }
20}

复制模型

你可以使用 该方法创建现有模型实例的未保存副本replicate。当你的模型实例共享许多相同属性时,此方法特别有用:

1use App\Models\Address;
2 
3$shipping = Address::create([
4 'type' => 'shipping',
5 'line_1' => '123 Example Street',
6 'city' => 'Victorville',
7 'state' => 'CA',
8 'postcode' => '90001',
9]);
10 
11$billing = $shipping->replicate()->fill([
12 'type' => 'billing'
13]);
14 
15$billing->save();

要排除将一个或多个属性复制到新模型,您可以将数组传递给该replicate方法:

1$flight = Flight::create([
2 'destination' => 'LAX',
3 'origin' => 'LHR',
4 'last_flown' => '2020-03-04 11:00:00',
5 'last_pilot_id' => 747,
6]);
7 
8$flight = $flight->replicate([
9 'last_flown',
10 'last_pilot_id'
11]);

查询范围

全局作用域

全局作用域允许您为给定模型的所有查询添加约束。Laravel 自身的软删除功能利用全局作用域仅从数据库中检索“未删除”的模型。编写您自己的全局作用域可以提供一种便捷、简单的方法,确保给定模型的每个查询都受到某些约束。

生成范围

要生成新的全局范围,您可以调用make:scopeArtisan 命令,该命令会将生成的范围放置在应用程序的app/Models/Scopes目录中:

1php artisan make:scope AncientScope

编写全局作用域

编写全局作用域很简单。首先,使用make:scope命令生成一个实现该Illuminate\Database\Eloquent\Scope接口的类。该Scope接口要求你实现一个方法:apply。该方法可以根据需要向查询apply添加约束或其他类型的子句:where

1<?php
2 
3namespace App\Models\Scopes;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7use Illuminate\Database\Eloquent\Scope;
8 
9class AncientScope implements Scope
10{
11 /**
12 * Apply the scope to a given Eloquent query builder.
13 */
14 public function apply(Builder $builder, Model $model): void
15 {
16 $builder->where('created_at', '<', now()->subYears(2000));
17 }
18}

如果您的全局范围是向查询的 select 子句添加列,则应使用addSelect方法而不是select。这将防止无意中替换查询的现有 select 子句。

应用全局范围

要为模型分配全局范围,您可以简单地将ScopedBy属性放在模型上:

1<?php
2 
3namespace App\Models;
4 
5use App\Models\Scopes\AncientScope;
6use Illuminate\Database\Eloquent\Attributes\ScopedBy;
7 
8#[ScopedBy([AncientScope::class])]
9class User extends Model
10{
11 //
12}

或者,您可以通过重写模型的方法来手动注册全局作用域booted,并调用该addGlobalScope方法。该addGlobalScope方法接受作用域的实例作为其唯一参数:

1<?php
2 
3namespace App\Models;
4 
5use App\Models\Scopes\AncientScope;
6use Illuminate\Database\Eloquent\Model;
7 
8class User extends Model
9{
10 /**
11 * The "booted" method of the model.
12 */
13 protected static function booted(): void
14 {
15 static::addGlobalScope(new AncientScope);
16 }
17}

将上面示例中的范围添加到App\Models\User模型后,调用该User::all()方法将执行以下 SQL 查询:

1select * from `users` where `created_at` < 0021-02-18 00:00:00

匿名全局作用域

Eloquent 还允许使用闭包定义全局作用域,这对于不需要单独创建类的简单作用域尤其有用。使用闭包定义全局作用域时,应该提供一个自己选择的作用域名称作为该addGlobalScope方法的第一个参数:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7 
8class User extends Model
9{
10 /**
11 * The "booted" method of the model.
12 */
13 protected static function booted(): void
14 {
15 static::addGlobalScope('ancient', function (Builder $builder) {
16 $builder->where('created_at', '<', now()->subYears(2000));
17 });
18 }
19}

删除全局范围

如果要删除给定查询的全局作用域,可以使用该withoutGlobalScope方法。此方法接受全局作用域的类名作为其唯一参数:

1User::withoutGlobalScope(AncientScope::class)->get();

或者,如果您使用闭包定义了全局范围,则应该传递分配给全局范围的字符串名称:

1User::withoutGlobalScope('ancient')->get();

如果您想要删除查询的几个甚至所有全局范围,您可以使用该withoutGlobalScopes方法:

1// Remove all of the global scopes...
2User::withoutGlobalScopes()->get();
3 
4// Remove some of the global scopes...
5User::withoutGlobalScopes([
6 FirstScope::class, SecondScope::class
7])->get();

本地作用域

本地作用域允许您定义一组通用的查询约束,以便在整个应用程序中轻松复用。例如,您可能需要频繁检索所有被视为“热门”的用户。要定义作用域,请将Scope属性添加到 Eloquent 方法中。

范围应该始终返回相同的查询生成器实例或void

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Attributes\Scope;
6use Illuminate\Database\Eloquent\Builder;
7use Illuminate\Database\Eloquent\Model;
8 
9class User extends Model
10{
11 /**
12 * Scope a query to only include popular users.
13 */
14 #[Scope]
15 protected function popular(Builder $query): void
16 {
17 $query->where('votes', '>', 100);
18 }
19 
20 /**
21 * Scope a query to only include active users.
22 */
23 #[Scope]
24 protected function active(Builder $query): void
25 {
26 $query->where('active', 1);
27 }
28}

利用本地作用域

定义作用域后,您可以在查询模型时调用作用域方法。您甚至可以链接调用各个作用域:

1use App\Models\User;
2 
3$users = User::popular()->active()->orderBy('created_at')->get();

通过查询运算符组合多个 Eloquent 模型范围or可能需要使用闭包来实现正确的逻辑分组

1$users = User::popular()->orWhere(function (Builder $query) {
2 $query->active();
3})->get();

然而,由于这可能很麻烦,Laravel 提供了一种“高阶”orWhere方法,允许您流畅地将范围链接在一起,而无需使用闭包:

1$users = User::popular()->orWhere->active()->get();

动态作用域

有时您可能希望定义一个接受参数的作用域。首先,只需将附加参数添加到作用域方法的签名中即可。作用域参数应在$query参数之后定义:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Attributes\Scope;
6use Illuminate\Database\Eloquent\Builder;
7use Illuminate\Database\Eloquent\Model;
8 
9class User extends Model
10{
11 /**
12 * Scope a query to only include users of a given type.
13 */
14 #[Scope]
15 protected function ofType(Builder $query, string $type): void
16 {
17 $query->where('type', $type);
18 }
19}

一旦将预期参数添加到范围方法的签名中,就可以在调用范围时传递参数:

1$users = User::ofType('admin')->get();

待定属性

如果您希望使用范围来创建具有与用于约束范围的属性相同的属性的模型,则可以withAttributes在构建范围查询时使用该方法:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Attributes\Scope;
6use Illuminate\Database\Eloquent\Builder;
7use Illuminate\Database\Eloquent\Model;
8 
9class Post extends Model
10{
11 /**
12 * Scope the query to only include drafts.
13 */
14 #[Scope]
15 protected function draft(Builder $query): void
16 {
17 $query->withAttributes([
18 'hidden' => true,
19 ]);
20 }
21}

withAttributes方法将where使用给定的属性向查询添加条件,并且还将给定的属性添加到通过范围创建的任何模型:

1$draft = Post::draft()->create(['title' => 'In Progress']);
2 
3$draft->hidden; // true

要指示withAttributes方法不where向查询添加条件,您可以将asConditions参数设置为false

1$query->withAttributes([
2 'hidden' => true,
3], asConditions: false);

比较模型

有时你可能需要判断两个模型是否“相同”。isisNot方法可以用来快速验证两个模型是否具有相同的主键、表和数据库连接:

1if ($post->is($anotherPost)) {
2 // ...
3}
4 
5if ($post->isNot($anotherPost)) {
6 // ...
7}

使用、关系is时, 和方法isNot也可用。当您想比较相关模型而不发出查询来检索该模型时,此方法特别有用:belongsTohasOnemorphTomorphOne

1if ($post->author()->is($user)) {
2 // ...
3}

Events

想要将 Eloquent 事件直接广播到客户端应用程序吗?查看 Laravel 的模型事件广播

Eloquent模型会分派多个事件,使您可以参与模型生命周期以下时刻retrieved,,,,,,,,,,,,,,,,creatingcreatedupdatingupdatedsavingsaveddeletingdeletedtrashedforceDeletingforceDeletedrestoringrestoredreplicating

当从数据库中检索现有模型时,将调度该retrieved事件。首次保存新模型时,将调度creatingcreated事件。当修改现有模型并调用 方法时,将调度updating/事件。当创建或更新模型时,将调度/事件 - 即使模型的属性未发生更改。以 结尾的事件名称在模型更改持久化之前调度,而以 结尾的事件在模型更改持久化之后调度。updatedsavesavingsaved-ing-ed

要开始监听模型事件,请$dispatchesEvents在 Eloquent 模型上定义一个属性。此属性将 Eloquent 模型生命周期的各个点映射到您自己的事件类。每个模型事件类都应该通过其构造函数接收受影响模型的实例:

1<?php
2 
3namespace App\Models;
4 
5use App\Events\UserDeleted;
6use App\Events\UserSaved;
7use Illuminate\Foundation\Auth\User as Authenticatable;
8use Illuminate\Notifications\Notifiable;
9 
10class User extends Authenticatable
11{
12 use Notifiable;
13 
14 /**
15 * The event map for the model.
16 *
17 * @var array<string, string>
18 */
19 protected $dispatchesEvents = [
20 'saved' => UserSaved::class,
21 'deleted' => UserDeleted::class,
22 ];
23}

定义和映射 Eloquent 事件后,您可以使用事件监听器来处理事件。

通过 Eloquent 发出批量更新或删除查询时,受影响的模型将不会调度savedupdateddeleting和model 事件。这是因为执行批量更新或删除操作时,模型实际上不会被检索。deleted

使用闭包

除了使用自定义事件类,你也可以注册闭包,这些闭包会在各种模型事件被调度时执行。通常,你应该在booted模型的方法中注册这些闭包:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class User extends Model
8{
9 /**
10 * The "booted" method of the model.
11 */
12 protected static function booted(): void
13 {
14 static::created(function (User $user) {
15 // ...
16 });
17 }
18}

如果需要,你可以在注册模型事件时使用可排队的匿名事件监听器。这将指示 Laravel 使用应用程序的队列在后台执行模型事件监听器

1use function Illuminate\Events\queueable;
2 
3static::created(queueable(function (User $user) {
4 // ...
5}));

观察员

定义观察者

如果您要监听给定模型上的多个事件,可以使用观察者 (observers) 将所有监听器分组到一个类中。观察者类的方法名称反映了您希望监听的 Eloquent 事件。每个方法都接收受影响的模型作为其唯一参数。Artisanmake:observer命令是创建新观察者类最简单的方法:

1php artisan make:observer UserObserver --model=User

此命令会将新的观察器放置到你的app/Observers目录中。如果此目录不存在,Artisan 会为你创建。新的观察器将如下所示:

1<?php
2 
3namespace App\Observers;
4 
5use App\Models\User;
6 
7class UserObserver
8{
9 /**
10 * Handle the User "created" event.
11 */
12 public function created(User $user): void
13 {
14 // ...
15 }
16 
17 /**
18 * Handle the User "updated" event.
19 */
20 public function updated(User $user): void
21 {
22 // ...
23 }
24 
25 /**
26 * Handle the User "deleted" event.
27 */
28 public function deleted(User $user): void
29 {
30 // ...
31 }
32 
33 /**
34 * Handle the User "restored" event.
35 */
36 public function restored(User $user): void
37 {
38 // ...
39 }
40 
41 /**
42 * Handle the User "forceDeleted" event.
43 */
44 public function forceDeleted(User $user): void
45 {
46 // ...
47 }
48}

要注册观察者,您可以将ObservedBy属性放在相应的模型上:

1use App\Observers\UserObserver;
2use Illuminate\Database\Eloquent\Attributes\ObservedBy;
3 
4#[ObservedBy([UserObserver::class])]
5class User extends Authenticatable
6{
7 //
8}

observe或者,您可以通过调用要观察的模型上的方法来手动注册观察者。您可以在boot应用程序AppServiceProvider类的方法中注册观察者:

1use App\Models\User;
2use App\Observers\UserObserver;
3 
4/**
5 * Bootstrap any application services.
6 */
7public function boot(): void
8{
9 User::observe(UserObserver::class);
10}

观察者还可以监听其他事件,例如savingretrieved。这些事件在事件文档中有详细描述。

观察者和数据库事务

在数据库事务中创建模型时,你可能希望指示观察者仅在数据库事务提交后执行其事件处理程序。你可以通过实现ShouldHandleEventsAfterCommit观察者的接口来实现这一点。如果数据库事务未在进行中,事件处理程序将立即执行:

1<?php
2 
3namespace App\Observers;
4 
5use App\Models\User;
6use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;
7 
8class UserObserver implements ShouldHandleEventsAfterCommit
9{
10 /**
11 * Handle the User "created" event.
12 */
13 public function created(User $user): void
14 {
15 // ...
16 }
17}

静音事件

您可能偶尔需要暂时“静音”模型触发的所有事件。您可以使用该withoutEvents方法来实现。该withoutEvents方法接受一个闭包作为其唯一参数。在此闭包中执行的任何代码都不会调度模型事件,并且闭包返回的任何值都将由该withoutEvents方法返回:

1use App\Models\User;
2 
3$user = User::withoutEvents(function () {
4 User::findOrFail(1)->delete();
5 
6 return User::find(2);
7});

保存不含事件的单个模型

有时你可能希望“保存”给定的模型而不发送任何事件。你可以使用以下saveQuietly方法实现:

1$user = User::findOrFail(1);
2 
3$user->name = 'Victoria Faith';
4 
5$user->saveQuietly();

您还可以“更新”、“删除”、“软删除”、“恢复”和“复制”给定模型而不分派任何事件:

1$user->deleteQuietly();
2$user->forceDeleteQuietly();
3$user->restoreQuietly();