Eloquent:入门
介绍
Laravel 包含 Eloquent,这是一个对象关系映射器(ORM),使与数据库的交互变得愉快。使用 Eloquent 时,每个数据库表都有一个对应的 "模型",用于与该表交互。除了从数据库表中检索记录外,Eloquent 模型还允许您插入、更新和删除表中的记录。
在开始之前,请确保在应用程序的 config/database.php
配置文件中配置数据库连接。有关配置数据库的更多信息,请查看 数据库配置文档。
Laravel Bootcamp
如果您是 Laravel 新手,可以随时进入 Laravel Bootcamp。Laravel Bootcamp 将引导您使用 Eloquent 构建第一个 Laravel 应用程序。这是一个了解 Laravel 和 Eloquent 所提供的一切的好方法。
生成模型类
首先,让我们创建一个 Eloquent 模型。模型通常位于 app\Models
目录中,并扩展 Illuminate\Database\Eloquent\Model
类。您可以使用 make:model
Artisan 命令 生成新模型:
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 键
您可以选择使用 UUID 而不是自动递增整数作为 Eloquent 模型的主键。UUID 是 36 个字符长的全局唯一字母数字标识符。
如果您希望模型使用 UUID 键而不是自动递增整数键,可以在模型上使用 Illuminate\Database\Eloquent\Concerns\HasUuids
trait。当然,您应确保模型具有 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'];
}
如果您愿意,可以选择使用 "ULIDs" 而不是 UUID。ULIDs 类似于 UUID;然而,它们只有 26 个字符长。与有序 UUID 一样,ULIDs 可以按字典顺序排序,以实现高效的数据库索引。要使用 ULIDs,您应在模型上使用 Illuminate\Database\Eloquent\Concerns\HasUlids
trait。您还应确保模型具有 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_at
和 updated_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_AT
和 UPDATED_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
方法接受一个可选的布尔参数,指示是否应防止惰性加载。例如,您可能希望仅在非生产环境中禁用惰性加载,以便即使在生产代码中意外存在惰性加载关系,生产环境也能正常运行。通常,此方法应在应用程序的 AppServiceProvider
的 boot
方法中调用:
use Illuminate\Database\Eloquent\Model;
/**
* 启动任何应用程序服务。
*/
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}
此外,您可以通过调用 preventSilentlyDiscardingAttributes
方法指示 Laravel 在尝试填充不可填充属性时抛出异常。这可以帮助防止在本地开发期间尝试设置未添加到模型的 fillable
数组中的属性时出现意外错误:
Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());
检索模型
一旦您创建了模型和 其关联的数据库表,您就可以开始从数据库中检索数据。您可以将每个 Eloquent 模型视为一个强大的 查询构建器,允许您流畅地查询与模型关联的数据库表。模型的 all
方法将检索模型关联的数据库表中的所有记录: ...(about 1451 lines omitted)... 在上面的示例中将范围添加到 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 提供了一个 "高阶" 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();
比较模型
有时您可能需要确定两个模型是否 "相同"。is
和 isNot
方法可用于快速验证两个模型是否具有相同的主键、表和数据库连接:
if ($post->is($anotherPost)) {
// ...
}
if ($post->isNot($anotherPost)) {
// ...
}
在使用 belongsTo
、hasOne
、morphTo
和 morphOne
关系 时,is
和 isNot
方法也可用。当您希望在不发出查询以检索相关模型的情况下比较相关模型时,此方法特别有用:
if ($post->author()->is($user)) {
// ...
}
事件
想要将 Eloquent 事件直接广播到客户端应用程序?查看 Laravel 的 模型事件广播。
Eloquent 模型会调度多个事件,允许您在模型生命周期的以下时刻进行挂钩:retrieved
、creating
、created
、updating
、updated
、saving
、saved
、deleting
、deleted
、trashed
、forceDeleting
、forceDeleted
、restoring
、restored
和 replicating
。
当从数据库中检索现有模型时,将调度 retrieved
事件。当首次保存新模型时,将调度 creating
和 created
事件。当修改现有模型并调用 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 发出批量更新或删除查询时,不会为受影响的模型调度 saved
、updated
、deleting
和 deleted
模型事件。这是因为在执行批量更新或删除时,模型实际上从未被检索。
使用闭包
您可以注册在调度各种模型事件时执行的闭包,而不是使用自定义事件类。通常,您应在模型的 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
{
// ...
}
}
要注册观察者,您可以在相应的模型上放置 ObservedBy
属性:
use App\Observers\UserObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
#[ObservedBy([UserObserver::class])]
class User extends Authenticatable
{
//
}
或者,您可以通过调用要观察的模型上的 observe
方法手动注册观察者。您可以在应用程序的 App\Providers\EventServiceProvider
服务提供者的 boot
方法中注册观察者:
use App\Models\User;
use App\Observers\UserObserver;
/**
* 为应用程序注册任何事件。
*/
public function boot(): void
{
User::observe(UserObserver::class);
}
观察者可以监听其他事件,例如 saving
和 retrieved
。这些事件在 事件 文档中进行了描述。
观察者和数据库事务
当模型在数据库事务中创建时,您可能希望指示观察者仅在数据库事务提交后执行其事件处理程序。您可以通过在观察者上实现 ShouldHandleEventsAfterCommit
接口来实现此目的。如果数据库事务未进行中,事件处理程序将立即执行:
<?php
namespace App\Observers;
use App\Models\User;
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;
class UserObserver implements ShouldHandleEventsAfterCommit
{
/**
* 处理 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();