快速入门


介绍

Laravel 包含了 Eloquent,一个对象关系映射 (ORM) 工具,使与数据库的交互变得愉悦。使用 Eloquent 时,每个数据库表都有一个相应的 "Model" 用于与该表进行交互。除了从数据库表中检索记录,Eloquent 模型还允许你向表中插入、更新和删除记录。

注意

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

Laravel 训练营

如果你是 Laravel 的新手,可以参加Laravel 训练营。Laravel 训练营将带你逐步构建你的第一个 Laravel 应用程序,并使用 Eloquent 进行开发。这是了解 Laravel 和 Eloquent 提供的一切的好办法。

生成模型类

首先,让我们创建一个 Eloquent 模型。模型通常存放在 app\Models 目录中,并且继承自 Illuminate\Database\Eloquent\Model 类。你可以使用 make:modelArtisan 命令来生成一个新模型:

php artisan make:model Flight

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

php artisan make:model Flight --migration

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

# 生成模型和 FlightFactory 类...
php artisan make:model Flight --factory
php artisan make:model Flight -f
# 生成模型和 FlightSeeder 类...
php artisan make:model Flight --seed
php artisan make:model Flight -s
# 生成模型和 FlightController 类...
php artisan make:model Flight --controller
php artisan make:model Flight -c
# 生成模型、FlightController 资源类和表单请求类...
php artisan make:model Flight --controller --resource --requests
php artisan make:model Flight -crR
# 生成模型和 FlightPolicy 类...
php artisan make:model Flight --policy
# 生成模型和迁移、工厂、填充器和控制器...
php artisan make:model Flight -mfsc
# 使用快捷方式生成模型、迁移、工厂、填充器、策略、控制器和表单请求...
php artisan make:model Flight --all
# 生成一个中间表模型...
php artisan make:model Member --pivot
php artisan make:model Member -p

检查模型

有时候通过查看模型的代码很难确定模型的所有可用属性和关系。相反,可以尝试使用 model:show Artisan 命令,它提供了模型的所有属性和关系的方便概览:

php artisan model:show Flight

Eloquent 模型约定

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

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
    // ...
}

表名

在上面的示例中,你可能注意到我们没有告诉 Eloquent Flight 模型对应的数据库表的名称。按照约定,类的 "蛇形命名法" 复数形式将被用作表名,除非另外指定名称。因此,在这种情况下,Eloquent 将假设 Flight 模型将记录存储在 flights 表中,而 AirTrafficController 模型将记录存储在 air_traffic_controllers 表中。

如果你的模型对应的数据库表不符合这个约定,你可以在模型上定义一个 table 属性来手动指定模型的表名:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
    /**
     * 与模型关联的表名
     *
     * @var string
     */
    protected $table = 'my_flights';
}

主键

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

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
    /**
     * 与表关联的主键
     *
     * @var string
     */
    protected $primaryKey = 'flight_id';
}

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

<?php
class Flight extends Model
{
    /**
     * 指示模型的 ID 是否自增长。
     *
     * @var bool
     */
    public $incrementing = false;
}

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

<?php
class Flight extends Model
{
    /**
     * 自增长 ID 的数据类型。
     *
     * @var string
     */
    protected $keyType = 'string';
}
"组合"主键

Eloquent 要求每个模型至少有一个用作主键的唯一标识符 "ID"。Eloquent 模型不支持 "组合"主键。但是,你可以在数据库表中除了表的唯一标识主键外,再添加多列的唯一索引。

UUID 和 ULID 主键

如果你不想使用自增整数作为 Eloquent 模型的主键,可以选择使用 UUID 作为主键。UUID 是通用唯一识别符,它是一个 36 个字符长的字母数字标识符。

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

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; // "8f8e8478-9035-4d23-b9a7-62f4d2612ce5"

默认情况下,HasUuids trait 会为你的模型生成"有序" UUID。这些 UUID 在索引数据库存储时更高效,因为它们可以按字典序排序。

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

