Skip to content
全部文档

Eloquent:入门

简介

Laravel包括Eloquent,它是一个对象关系映射器 (ORM),可以让你轻松地与数据库进行交互。使用Eloquent时,每个数据库表都有一个相应的「模型」,用于与该表交互。除了从数据库表中检索记录之外,Eloquent模型还允许你从表中插入、更新和删除记录。

INFO

开始之前,请确保在应用程序的config/database.php配置文件中配置数据库连接。有关配置数据库的更多信息,请查看the database configuration documentation

生成模型类

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

shell
php artisan make:model Flight

如果你想在生成模型时生成database migration,可以使用--migration-m选项:

shell
php artisan make:model Flight --migration

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

shell
# Generate a model and a FlightFactory class...
php artisan make:model Flight --factory
php artisan make:model Flight -f

# Generate a model and a FlightSeeder class...
php artisan make:model Flight --seed
php artisan make:model Flight -s

# Generate a model and a FlightController class...
php artisan make:model Flight --controller
php artisan make:model Flight -c

# Generate a model, FlightController resource class, and form request classes...
php artisan make:model Flight --controller --resource --requests
php artisan make:model Flight -crR

# Generate a model and a FlightPolicy class...
php artisan make:model Flight --policy

# Generate a model and a migration, factory, seeder, and controller...
php artisan make:model Flight -mfsc

# Shortcut to generate a model, migration, factory, seeder, policy, controller, and form requests...
php artisan make:model Flight --all
php artisan make:model Flight -a

# Generate a pivot model...
php artisan make:model Member --pivot
php artisan make:model Member -p

检查模型

有时,仅通过浏览代码很难确定模型的所有可用属性和关系。相反,请尝试model:showArtisan 命令,它提供了所有模型属性和关系的便捷概述:

shell
php artisan model:show Flight

Eloquent 模型约定

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

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    // ...
}

表名

浏览完上面的示例后,你可能已经注意到,我们没有告诉Eloquent哪个数据库表对应于我们的Flight模型。按照约定,除非显式指定另一个名称,否则类的「蛇形」复数名称将用作表名称。因此,在这种情况下,Eloquent将假定Flight模型将记录存储在flights表中,而AirTrafficController模型将假设air_traffic_controllers表中存储记录。

如果你的模型对应的数据库表不符合此约定,你可以使用Table属性手动指定模型的表名称:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Model;

#[Table('my_flights')]
class Flight extends Model
{
    // ...
}

主键

Eloquent还将假设每个模型对应的数据库表都有一个名为id的主键列。如有必要,你可以使用Table属性上的key参数指定另一个列作为模型的主键:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Model;

#[Table(key: 'flight_id')]
class Flight extends Model
{
    // ...
}

此外,Eloquent假定主键是递增的整数值,这意味着Eloquent会自动将主键强制转换为整数。如果你希望使用非递增或非数字主键,则应在Table属性上指定keyTypeincrementing参数:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Model;

#[Table(key: 'uuid', keyType: 'string', incrementing: false)]
class Flight extends Model
{
    // ...
}

如果你只需要禁用自动递增ID,你可以使用WithoutIncrementing属性:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\WithoutIncrementing;
use Illuminate\Database\Eloquent\Model;

#[WithoutIncrementing]
class Flight extends Model
{
    // ...
}

「复合」主键

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

UUID 与 ULID 键

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

如果你希望模型使用 UUID 键而不是自动递增整数键,你可以在模型上使用Illuminate\Database\Eloquent\Concerns\HasUuids特征。当然,你应该确保模型具有UUID equivalent primary key column

php
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    use HasUuids;

    // ...
}

$article = Article::create(['title' => 'Traveling to Europe']);

$article->id; // "018f2b5c-6a7f-7b12-9d6f-2f8a4e0c9c11"

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

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

php
use Ramsey\Uuid\Uuid;

/**
 * Generate a new UUID for the model.
 */
public function newUniqueId(): string
{
    return (string) Uuid::uuid4();
}

/**
 * Get the columns that should receive a unique identifier.
 *
 * @return array<int, string>
 */
public function uniqueIds(): array
{
    return ['id', 'discount_code'];
}

如果你愿意,你可以选择使用「ULID」而不是 UUID。 ULID 与 UUID 类似;然而,它们的长度只有 26 个字符。与有序 UUID 一样,ULID 可以按字典顺序排序,以实现高效的数据库索引。要利用 ULID,你应该在模型上使用Illuminate\Database\Eloquent\Concerns\HasUlids特征。你还应该确保模型具有ULID equivalent primary key column

php
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    use HasUlids;

    // ...
}

$article = Article::create(['title' => 'Traveling to Asia']);

$article->id; // "01gd4d3tgrrfqeda94gdbtdk5c"

时间戳

默认情况下,Eloquent期望created_atupdated_at列存在于模型的相应数据库表中。 创建或更新模型时,Eloquent将自动设置这些列的值。如果你不希望这些列由Eloquent自动管理,你可以在模型的Table属性上将timestamps设置为false

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Model;

#[Table(timestamps: false)]
class Flight extends Model
{
    // ...
}

如果你只需要禁用时间戳,你可以使用WithoutTimestamps属性:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\WithoutTimestamps;
use Illuminate\Database\Eloquent\Model;

#[WithoutTimestamps]
class Flight extends Model
{
    // ...
}

如果你需要自定义模型时间戳的格式,你可以在Table属性上使用dateFormat参数。这决定了当模型序列化为数组或 JSON 时日期属性如何存储在数据库中以及它们的格式:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Model;

#[Table(dateFormat: 'U')]
class Flight extends Model
{
    // ...
}

如果你只需要定义日期格式,你可以使用DateFormat属性:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\DateFormat;
use Illuminate\Database\Eloquent\Model;

#[DateFormat('U')]
class Flight extends Model
{
    // ...
}

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

php
<?php

class Flight extends Model
{
    /**
     * The name of the "created at" column.
     *
     * @var string|null
     */
    public const CREATED_AT = 'creation_date';

    /**
     * The name of the "updated at" column.
     *
     * @var string|null
     */
    public const UPDATED_AT = 'updated_date';
}

如果你想在模型的updated_at时间戳不被修改的情况下执行模型操作,你可以在给定withoutTimestamps方法的闭包内对模型进行操作:

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

数据库连接

默认情况下,所有Eloquent模型都将使用为你的应用程序配置的默认数据库连接。如果你想指定与特定模型交互时应使用的不同连接,你可以使用Connection属性:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Connection;
use Illuminate\Database\Eloquent\Model;

#[Connection('mysql')]
class Flight extends Model
{
    // ...
}

默认属性值

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

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * The model's default values for attributes.
     *
     * @var array<string, mixed>
     */
    protected $attributes = [
        'options' => '[]',
        'delayed' => false,
    ];
}

配置 Eloquent 严格模式

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

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

php
use Illuminate\Database\Eloquent\Model;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Model::preventLazyLoading(! $this->app->isProduction());
}

此外,你可以指示Laravel在尝试通过调用preventSilentlyDiscardingAttributes方法填充不可填充的属性时引发异常。当尝试设置尚未添加到模型的fillable数组的属性时,这可以帮助防止本地开发期间出现意外错误:

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

检索模型

创建模型和its associated database table后,你就可以开始从数据库检索数据了。你可以将每个Eloquent模型视为功能强大的query builder,允许你流畅地查询与模型关联的数据库表。模型的all方法将从模型的关联数据库表中检索所有记录:

php
use App\Models\Flight;

foreach (Flight::all() as $flight) {
    echo $flight->name;
}

构建查询

Eloquentall方法将返回模型表中的所有结果。但是,由于每个Eloquent模型都充当query builder,因此你可以向查询添加额外的约束,然后调用get方法来检索结果:

php
$flights = Flight::where('active', 1)
    ->orderBy('name')
    ->limit(10)
    ->get();

INFO

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

刷新模型

如果你已经拥有从数据库检索的Eloquent模型的实例,则可以使用freshrefresh方法「刷新」模型。fresh方法将从数据库中重新检索模型。现有模型实例不会受到影响:

php
$flight = Flight::where('number', 'FR 900')->first();

$freshFlight = $flight->fresh();

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

php
$flight = Flight::where('number', 'FR 900')->first();