use Ramsey\Uuid\Uuid;
/**
 * 为模型生成新的 UUID。
 */
public function newUniqueId(): string
{
    return (string) Uuid::uuid4();
}
/**
 * 获取应接收唯一标识符的列。
 *
 * @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 trait,并确保该模型具有ULID 主键列的 ULID 等效

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 自动管理这些列,你应该在模型上定义一个 $timestamps 属性,并将其设置为 false

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
    /**
     * 指示模型是否应该有时间戳。
     *
     * @var bool
     */
    public $timestamps = false;
}

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

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
    /**
     * 模型日期列的存储格式。
     *
     * @var string
     */
    protected $dateFormat = 'U';
}

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

<?php
class Flight extends Model
{
    const CREATED_AT = 'creation_date';
    const UPDATED_AT = 'updated_date';
}

如果你希望在不修改模型的 updated_at 时间戳的情况下执行模型操作,可以在传递给 withoutTimestamps 方法的闭包中操作模型:

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

数据库连接

默认情况下,所有 Eloquent 模型将使用应用程序配置的默认数据库连接。如果你想使用不同的连接与特定模型进行交互,你应该在模型上定义一个 $connection 属性:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
    /**
     * 模型应使用的数据库连接。
     *
     * @var string
     */
    protected $connection = 'sqlite';
}

默认属性值

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

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
    /**
     * 模型属性的默认值。
     *
     * @var array
     */
    protected $attributes = [
        'options' => '[]',
        'delayed' => false,
    ];
}

配置 Eloquent 的严格性

Laravel 提供了几种方式,允许你在各种情况下配置 Eloquent 的行为和 "严格性"。

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

use Illuminate\Database\Eloquent\Model;
/**
 * 引导任何应用程序服务。
 */
public function boot(): void
{
    Model::preventLazyLoading(! $this->app->isProduction());
}

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

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

检索模型

创建模型和其关联的数据库表之后,可以开始从数据库中检索数据。你可以将每个 Eloquent 模型都视为一个强大的查询构造器,允许你流畅地查询与模型关联的数据库表。模型的 all 方法将检索模型关联的数据库表中的所有记录:

use App\Models\Flight;
foreach (Flight::all() as $flight) {
    echo $flight->name;
}
构建查询

all 方法将返回模型的表中的所有结果。然而,由于每个 Eloquent 模型都充当查询构造器,你可以向查询添加其他约束,然后调用 get 方法来检索结果:

$flights = Flight::where('active', 1)
               ->orderBy('name')
               ->take(10)
               ->get();