$flight->number = 'FR 456';

$flight->refresh();

$flight->number; // "FR 900"

如果你需要刷新模型并在事务内获取悲观锁,你可以使用refreshForUpdate方法。此方法使用FOR UPDATE锁重新加载模型:

php
DB::transaction(function () use ($flight) {
    $flight->refreshForUpdate();

    // Update the locked model...
});

集合

正如我们所看到的,Eloquent方法(如allget)从数据库中检索多条记录。但是,这些方法不会返回纯 PHP 数组。相反,返回Illuminate\Database\Eloquent\Collection的实例。

EloquentCollection类扩展了Laravel的基Illuminate\Support\Collection类,它提供了variety of helpful methods用于与数据集合交互。例如,reject方法可用于根据调用闭包的结果从集合中删除模型:

php
$flights = Flight::where('destination', 'Paris')->get();

$flights = $flights->reject(function (Flight $flight) {
    return $flight->cancelled;
});

除了Laravel的基集合类提供的方法之外,Eloquent集合类还提供a few extra methods,专门用于与Eloquent模型的集合进行交互。

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

php
foreach ($flights as $flight) {
    echo $flight->name;
}

分块结果

如果你尝试通过allget方法加载数万条Eloquent记录,你的应用程序可能会耗尽内存。除了使用这些方法之外,还可以使用chunk方法来更有效地处理大量模型。

chunk方法将检索Eloquent模型的子集,并将它们传递给闭包进行处理。由于一次仅检索Eloquent模型的当前块,因此在处理大量模型时,chunk方法将显着减少内存使用量:

php
use App\Models\Flight;
use Illuminate\Database\Eloquent\Collection;

Flight::chunk(200, function (Collection $flights) {
    foreach ($flights as $flight) {
        // ...
    }
});

传递给chunk方法的第一个参数是你希望每个「块」接收的记录数。将为从数据库检索的每个块调用作为第二个参数传递的闭包。将执行数据库查询来检索传递给闭包的每个记录块。

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

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

由于chunkByIdlazyById方法将自己的「where」条件添加到正在执行的查询中,因此你通常应该在闭包中logically group自己的条件:

php
Flight::where(function ($query) {
    $query->where('delayed', true)->orWhere('cancelled', true);
})->chunkById(200, function (Collection $flights) {
    $flights->each->update([
        'departed' => false,
        'cancelled' => true
    ]);
}, column: 'id');

使用惰性集合分块

lazy方法的工作方式与thechunkmethod类似,因为它在幕后以块的形式执行查询。但是,lazy方法不是将每个块直接传递到回调中,而是返回Eloquent模型的扁平LazyCollection模型,这使你可以将结果作为单个流进行交互:

php
use App\Models\Flight;

foreach (Flight::lazy() as $flight) {
    // ...
}

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

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

你可以使用lazyByIdDesc方法根据id的降序过滤结果。

游标

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

cursor方法只会执行单个数据库查询;然而,各个Eloquent模型在实际迭代之前不会被水合。因此,在迭代游标时,在任何给定时间内存中仅保留一个Eloquent模型。

WARNING

由于cursor方法一次仅在内存中保存一个Eloquent模型,因此它无法急切加载关系。如果你需要急切加载关系,请考虑使用thelazymethod代替。

在内部,cursor方法使用 PHPgenerators来实现此功能:

php
use App\Models\Flight;

foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) {
    // ...
}

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

php
use App\Models\User;

$users = User::cursor()->filter(function (User $user) {
    return $user->id > 500;
});

foreach ($users as $user) {
    echo $user->id;
}

尽管cursor方法使用的内存比常规查询少得多(一次仅在内存中保存一个Eloquent模型),但它最终仍会耗尽内存。这是due to PHP's PDO driver internally caching all raw query results in its buffer。如果你正在处理大量Eloquent记录,请考虑改用thelazymethod

高级子查询

子查询选择

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

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

php
use App\Models\Destination;
use App\Models\Flight;

return Destination::addSelect(['last_flight' => Flight::select('name')
    ->whereColumn('destination_id', 'destinations.id')
    ->orderByDesc('arrived_at')
    ->limit(1)
])->get();

子查询排序

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

php
return Destination::orderByDesc(
    Flight::select('arrived_at')
        ->whereColumn('destination_id', 'destinations.id')
        ->orderByDesc('arrived_at')
        ->limit(1)
)->get();

检索单个模型 / 聚合

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

php
use App\Models\Flight;

// Retrieve a model by its primary key...
$flight = Flight::find(1);

// Retrieve the first model matching the query constraints...
$flight = Flight::where('active', 1)->first();

// Alternative to retrieving the first model matching the query constraints...
$flight = Flight::firstWhere('active', 1);

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

php
$flight = Flight::findOr(1, function () {
    // ...
});

$flight = Flight::where('legs', '>', 3)->firstOr(function () {
    // ...
});

未找到异常

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

php
$flight = Flight::findOrFail(1);

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

如果没有捕获到ModelNotFoundException,则会自动向客户端发送 404 HTTP 响应:

php
use App\Models\Flight;

Route::get('/api/flights/{id}', function (string $id) {
    return Flight::findOrFail($id);
});

检索或创建模型

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

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

php
use App\Models\Flight;

// Retrieve flight by name or create it if it doesn't exist...
$flight = Flight::firstOrCreate([
    'name' => 'London to Paris'
]);

// Retrieve flight by name or create it with the name, delayed, and arrival_time attributes...
$flight = Flight::firstOrCreate(
    ['name' => 'London to Paris'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);

// Retrieve flight by name or instantiate a new Flight instance...
$flight = Flight::firstOrNew([
    'name' => 'London to Paris'
]);

// Retrieve flight by name or instantiate with the name, delayed, and arrival_time attributes...
$flight = Flight::firstOrNew(
    ['name' => 'Tokyo to Sydney'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);

检索聚合

与Eloquent模型交互时,你还可以使用Laravelquery builder提供的countsummax和其他aggregate methods。正如你所期望的,这些方法返回标量值而不是Eloquent模型实例:

php
$count = Flight::where('active', 1)->count();

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

插入与更新模型

插入

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

php
<?php

namespace App\Http\Controllers;

use App\Models\Flight;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class FlightController extends Controller
{
    /**
     * Store a new flight in the database.
     */
    public function store(Request $request): RedirectResponse
    {
        // Validate the request...

        $flight = new Flight;

        $flight->name = $request->name;

        $flight->save();

        return redirect('/flights');
    }
}

In this example, we assign thenamefield from the incoming HTTP request to thenameattribute of theApp\Models\Flightmodel instance. When we call thesavemethod, a record will be inserted into the database. The model'screated_atandupdated_attimestamps will automatically be set when thesavemethod is called, so there is no need to set them manually.

如果你想在数据库事务中保存模型,你可以使用saveOrFail方法。如果保存过程中抛出异常,事务将自动回滚:

php
$flight->saveOrFail();

或者,你可以使用create方法通过单个 PHP 语句「保存」新模型。插入的模型实例将通过create方法返回给你:

php
use App\Models\Flight;

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

但是,在使用create方法之前,你需要在模型类上指定FillableGuarded属性。这些属性是必需的,因为默认情况下所有Eloquent模型都受到针对大规模分配漏洞的保护。要了解有关批量分配的更多信息,请参阅mass assignment documentation

更新

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

php
use App\Models\Flight;

$flight = Flight::find(1);

$flight->name = 'Paris to London';

$flight->save();

如果你想在数据库事务中更新模型,你可以使用updateOrFail方法。如果更新过程中抛出异常,事务会自动回滚:

php
$flight->updateOrFail(['name' => 'Paris to London']);

有时,如果不存在匹配模型,你可能需要更新现有模型或创建新模型。与firstOrCreate方法一样,updateOrCreate方法会保留模型,因此无需手动调用save方法。

在下面的示例中,如果存在departure位置为Oaklanddestination位置为San Diego的航班,则其pricediscounted列将被更新。如果不存在这样的航班,则会创建一个新航班,该航班具有将第一个参数数组与第二个参数数组合并而产生的属性:

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

当使用firstOrCreateupdateOrCreate等方法时,你可能不知道是否已创建新模型或已更新现有模型。wasRecentlyCreated属性指示模型是否是在其当前生命周期内创建的:

php
$flight = Flight::updateOrCreate(
    // ...
);

if ($flight->wasRecentlyCreated) {
    // New flight record was inserted...
}

批量更新

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

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

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

WARNING

通过Eloquent发出批量更新时,更新后的模型不会触发savingsavedupdatingupdated模型事件。这是因为在发布批量更新时永远不会实际检索模型。

检查属性变更

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

isDirty方法确定自检索模型以来模型的任何属性是否已更改。你可以将特定的属性名称或属性数组传递给isDirty方法来确定是否有任何属性是「脏」的。isClean方法将确定自检索模型以来属性是否保持不变。此方法还接受一个可选的属性参数:

php
use App\Models\User;

$user = User::create([
    'first_name' => 'Taylor',
    'last_name' => 'Otwell',
    'title' => 'Developer',
]);

$user->title = 'Painter';

$user->isDirty(); // true
$user->isDirty('title'); // true
$user->isDirty('first_name'); // false
$user->isDirty(['first_name', 'title']); // true

$user->isClean(); // false
$user->isClean('title'); // false
$user->isClean('first_name'); // true
$user->isClean(['first_name', 'title']); // false

$user->save();

$user->isDirty(); // false
$user->isClean(); // true

wasChanged方法确定当前请求周期内上次保存模型时是否更改了任何属性。如果需要,你可以传递属性名称来查看特定属性是否已更改:

php
$user = User::create([
    'first_name' => 'Taylor',
    'last_name' => 'Otwell',
    'title' => 'Developer',
]);

$user->title = 'Painter';

$user->save();

$user->wasChanged(); // true
$user->wasChanged('title'); // true
$user->wasChanged(['title', 'slug']); // true
$user->wasChanged('first_name'); // false
$user->wasChanged(['first_name', 'title']); // true

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

php
$user = User::find(1);

$user->name; // John
$user->email; // john@example.com

$user->name = 'Jack';
$user->name; // Jack

$user->getOriginal('name'); // John
$user->getOriginal(); // Array of original attributes...

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

php
$user = User::find(1);

$user->name; // John
$user->email; // john@example.com

$user->update([
    'name' => 'Jack',
    'email' => 'jack@example.com',
]);

$user->getChanges();

/*
    [
        'name' => 'Jack',
        'email' => 'jack@example.com',
    ]
*/

$user->getPrevious();

/*
    [
        'name' => 'John',
        'email' => 'john@example.com',
    ]
*/

批量赋值

你可以使用create方法通过单个 PHP 语句「保存」新模型。插入的模型实例将通过以下方法返回给你:

php
use App\Models\Flight;

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

但是,在使用create方法之前,你需要在模型类上指定FillableGuarded属性。这些属性是必需的,因为默认情况下所有Eloquent模型都受到针对大规模分配漏洞的保护。

当用户传递意外的 HTTP 请求字段并且该字段更改了数据库中你未预料到的列时,就会出现批量分配漏洞。例如,恶意用户可能通过 HTTP 请求发送is_admin参数,然后将该参数传递给模型的create方法,从而允许用户将自己升级为管理员。

因此,首先,你应该定义要进行批量分配的模型属性。你可以使用模型上的Fillable属性来执行此操作。例如,让我们将Flight模型的name属性设置为可批量分配:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;

#[Fillable(['name'])]
class Flight extends Model
{
    // ...
}

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

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

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

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

批量赋值与 JSON 列

分配 JSON 列时,必须在模型的Fillable属性中指定每列的批量可分配键。为了安全起见,当使用Guarded属性时,Laravel不支持更新嵌套 JSON 属性:

php
use Illuminate\Database\Eloquent\Attributes\Fillable;

#[Fillable(['options->enabled'])]
class Flight extends Model
{
    // ...
}

允许批量赋值

如果你想让所有属性都可批量分配,你可以在模型上使用Unguarded属性。如果你选择不保护模型,则应特别注意始终手工制作传递给Eloquent的fillcreateupdate方法的数组:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Unguarded;
use Illuminate\Database\Eloquent\Model;

#[Unguarded]
class Flight extends Model
{
    // ...
}

批量赋值异常

默认情况下,执行批量分配操作时,未包含在Fillable属性中的属性将被静默丢弃。在生产中,这是预期的行为;然而,在本地开发过程中,这可能会导致人们对模型更改为何未生效感到困惑。

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

php
use Illuminate\Database\Eloquent\Model;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Model::preventSilentlyDiscardingAttributes($this->app->isLocal());
}

Upsert

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

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

WARNING

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

删除模型

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

php
use App\Models\Flight;

$flight = Flight::find(1);

$flight->delete();

如果你想在数据库事务中删除模型,你可以使用deleteOrFail方法。如果删除过程中抛出异常,事务会自动回滚:

php
$flight->deleteOrFail();

通过主键删除已有模型

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

php
Flight::destroy(1);

Flight::destroy(1, 2, 3);

Flight::destroy([1, 2, 3]);

Flight::destroy(collect([1, 2, 3]));

如果你使用soft deleting models,你可以通过forceDestroy方法永久删除模型:

php
Flight::forceDestroy(1);

WARNING

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

使用查询删除模型

当然,你可以构建Eloquent查询来删除与查询条件匹配的所有模型。在此示例中,我们将删除所有标记为非活动的航班。与批量更新一样,批量删除不会为已删除的模型调度模型事件:

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

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

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

WARNING

通过Eloquent执行批量删除语句时,不会为已删除的模型调度deletingdeleted模型事件。这是因为执行删除语句时从未实际检索模型。

软删除

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

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Flight extends Model
{
    use SoftDeletes;
}

INFO

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

你还应该将deleted_at列添加到数据库表中。Laravelschema builder包含一个用于创建此列的辅助方法:

php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('flights', function (Blueprint $table) {
    $table->softDeletes();
});

Schema::table('flights', function (Blueprint $table) {
    $table->dropSoftDeletes();
});

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

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

php
if ($flight->trashed()) {
    // ...
}

恢复软删除模型

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

php
$flight->restore();

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

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

构建relationship查询时也可以使用restore方法:

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

永久删除模型

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

php
$flight->forceDelete();

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

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

查询软删除模型

包含软删除模型

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

php
use App\Models\Flight;

$flights = Flight::withTrashed()
    ->where('account_id', 1)
    ->get();

构建relationship查询时也可以调用withTrashed方法:

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

仅检索软删除模型

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

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

修剪模型

有时你可能想要定期删除不再需要的模型。为了实现这一点,你可以将Illuminate\Database\Eloquent\PrunableIlluminate\Database\Eloquent\MassPrunable特征添加到你想要定期修剪的模型中。将特征之一添加到模型后,实现prunable方法,该方法返回Eloquent查询生成器,用于解析不再需要的模型:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Prunable;

class Flight extends Model
{
    use Prunable;

    /**
     * Get the prunable model query.
     */
    public function prunable(): Builder
    {
        return static::where('created_at', '<=', now()->minus(months: 1));
    }
}

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

php
/**
 * Prepare the model for pruning.
 */
protected function pruning(): void
{
    // ...
}

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

php
use Illuminate\Support\Facades\Schedule;

Schedule::command('model:prune')->daily();

在幕后,model:prune命令将自动检测应用程序的app/Models目录中的「Prunable」模型。如果你的模型位于不同的位置,你可以使用--model选项来指定模型类名称:

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

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

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

你可以通过执行带有--pretend选项的model:prune命令来测试prunable查询。假装时,model:prune命令将简单地报告如果该命令实际运行,将修剪多少条记录:

shell
php artisan model:prune --pretend

WARNING

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

批量修剪

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

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\MassPrunable;

class Flight extends Model
{
    use MassPrunable;

    /**
     * Get the prunable model query.
     */
    public function prunable(): Builder
    {
        return static::where('created_at', '<=', now()->minus(months: 1));
    }
}

复制模型

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

php
use App\Models\Address;

$shipping = Address::create([
    'type' => 'shipping',
    'line_1' => '123 Example Street',
    'city' => 'Victorville',
    'state' => 'CA',
    'postcode' => '90001',
]);

$billing = $shipping->replicate()->fill([
    'type' => 'billing'
]);

$billing->save();

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

php
$flight = Flight::create([
    'destination' => 'LAX',
    'origin' => 'LHR',
    'last_flown' => '2020-03-04 11:00:00',
    'last_pilot_id' => 747,
]);

$flight = $flight->replicate([
    'last_flown',
    'last_pilot_id'
]);

查询作用域

全局作用域

全局范围允许你向给定模型的所有查询添加约束。Laravel自己的soft delete功能利用全局范围仅从数据库中检索「未删除」模型。编写自己的全局作用域可以提供一种方便、简单的方法来确保给定模型的每个查询都受到某些约束。

生成作用域

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

shell
php artisan make:scope AncientScope

编写全局作用域

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

php
<?php

namespace App\Models\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class AncientScope implements Scope
{
    /**
     * Apply the scope to a given Eloquent query builder.
     */
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where('created_at', '<', now()->minus(years: 2000));
    }
}