```**注意**

由于Eloquent模型是查询构建器,因此您应该查看Laravel的[查询构建器](https://laravel.com/docs/10.x/queries)提供的所有方法。在编写Eloquent查询时,您可以使用这些方法中的任何一个。

#### 刷新模型

如果您已经有从数据库中检索到的Eloquent模型的实例,可以使用`fresh`和`refresh`方法"刷新"模型。`fresh`方法会重新从数据库中检索模型。现有的模型实例不会受到影响:

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

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

$flight = Flight::where('number', 'FR 900')->first();
$flight->number = 'FR 456';
$flight->refresh();
$flight->number; // "FR 900"

集合

正如我们所见,类似于allget的Eloquent方法会从数据库中检索多条记录。但是,这些方法不会返回普通的PHP数组。相反,会返回Illuminate\Database\Eloquent\Collection的实例。

Eloquent的Collection类扩展了Laravel的基本Illuminate\Support\Collection类,该类提供了各种有用的方法以与数据集交互。例如,可以使用reject方法基于调用的闭包的结果从集合中删除模型:

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

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

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

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

分块处理结果

如果通过allget方法尝试加载数以万计的Eloquent记录,您的应用程序可能会耗尽内存。您可以使用chunk方法来更高效地处理大量的模型。

chunk方法会检索子集的Eloquent模型,并将它们传递给闭包进行处理。由于每次只检索当前块的Eloquent模型,因此当处理大量模型时,chunk方法将显著降低内存使用率:

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列的模型:

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

使用懒惰集合进行分块

lazy方法与分块处理结果的chunk方法类似,因为它在幕后以块执行查询。但是,与直接将每个块传递给回调不同,lazy方法返回Eloquent模型的扁平化LazyCollection,使您可以将结果作为单个流进行交互:

use App\Models\Flight;
foreach (Flight::lazy() as $flight) {
    // ...
}

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

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

您可以使用lazyByIdDesc方法根据id的降序对结果进行过滤。

游标

lazy方法类似,当迭代数以万计的Eloquent模型记录时,cursor方法可以显著降低应用程序的内存消耗。

cursor方法只会执行一次数据库查询;但是,只有在实际进行迭代时,才会将各个Eloquent模型进行填充。因此,在迭代游标时,每次只有一个Eloquent模型保存在内存中。

警告

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

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

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

cursor返回的是一个Illuminate\Support\LazyCollection实例。懒惰集合允许您在一次只加载一个模型到内存中的同时使用许多常见的Laravel集合方法:

use App\Models\User;
$users = User::cursor()->filter(function (User $user) {
    return $user->id > 500;
});
foreach ($users as $user) {
    echo $user->id;
}

尽管cursor方法使用的内存比常规查询少得多(一次只保存一个Eloquent模型在内存中),但最终仍然会耗尽内存。这是因为PHP的PDO驱动在其缓冲区中内部缓存所有原始查询结果。如果处理非常大量的Eloquent记录,请考虑改用懒惰集合的lazy方法

高级子查询

子查询选择

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

使用查询构建器的selectaddSelect方法提供的子查询功能,可以使用一个查询选择所有的目的地和最近到达该目的地的航班的名称:

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函数支持子查询。继续使用我们的航班示例,我们可以使用此功能根据最后一次到达航班的时间对所有目的地进行排序。同样,这可以在执行单个数据库查询时完成:

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

检索单个模型/聚合

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

use App\Models\Flight;
//按主键检索模型...
$flight = Flight::find(1);
//检索与查询约束匹配的第一个模型...
$flight = Flight::where('active', 1)->first();
//替代检索与查询约束匹配的第一个模型...
$flight = Flight::firstWhere('active', 1);

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

use App\Models\Flight;
//将飞行按照主键检索或创建新的飞行模型...
$flight = Flight::findOr(1, function () {
    //...
});
$flight = Flight::where('legs', '>', 3)->firstOr(function () {
    //...
});
模型未找到异常

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

use App\Models\Flight;
$flight = Flight::findOrFail(1);
$flight = Flight::where('legs', '>', 3)->firstOrFail();

如果未捕获ModelNotFoundException,将自动向客户端发送404 HTTP响应:

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

检索或创建模型

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

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

use App\Models\Flight;
//按名称检索航班或创建它(如果不存在)...
$flight = Flight::firstOrCreate([
    'name' => 'London to Paris'
]);
//按名称检索航班或使用名称,延误和到达时间属性创建它...
$flight = Flight::firstOrCreate(
    ['name' => 'London to Paris'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);
//按名称检索航班或实例化新的航班实例...
$flight = Flight::firstOrNew([
    'name' => 'London to Paris'
]);
//按名称检索航班或实例化并使用名称,延误和到达时间属性...
$flight = Flight::firstOrNew(
    ['name' => 'Tokyo to Sydney'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);

检索聚合

与Eloquent模型交互时,您还可以使用countsummax和其他Laravel查询生成器提供的聚合方法。正如您所期望的那样,这些方法返回一个标量值,而不是一个Eloquent模型实例:

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

插入和更新模型

插入

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

<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Flight;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class FlightController extends Controller
{
    /**
     * 在数据库中存储新的航班.
     */
    public function store(Request $request): RedirectResponse
    {
        // 验证请求...
        $flight = new Flight;
        $flight->name = $request->name;
        $flight->save();
        return redirect('/flights');
    }
}

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

或者,您可以使用create方法以单个PHP语句来"保存"新的模型。create方法将返回插入的模型实例:

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

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

更新

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

use App\Models\Flight;
$flight = Flight::find(1);
$flight->name = 'Paris to London';
$flight->save();
批量更新

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

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

update方法期望一个包含要更新的列和值对的数组。update方法返回受影响的行数。

警告

当通过Eloquent发出批量更新时,不会触发模型的savingsavedupdatingupdated事件,因为当发出批量更新时,实际上从不检索模型。

检查属性更改

Eloquent提供了isDirtyisCleanwasChanged方法来检查模型的内部状态,以确定其属性与检索模型时的属性相比发生了哪些更改。

isDirty方法确定是否自从检索模型以来已更改模型的任何属性。您可以向isDirty方法传递特定属性名称或属性数组,以确定属性是否是"脏的"。isClean方法确定属性是否与检索模型时的属性保持不变。此方法还接受一个可选的属性参数:

#use App\Models\User;
$user = User::create([
    'first_name' => 'Taylor',
    'last_name' => 'Otwell',
    'title' => '开发者',
]);
$user->title = '画家';
$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 方法用于确定模型在当前请求周期内最后保存时是否有任何属性发生更改。如果需要,可以传递属性名来查看特定属性是否发生了更改:


$user = User::create([
    'first_name' => 'Taylor',
    'last_name' => 'Otwell',
    'title' => '开发者',
]);
$user->title = '画家';
$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 方法返回一个包含模型的原始属性的数组,无论自从检索该模型以来是否对模型进行了任何更改。如果需要,可以传递特定的属性名以获取特定属性的原始值:

$user = User::find(1);
$user->name; // John

$user->email; // [email protected]
$user->name = "Jack";
$user->name; // Jack
$user->getOriginal('name'); // John
$user->getOriginal(); // 原始属性的数组...

批量赋值

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

use App\Models\Flight;
$flight = Flight::create([
    'name' => '伦敦到巴黎',
]);

但是,在使用 create 方法之前,您需要在模型类上指定 fillableguarded 属性之一。默认情况下,所有的 Eloquent 模型都受到了批量赋值漏洞的保护。

当用户传递一个意外的 HTTP 请求字段并且该字段更改了您在数据库中未预期更改的列时,会发生批量赋值漏洞。例如,一个恶意用户可能会通过 HTTP 请求发送一个 is_admin 参数,然后将其传递给您的模型的 create 方法,从而使用户升级为管理员。

因此,您应该首先确定要创建可批量赋值的模型的哪些属性。您可以使用模型上的 $fillable 属性来进行设置。例如,让我们将 Flight 模型的 name 属性设置为可批量赋值:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
    /**
     * 可批量赋值的属性。
     *
     * @var array
     */
    protected $fillable = ['name'];
}

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

$flight = Flight::create(['name' => '伦敦到巴黎']);

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

$flight->fill(['name' => '阿姆斯特丹到法兰克福']);
批量赋值和 JSON 列

当分配 JSON 列时,每个列的可批量赋值键必须在模型的 $fillable 数组中指定。出于安全原因,Laravel 不支持在使用 guarded 属性时更新嵌套的 JSON 属性:

/**
 * 可批量赋值的属性。
 *
 * @var array
 */
protected $fillable = [
    'options->enabled',
];
允许批量赋值

如果您希望使所有属性都可批量赋值,可以将您的模型的 $guarded 属性定义为空数组。如果选择取消守卫模型,请特别注意始终手工定制传递给 Eloquent 的 fillcreateupdate 方法的数组:

/**
 * 不可批量赋值的属性。
 *
 * @var array
 */
protected $guarded = [];
批量赋值异常

默认情况下,在进行批量赋值操作时,不包含在 $fillable 数组中的属性将被静默丢弃。在生产环境中,这是期望的行为;然而,在本地开发期间,这可能会导致困惑,因为无法看到模型更改是否生效。

如果希望,您可以在尝试填充不可填充属性时调用 preventSilentlyDiscardingAttributes 方法,以便 Laravel 在碰到不可填充属性时抛出异常。通常情况下,应该在应用程序的服务提供者的 boot 方法中调用此方法:

use Illuminate\Database\Eloquent\Model;
/**
 * 引导任何应用程序服务。
 */
public function boot(): void
{
    Model::preventSilentlyDiscardingAttributes($this->app->isLocal());
}

更新或插入操作

偶尔,您可能需要更新现有的模型,如果没有匹配的模型,则创建一个新的模型。与firstOrCreate方法一样,updateOrCreate方法会持久化模型,所以不需要手动调用save方法。

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

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

如果您想在单个查询中执行多个“upsert”操作,则应改用upsert方法。该方法的第一个参数是要插入或更新的值,而第二个参数是列出表中唯一标识记录的列。方法的第三个和最后一个参数是一个数组,其中列出了如果数据库中已存在匹配的记录应该更新的列。如果模型上启用了时间戳,upsert方法将自动设置created_atupdated_at时间戳:

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

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

删除模型

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

use App\Models\Flight;

$flight = Flight::find(1);
$flight->delete();

您可以调用truncate方法来删除模型的所有关联数据库记录。truncate操作还将重置模型关联表上的任何自增ID:

Flight::truncate();
根据主键删除现有模型

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

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

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

使用查询删除模型

当然,您可以构建一个Eloquent查询来删除所有符合查询条件的模型。在此示例中,我们将删除所有标记为不活动的航班。与批量更新一样,批量删除将不会为删除的模型分派模型事件:

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

注意
通过Eloquent执行批量删除语句时,已删除的模型将不会触发deletingdeleted模型事件。这是因为在执行删除语句时,实际上没有检索到模型。

软删除

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

<?php

namespace App\Models;

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

class Flight extends Model
{
    use SoftDeletes;
}

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

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

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方法:

if ($flight->trashed()) {
    // ...
}
还原已删除的软删除模型

有时您可能希望“取消删除”一个软删除的模型。要还原软删除的模型,可以在模型实例上调用restore方法。restore方法将模型的deleted_at列设置为null

$flight->restore();

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

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

在构建关联查询时,也可以使用restore方法:

$flight->history()->restore();
永久删除模型

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

$flight->forceDelete();

在构建Eloquent关联查询时,您也可以使用forceDelete方法:

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

查询软删除的模型

包括软删除的模型

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

use App\Models\Flight;

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

当构建关联查询时,也可以调用withTrashed方法:

$flight->history()->withTrashed()->get();
仅检索软删除的模型

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

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

修剪模型

有时,您可能希望定期删除不再需要的模型。为此,您可以将Illuminate\Database\Eloquent\PrunableIlluminate\Database\Eloquent\MassPrunable特性添加到您希望定期修剪的模型上。在将特性之一添加到模型之后,实现一个prunable方法,该方法返回用于解析不再需要的模型的Eloquent查询构建器:

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

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

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

配置完可修剪的模型后,应在应用程序的 App\Console\Kernel 类中安排model:prune Artisan命令。你可以自由选择运行该命令的适当间隔:

/**
 * Define the application's command schedule.
 */
protected function schedule(Schedule $schedule): void
{
    $schedule->command('model:prune')->daily();
}

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

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

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

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

您可以使用--pretend选项执行带有prunable查询的model:prune命令来测试您的查询。在预演时,model:prune命令将只报告如果命令实际运行,将剪切多少记录:

php artisan model:prune --pretend

注意
如果软删除的模型与修剪查询匹配,则将永久删除(forceDelete)这些模型。

批量修剪

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

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

复制模型

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

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 方法:

$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 的 软删除 功能就是利用全局作用域从数据库中只检索“未删除”的模型。编写自己的全局作用域可以提供一种方便、简单的方式来确保给定模型的每个查询都接收到特定的约束条件。

生成作用域

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

php artisan make:scope AncientScope
编写全局作用域

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

<?php
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class AncientScope implements Scope
{
    /**
     * 将作用域应用于给定的 Eloquent 查询构建器。
     */
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where('created_at', '<', now()->subYears(2000));
    }
}

注意

如果您的全局作用域正在向查询的选择子句中添加列,您应该使用 addSelect 方法而不是 select。这将防止意外替换查询的现有选择子句。

应用全局作用域

要为模型分配一个全局作用域,您应该覆盖模型的 booted 方法并调用模型的 addGlobalScope 方法。addGlobalScope 方法接受您的作用域的实例作为其唯一参数:

<?php
namespace App\Models;
use App\Models\Scopes\AncientScope;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
    /**
     * 模型的 "booted" 方法。
     */
    protected static function booted(): void
    {
        static::addGlobalScope(new AncientScope);
    }
}

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

select * from `users` where `created_at` < 0021-02-18 00:00:00
匿名全局作用域

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

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
    /**
     * 模型的 "booted" 方法。
     */
    protected static function booted(): void
    {
        static::addGlobalScope('ancient', function (Builder $builder) {
            $builder->where('created_at', '<', now()->subYears(2000));
        });
    }
}
移除全局作用域

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

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

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

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

如果您想要删除查询的多个甚至全部全局作用域,您可以使用 withoutGlobalScopes 方法:

// 删除所有的全局作用域...
User::withoutGlobalScopes()->get();
// 删除一部分全局作用域...
User::withoutGlobalScopes([
    FirstScope::class, SecondScope::class
])->get();

本地作用域

本地作用域允许您定义常用的查询约束,您可以在整个应用程序中轻松重用。例如,您经常需要检索所有被认为是“受欢迎”的用户。要定义一个作用域,只需在 Eloquent 模型的方法前缀中添加 scope

作用域应始终返回相同的查询构建器实例或 void

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
    /**
     * 限制查询只包括受欢迎的用户。
     */
    public function scopePopular(Builder $query): void
    {
        $query->where('votes', '>', 100);
    }
    /**
     * 限制查询只包括活跃用户。
     */
    public function scopeActive(Builder $query): void
    {
        $query->where('active', 1);
    }
}
使用本地作用域

一旦定义了作用域,可以在查询模型时调用作用域方法。但是,当调用方法时,不应包含 scope 前缀。您甚至可以链接各种作用域的调用:

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

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

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

然而,由于这可能很麻烦,Laravel 提供了一个“higher order”orWhere 方法,可以让您在不使用闭包的情况下流畅地链接各种作用域:

$users = User::popular()->orWhere->active()->get();
动态作用域

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

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
    /**
     * 限制查询只包括指定类型的用户。
     */
    public function scopeOfType(Builder $query, string $type): void
    {
        $query->where('type', $type);
    }
}

一旦已将预期参数添加到作用域方法的签名中,您可以在调用作用域时传递参数:

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

比较模型

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

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

在使用 belongsTohasOnemorphTomorphOne 关系时,isisNot 方法也是可用的。当您想要比较一个相关模型而不发出查询以检索该模型时,这个方法特别有用:

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

事件

注意

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

Eloquent 模型会触发多个事件,允许您挂接到模型的生命周期中的以下时刻:retrievedcreatingcreatedupdatingupdatedsavingsaveddeletingdeletedtrashedforceDeletingforceDeletedrestoringrestoredreplicating

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

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

<?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;
    /**
     * 模型的事件映射。
     *
     * @var array
     */
    protected $dispatchesEvents = [
        'saved' => UserSaved::class,
        'deleted' => UserDeleted::class,
    ];
}

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

警告

当通过 Eloquent 发出批量更新或删除查询时,将不会为受影响的模型触发 savedupdateddeletingdeleted 模型事件。这是因为在执行批量更新或删除时,实际上从未检索到模型。

使用闭包

您可以注册闭包来处理各种模型事件,而不是使用自定义的事件类。通常情况下,应在您的模型的 booted 方法中注册这些闭包:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
    /**
     * 模型的 "booted" 方法。
     */
    protected static function booted(): void
    {
        static::created(function (User $user) {
            // ...
        });
    }
}

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

use function Illuminate\Events\queueable;
static::created(queueable(function (User $user) {
    // ...
}));

观察者

定义观察者

如果您要在给定模型上监听许多事件,可以使用观察者将所有监听器分组到一个类中。观察者类的方法名应与要监听的 Eloquent 事件相对应。每个这些方法都接收受影响的模型作为它们的唯一参数。make:observer Artisan 命令是创建新观察者类的最简单方法:

php artisan make:observer UserObserver --model=User

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

<?php
namespace App\Observers;
use App\Models\User;
class UserObserver
{
    /**
     * 处理 User "created" 事件。
     */
    public function created(User $user): void
    {
        // ...
    }
    /**
     * 处理 User "updated" 事件。
     */
    public function updated(User $user): void
    {
        // ...
    }
    /**
     * 处理 User "deleted" 事件。
     */
    public function deleted(User $user): void
    {
        // ...
    }
    /**
     * 处理 User "restored" 事件。
     */
    public function restored(User $user): void
    {
        // ...
    }
    /**
     * 处理 User "forceDeleted" 事件。
     */
    public function forceDeleted(User $user): void
    {
        // ...
    }
}

要注册观察者,需要在要观察的模型上调用 observe 方法。您可以在应用程序的 App\Providers\EventServiceProvider 服务提供者的 boot 方法中注册观察者:

use App\Models\User;
use App\Observers\UserObserver;
/**
 * Register any events for your application.
 */
public function boot(): void
{
    User::observe(UserObserver::class);
}

或者,您可以在应用程序的 App\Providers\EventServiceProvider 类中的 $observers 属性中列出观察者:

use App\Models\User;
use App\Observers\UserObserver;
/**
 * 您的应用程序的模型观察者。
 *
 * @var array
 */
protected $observers = [
    User::class => [UserObserver::class],
];

注意

观察者可以侦听的事件还有 savingretrieved,这些事件在 事件 文档中有描述。

观察者和数据库事务

当在数据库事务中创建模型时,您可能希望指示观察者仅在数据库事务提交后执行其事件处理程序。您可以通过在观察者上定义一个 $afterCommit 属性来实现此目的。如果没有进行数据库事务,事件处理程序将立即执行:

<?php
namespace App\Observers;
use App\Models\User;
class UserObserver
{
    /**
     * 在所有事务提交后处理事件。
     *
     * @var bool
     */
    public $afterCommit = true;
    /**
     * 处理 User "created" 事件。
     */
    public function created(User $user): void
    {
        // ...
    }
}

静音事件

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

use App\Models\User;
$user = User::withoutEvents(function () {
    User::findOrFail(1)->delete();
    return User::find(2);
});
静默保存单个模型

有时,您可能希望“保存”给定模型时不触发任何事件。您可以使用 saveQuietly 方法来实现此目的:

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

您还可以在不触发任何事件的情况下“更新”、“删除”、“软删除”、“恢复”和“复制”给定模型:

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

本文由极客智坊网页翻译服务自动翻译完成:

微信扫描体验极客翻译

原文地址:https://laravel.com/docs/10.x/eloquent


点赞 取消点赞 收藏 取消收藏

<< 上一篇: Redis

>> 下一篇: Laravel Pennant