INFO

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

应用全局作用域

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

php
<?php

namespace App\Models;

use App\Models\Scopes\AncientScope;
use Illuminate\Database\Eloquent\Attributes\ScopedBy;

#[ScopedBy([AncientScope::class])]
class User extends Model
{
    //
}

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

php
<?php

namespace App\Models;

use App\Models\Scopes\AncientScope;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * The "booted" method of the model.
     */
    protected static function booted(): void
    {
        static::addGlobalScope(new AncientScope);
    }
}

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

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

匿名全局作用域

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

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * The "booted" method of the model.
     */
    protected static function booted(): void
    {
        static::addGlobalScope('ancient', function (Builder $builder) {
            $builder->where('created_at', '<', now()->minus(years: 2000));
        });
    }
}

移除全局作用域

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

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

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

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

如果你想删除多个甚至全部查询的全局范围,你可以使用withoutGlobalScopeswithoutGlobalScopesExcept方法:

php
// Remove all of the global scopes...
User::withoutGlobalScopes()->get();

// Remove some of the global scopes...
User::withoutGlobalScopes([
    FirstScope::class, SecondScope::class
])->get();

// Remove all global scopes except the given ones...
User::withoutGlobalScopesExcept([
    SecondScope::class,
])->get();

本地作用域

本地范围允许你定义常见的查询约束集,你可以在整个应用程序中轻松地重复使用它们。例如,你可能需要经常检索所有被视为「热门」的用户。要定义范围,请将Scope属性添加到Eloquent方法。

范围应始终返回相同的查询构建器实例或void

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Scope a query to only include popular users.
     */
    #[Scope]
    protected function popular(Builder $query): void
    {
        $query->where('votes', '>', 100);
    }

    /**
     * Scope a query to only include active users.
     */
    #[Scope]
    protected function active(Builder $query): void
    {
        $query->where('active', 1);
    }
}

使用本地作用域

定义范围后,你可以在查询模型时调用范围方法。你甚至可以将调用链接到各种范围:

php
use App\Models\User;

$users = User::popular()->active()->orderBy('created_at')->get();

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

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

然而,由于这可能很麻烦,Laravel提供了一个「高阶」orWhere方法,允许你在不使用闭包的情况下流畅地将作用域链接在一起:

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

动态作用域

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

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Scope a query to only include users of a given type.
     */
    #[Scope]
    protected function ofType(Builder $query, string $type): void
    {
        $query->where('type', $type);
    }
}

将预期参数添加到作用域方法的签名后,你可以在调用作用域时传递参数:

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

属性范围方法应该是protected。从模型类中调用属性作用域时,请通过查询生成器实例(例如static::query()->ofType('admin'))调用该作用域,以确保调用通过Eloquent的作用域处理进行路由。

待定属性

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

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * Scope the query to only include drafts.
     */
    #[Scope]
    protected function draft(Builder $query): void
    {
        $query->withAttributes([
            'hidden' => true,
        ]);
    }
}

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

php
$draft = Post::draft()->create(['title' => 'In Progress']);

$draft->hidden; // true

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

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

比较模型

有时你可能需要确定两个模型是否「相同」。isisNot方法可用于快速验证两个模型是否具有相同的主键、表和数据库连接:

php
if ($post->is($anotherPost)) {
    // ...
}

if ($post->isNot($anotherPost)) {
    // ...
}

当使用belongsTohasOnemorphTomorphOnerelationships时,isisNot方法也可用。当你想要比较相关模型而不发出查询来检索该模型时,此方法特别有用:

php
if ($post->author()->is($user)) {
    // ...
}

事件

INFO

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

Eloquent模型调度多个事件,允许你挂钩模型生命周期中的以下时刻:retrievedcreatingcreatedupdatingupdatedsavingsaveddeletingdeletedtrashedforceDeletingforceDeletedrestoringrestoredreplicating

当从数据库检索现有模型时,将调度retrieved事件。第一次保存新模型时,将调度creatingcreated事件。当修改现有模型并调用save方法时,将调度updating/updated事件。saving/saved事件将在创建或更新模型时调度 - 即使模型的属性尚未更改。以-ing结尾的事件名称在持久化模型的任何更改之前调度,而以-ed结尾的事件在持久化模型的更改之后调度。

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

php
<?php

namespace App\Models;

use App\Events\UserDeleted;
use App\Events\UserSaved;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The event map for the model.
     *
     * @var array<string, string>
     */
    protected $dispatchesEvents = [
        'saved' => UserSaved::class,
        'deleted' => UserDeleted::class,
    ];
}

定义并映射Eloquent事件后,你可以使用event listeners来处理事件。

WARNING

通过Eloquent发出批量更新或删除查询时,不会为受影响的模型调度savedupdateddeletingdeleted模型事件。这是因为在执行批量更新或删除时永远不会实际检索模型。

使用闭包

你可以注册在分派各种模型事件时执行的闭包,而不是使用自定义事件类。通常,你应该在模型的booted方法中注册这些闭包:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * The "booted" method of the model.
     */
    protected static function booted(): void
    {
        static::created(function (User $user) {
            // ...
        });
    }
}

如果需要,你可以在注册模型事件时使用queueable anonymous event listeners。这将指示Laravel使用应用程序的queue在后台执行模型事件侦听器:

php
use function Illuminate\Events\queueable;

static::created(queueable(function (User $user) {
    // ...
}));

观察者

定义观察者

如果你正在侦听给定模型上的许多事件,则可以使用观察者将所有侦听器分组到一个类中。观察者类的方法名称反映了你希望侦听的Eloquent事件。这些方法中的每一个都接收受影响的模型作为其唯一的参数。make:observerArtisan 命令是创建新观察者类的最简单方法:

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

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

php
<?php

namespace App\Observers;

use App\Models\User;

class UserObserver
{
    /**
     * Handle the User "created" event.
     */
    public function created(User $user): void
    {
        // ...
    }

    /**
     * Handle the User "updated" event.
     */
    public function updated(User $user): void
    {
        // ...
    }

    /**
     * Handle the User "deleted" event.
     */
    public function deleted(User $user): void
    {
        // ...
    }

    /**
     * Handle the User "restored" event.
     */
    public function restored(User $user): void
    {
        // ...
    }

    /**
     * Handle the User "forceDeleted" event.
     */
    public function forceDeleted(User $user): void
    {
        // ...
    }
}

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

php
use App\Observers\UserObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;

#[ObservedBy([UserObserver::class])]
class User extends Authenticatable
{
    //
}

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

php
use App\Models\User;
use App\Observers\UserObserver;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    User::observe(UserObserver::class);
}

INFO

观察者还可以监听其他事件,例如savingretrieved。这些事件在events文档中进行了描述。

观察者与数据库事务

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

php
<?php

namespace App\Observers;

use App\Models\User;
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;

class UserObserver implements ShouldHandleEventsAfterCommit
{
    /**
     * Handle the User "created" event.
     */
    public function created(User $user): void
    {
        // ...
    }
}

静默事件

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

php
use App\Models\User;

$user = User::withoutEvents(function () {
    User::findOrFail(1)->delete();

    return User::find(2);
});

保存单个模型且不触发事件

有时你可能希望「保存」给定模型而不调度任何事件。你可以使用saveQuietly方法来完成此操作:

php
$user = User::findOrFail(1);

$user->name = 'Victoria Faith';

$user->saveQuietly();

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

php
$user->deleteQuietly();
$user->forceDeleteQuietly();
$user->restoreQuietly();