简介 Laravel 的 Eloquent ORM 提供了一个漂亮、简洁的 ActiveRecord 实现来和数据库交互。每个数据库表都有一个对应的「模型」用来与该表交互。你可以通过模型查询数据表中的数据,以及在数据表中插入新记录。 在开始之前,请确保在 config/database.php 中配置数据库连接。更多关于数据库配置的信息,请查看 文档。 模型定义 首先,创建一个 Eloquent 模型。 模型通常在 app\Models 目录中,但你可以根据 composer.json 文件将他们放置在可以被自动加载的任意位置。所有的 Eloquent 模型都需要继承 Illuminate\Database\Eloquent\Model 类。 创建模型最简单的方法就是使用 make:model Artisan 命令: ``` php artisan make:model Flight ``` 如果要在生成模型的时候生成 数据库迁移,可以使用 --migration 或 -m 选项: ``` php artisan make:model Flight --migration php artisan make:model Flight -m ``` 生成模型时,你可能需要生成各种其他类型的类,例如模型工厂、seeders 和 控制器。另外,这些选项可以组合在一起一次创建多个类: ``` php artisan make:model Flight --factory php artisan make:model Flight -f php artisan make:model Flight --seed php artisan make:model Flight -s php artisan make:model Flight --controller php artisan make:model Flight -c php artisan make:model Flight -mfsc ``` Eloquent 模型约定 现在,我们来看一个 Flight 模型的示例,我们将用它从 flights 数据库表中检索和存储数据信息: ``` <?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 假设主键是一个自增的整数值,这意味着默认情况下主键会自动转换为 int 类型。如果您希望使用非递增或非数字的主键则需要设置公共的 $incrementing 属性设置为 false: ``` <?php class Flight extends Model { /** * 主键是否主动递增 * * @var bool */ public $incrementing = false; } ``` 如果你的主键不是一个整数,你需要将模型上受保护的 $keyType 属性设置为 string: ``` <?php class Flight extends Model { /** * 自动递增主键的「类型」 * * @var string */ protected $keyType = 'string'; } ``` 时间戳 默认情况下,Eloquent 预期你的数据表中存在 created_at 和 updated_at 两个字段 。如果你不想让 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 = 'last_update'; } ``` 数据库连接 默认情况下,Eloquent 模型将使用你的应用程序配置的默认数据库连接。如果你想为模型指定一个不同的连接,设置 $connection 属性即可: ``` <?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model { /** * 模型的数据库连接名 * * @var string */ protected $connection = 'connection-name'; } ``` 默认属性值 如果要为模型的某些属性定义默认值,可以在模型上定义 $attributes 属性: ``` <?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model { /** * 模型属性的默认值 * * @var array */ protected $attributes = [ 'delayed' => false, ]; } ``` 模型检索 创建模型和 它关联的数据库表 后,你就可以从数据库中查询数据了。将每个 Eloquent 模型想象成一个强大的 查询构造器,你可以用它更快速的查询与其相关联的数据表。例如: ``` <?php $flights = App\Models\Flight::all(); foreach ($flights as $flight) { echo $flight->name; } ``` 附加约束 Eloquent 的 all 方法会返回模型中所有的结果。由于每个 Eloquent 模型都充当一个 查询构造器,所以你也可以添加查询条件,然后使用 get 方法获取查询结果: ``` $flights = App\Models\Flight::where('active', 1) ->orderBy('name', 'desc') ->take(10) ->get(); ``` 技巧:因为 Eloquent 模型也是查询构造器,所以你也应当阅读 查询构造器 可用的所有方法。你可以在 Eloquent 查询中使用这些方法。 重新加载模型 你可以使用 fresh 和 refresh 方法重新加载模型。 fresh 方法会重新从数据库中检索模型。现有的模型实例不受影响: ``` $flight = App\Models\Flight::where('number', 'FR 900')->first(); $freshFlight = $flight->fresh(); ``` refresh 方法使用数据库中的新数据重新赋值现有模型。此外,已经加载的关系会被重新加载: ``` $flight = App\Models\Flight::where('number', 'FR 900')->first(); $flight->number = 'FR 456'; $flight->refresh(); $flight->number; // "FR 900" ``` 集合 Eloquent 的 all 和 get 方法可以查询到多个结果,返回一个 Illuminate\Database\Eloquent\Collection 实例。Collection 类提供了 大量的辅助函数 来处理 Eloquent 结果: ``` $flights = $flights->reject(function ($flight) { return $flight->cancelled; }); ``` 你可以像数组一样遍历集合: ``` foreach ($flights as $flight) { echo $flight->name; } ``` 结果分块 如果你需要处理数以千计的 Eloquent 结果,使用 chunk 命令。 chunk 方法会检索 Eloquent 模型中的『分块』将他们提供给指定的 Closure 处理。在处理大型结果集时,使用 chunk 方法可以节省内存: ``` Flight::chunk(200, function ($flights) { foreach ($flights as $flight) { // } }); ``` 传递到方法的第一个参数是希望每个『分块』接收的数据量。闭包作为第二个参数传递,它在每次从数据库中检索分块的时候调用。它将执行数据库查询把检索分块的结果传递给闭包方法。 如果要基于迭代结果时也要更新的列筛选 chunk 方法的结果,则应使用 chunkById 方法。在这种情况下使用 chunk 方法可能会导致意外和不一致的结果: ``` Flight::where('departed', true)->chunkById(200, function ($flights) { $flights->each->update(['departed' => false]); }); ``` 使用游标 cursor 方法允许你使用游标遍历数据库,它只执行一次查询。处理大量的数据时, cursor 方法可以大大减少内存的使用量: ``` foreach (Flight::where('foo', 'bar')->cursor() as $flight) { // } cursor 返回 Illuminate\Support\LazyCollection 实例。 Lazy collections 允许你使用 Laravel 集合中大多数集合方法,而且每次只会加载单个模型到内存中: $users = App\Models\User::cursor()->filter(function ($user) { return $user->id > 500; }); foreach ($users as $user) { echo $user->id; } ``` 高级子查询 Selects 子查询 Eloquent 提供了高级子查询支持,你可以用单条查询语句从相关表中提取信息。举个例子,假设我们有一个目的地表 destinations 和一个到目的地的航班表 flights。flights 表包含一个 arrival_at 字段,表示航班何时到达目的地。 使用子查询功能提供的 select 和 addSelect 方法,我们可以用单条语句查询全部目的地 destinations,以及抵达各目的地最后一班飞机的名称: ``` use App\Models\Destination; use App\Models\Flight; return Destination::addSelect(['last_flight' => Flight::select('name') ->whereColumn('destination_id', 'destinations.id') ->orderBy('arrived_at', 'desc') ->limit(1) ])->get(); ``` 根据子查询进行排序 此外,查询构建器的 orderBy 函数也支持子查询。我们可以使用此功能根据最后一班航班到达目的地的时间对所有目的地排序。 同样,这可以只对数据库执行单个查询: ``` return Destination::orderByDesc( Flight::select('arrived_at') ->whereColumn('destination_id', 'destinations.id') ->orderBy('arrived_at', 'desc') ->limit(1) )->get(); ``` 检索单个模型 / 集合 除了从指定的数据表检索所有记录外,你可以使用 find、 first 或 firstWhere 方法来检索单条记录。这些方法返回单个模型实例,而不是返回模型集合: ``` // 通过主键查找一个模型... $flight = App\Models\Flight::find(1); // 查找符合查询条件的首个模型... $flight = App\Models\Flight::where('active', 1)->first(); // 查找符合查询条件的首个模型的快速实现... $flight = App\Models\Flight::firstWhere('active', 1); ``` 你也可以使用主键数组作为参数调用 find 方法,它将返回匹配记录的集合: ``` $flights = App\Models\Flight::find([1, 2, 3]); ``` 有时你可能希望在查找首个结果但找不到值时执行其他动作。firstOr 方法将会在查找到结果时返回首个结果,如果没有结果,将会执行给定的回调。回调的返回值将会作为 firstOr 方法的返回值: ``` $model = App\Models\Flight::where('legs', '>', 100)->firstOr(function () { // ... }); ``` firstOr 方法同样接受数据库列的数组来查询: ``` $model = App\Models\Flight::where('legs', '>', 100) ->firstOr(['id', 'legs'], function () { // ... }); ``` 「未找到」异常 有时你希望在未找到模型时抛出异常。这在控制器和路由中非常有用。 findOrFail 和 firstOrFail 方法会检索查询的第一个结果,如果未找到,将抛出 Illuminate\Database\Eloquent\ModelNotFoundException 异常: ``` $model = App\Models\Flight::findOrFail(1); $model = App\Models\Flight::where('legs', '>', 100)->firstOrFail(); ``` 如果没有捕获异常,则会自动返回 404 响应给用户。也就是说,使用这些方法时,没有必要再写个检查来返回 404 响应: ``` Route::get('/api/flights/{id}', function ($id) { return App\Models\Flight::findOrFail($id); }); ``` 检索集合 你还可以使用 查询构造器提供的 count、 sum 和 max 方法,和其他的 集合函数 来操作集合。这些方法只会返回适当的标量值而不是一个模型实例: ``` $count = App\Models\Flight::where('active', 1)->count(); $max = App\Models\Flight::where('active', 1)->max('price'); ``` 插入及更新模型 插入 要往数据库新增一条记录,先创建新模型实例,给实例设置属性,然后调用 save 方法: ``` <?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; use App\Models\Flight; use Illuminate\Http\Request; class FlightController extends Controller { /** * 创建一个新实例 * * @param Request $request * @return Response */ public function store(Request $request) { // 验证请求 $flight = new Flight; $flight->name = $request->name; $flight->save(); } } ``` 在这个示例中,我们将 HTTP 请求参数 name 赋值给了 App\Flight 模型实例的 name 属性。当调用 save 方法时,将会插入一条新记录。 created_at 和 updated_at 时间戳将会自动设置,不需要手动赋值。 更新 save 方法也可以用来更新数据库已经存在的模型。更新模型,你需要先检索出来,设置要更新的属性,然后调用 save 方法。同样,updated_at 时间戳会自动更新,所以也不需要手动赋值: ``` $flight = App\Models\Flight::find(1); $flight->name = 'New Flight Name'; $flight->save(); ``` 批量更新 也可以更新匹配查询条件的多个模型。在这个示例中,所有的 active 等于 1 和 destination 为 San Diego 的航班会标记为延误: ``` App\Models\Flight::where('active', 1) ->where('destination', 'San Diego') ->update(['delayed' => 1]); ``` update 方法需要一个键为字段名和值为对应数据的数组,该数组代表应更新的字段。 注意:通过 Eloquent 批量更新时,更新的模型不会触发 saving, saved, updating 和 updated 模型事件。这是因为在批量更新时实际上从未检索模型。 检查属性变化 Eloquent 提供了 isDirty, isClean 和 wasChanged 方法,以检查模型的内部状态并确定其属性从最初加载时如何变化。 isDirty 方法确定自加载模型以来是否已更改任何属性。 您可以传递特定的属性名称来确定特定的属性是否变脏。isClean 方法与 isDirty 相反,它也接受可选的属性参数: ``` $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->isClean(); // false $user->isClean('title'); // false $user->isClean('first_name'); // true $user->save(); $user->isDirty(); // false $user->isClean(); // true wasChanged 方法确定在当前请求周期内最后一次保存模型时是否更改了任何属性。 你还可以传递属性名称以查看特定属性是否已更改: $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('first_name'); // false getOriginal 方法返回一个包含模型原始属性的数组,忽略加载模型之后进行的任何更改。 您可以传递特定的属性名称来获取特定属性的原始值: $user = User::find(1); $user->name; // John $user->email; // john@example.com $user->name = "Jack"; $user->name; // Jack $user->getOriginal('name'); // John $user->getOriginal(); // 原始属性数组 ``` 批量赋值 你也可以使用 create 方法来保存新模型。 此方法会返回模型实例。 不过,在使用之前,你需要在模型上指定 fillable 或 guarded 属性,因为所有的 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 = App\Models\Flight::create(['name' => 'Flight 10']); ``` 如果你已经有一个模型实例,你可以传递一个数组给 fill 方法来赋值: ``` $flight->fill(['name' => 'Flight 22']); ``` 批量赋值 & JSON 列 赋值 JSON 列时,必须在模型的 $fillable 数组中指定每个列的可赋值键。为了安全起见,在使用 guarded 属性时,Laravel 不支持更新嵌套的 JSON 属性: ``` /** * 可批量赋值的属性。 * * @var array */ $fillable = [ 'options->enabled', ]; ``` 允许批量赋值 如果你想让所有属性都可以批量赋值, 你可以将 $guarded 定义成一个空数组: ``` /** * 不可批量赋值的属性 * * @var array */ protected $guarded = []; ``` 其他创建方法 ``` firstOrCreate/ firstOrNew ``` 这里有两个你可能用来批量赋值的方法: firstOrCreate 和 firstOrNew。 firstOrCreate 方法会通过给定的键 / 值对来匹配数据库中的数据。如果在数据库中找不到模型,则将插入一条记录,其中包含第一个参数的属性以及可选的第二个参数的属性。 ``` firstOrNew 方法像 firstOrCreate 方法一样尝试通过给定的属性查找数据库中的记录。不过,如果 firstOrNew 方法找不到对应的模型,会返回一个新的模型实例。注意 firstOrNew 返回的模型实例尚未保存到数据库中,你需要手动调用 save 方法来保存: // 通过 name 检索航班,不存在则创建新模型并插入数据库... $flight = App\Models\Flight::firstOrCreate(['name' => 'Flight 10']); // 通过 name 检索航班,或使用 name 和 delayed 属性和 arrival_time 属性创建新模型并插入数据库... $flight = App\Models\Flight::firstOrCreate( ['name' => 'Flight 10'], ['delayed' => 1, 'arrival_time' => '11:30'] ); // 通过 name 检索航班,不存在则返回的模型实例尚未保存到数据库... $flight = App\Models\Flight::firstOrNew(['name' => 'Flight 10']); // 通过 name 检索航班,或使用 name 和 delayed 属性和 arrival_time 属性返回的模型实例尚未保存到数据库... $flight = App\Models\Flight::firstOrNew( ['name' => 'Flight 10'], ['delayed' => 1, 'arrival_time' => '11:30'] ); updateOrCreate ``` 你还可能遇到希望更新现有模型或在不存在的情况下则创建新的模型的情景。 Laravel 提供 updateOrCreate 方法来一步实现。 类似于 firstOrCreate 方法,updateOrCreate 持久化模型,因此无需调用 save(): ``` // 如果有从奥克兰到圣地亚哥的航班,则价格定为 99 美元... // 如果没匹配到存在的模型,则创建一个... $flight = App\Models\Flight::updateOrCreate( ['departure' => 'Oakland', 'destination' => 'San Diego'], ['price' => 99, 'discounted' => 1] ); ``` 如果你想在单次查询中执行多个 upsert,那么应该使用 upsert 方法。该方法的第一个参数是由要插入或更新的值组成,而第二个参数列出相应表中惟一标识记录的列,该方法的第三个也是最后一个参数是一个列数组,即如果数据库中已经存在匹配的记录,应该被更新的列。如果模型上启用了时间戳,upsert 方法将自动设置 created_at 和 updated_at 时间戳: ``` App\Models\Flight::upsert([ ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99], ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150] ], ['departure', 'destination'], ['price']); ``` 注意:除 SQL Server 之外的所有数据库都要求 upsert 方法的第二个参数中的列具有 “primary” 或 “unique” 索引。 删除模型 可以在模型实例上调用 delete 方法来删除实例: ``` $flight = App\Models\Flight::find(1); $flight->delete(); ``` 通过主键删除模型 在上面的例子中,在调用 delete 之前需要先去数据库中查找对应的模型。事实上,如果你知道了模型的主键,你可以直接使用 destroy 方法来删除模型,而不用先去数据库中查找。destroy 方法除了接受单个主键作为参数之外,还接受多个主键,或者使用数组、集合来保存多个主键: ``` App\Models\Flight::destroy(1); App\Models\Flight::destroy(1, 2, 3); App\Models\Flight::destroy([1, 2, 3]); App\Models\Flight::destroy(collect([1, 2, 3])); ``` 注意:destroy 会方法分别加载每个模型,并在其上调用 delete 方法,以便触发 deleting 和 deleted 事件。 通过查询删除模型 你也可以在模型上运行删除语句。在这个例子中,我们将删除所有标记为非活跃的航班。与批量更新一样,批量删除不会为删除的模型启动任何模型事件: ``` $deletedRows = App\Models\Flight::where('active', 0)->delete(); ``` 注意:通过 Eloquent 执行批量删除语句时,不会触发 deleting 和 deleted 模型事件。因此,在执行删除语句时,从不检索模型示例。 软删除 除了真实删除数据库记录,Eloquent 也可以「软删除」模型。软删除的模型并不是真的从数据库中删除了。 事实上,是在模型上设置了 deleted_at 属性并将其值写入数据库。如果 deleted_at 值非空,代表这个模型已被软删除。如果要开启模型软删除功能,你需要在模型上使用 Illuminate\Database\Eloquent\SoftDeletes trait: ``` <?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 的 数据迁移 有创建这个字段的方法: ``` public function up() { Schema::table('flights', function (Blueprint $table) { $table->softDeletes(); }); } public function down() { Schema::table('flights', function (Blueprint $table) { $table->dropSoftDeletes(); }); } ``` 那现在,当你在模型实例上使用 delete 方法,当前日期时间会写入 deleted_at 字段。同时,查询出来的结果也会自动排除已被软删除的记录。 你可以使用 trashed 方法来验证给定的模型实例是否已被软删除: ``` if ($flight->trashed()) { // } ``` 查询软删除模型 包括已软删除的模型 前面提到,查询结果会自动剔除已被软删除的结果。当然,你可以使用 withTrashed 方法来获取包括软删除模型在内的模型: ``` $flights = App\Models\Flight::withTrashed() ->where('account_id', 1) ->get(); ``` withTrashed 方法也可以用在 关联 查询: ``` $flight->history()->withTrashed()->get(); ``` 只检索软删除模型 onlyTrashed 方法 只 获取已软删除的模型: ``` $flights = App\Models\Flight::onlyTrashed() ->where('airline_id', 1) ->get(); ``` 恢复软删除模型 有时会对软删除模型进行「撤销」,在已软删除的数据上使用 restore 方法即可恢复到有效状态: ``` $flight->restore(); ``` 你也可以在查询中使用 restore 方法,从而快速恢复多个模型。和其他「批量」操作一样,这个操作不会触发模型的任何事件: ``` App\Models\Flight::withTrashed() ->where('airline_id', 1) ->restore(); ``` 类似 withTrashed 方法,restore 方法也用在 关联上: ``` $flight->history()->restore(); ``` 永久删除 有时你可能需要从数据库中真正删除模型。要从数据库中永久删除软删除的模型,请使用 forceDelete 方法: ``` //强制删除单个模型实例... $flight->forceDelete(); // 强制删除所有关联模型... $flight->history()->forceDelete(); ``` 复制模型 您可以使用 replicate 方法复制一个新的未保存到数据库的实例, 当模型实例共享许多相同的属性时,这个方法非常好用。 ``` $shipping = App\Models\Address::create([ 'type' => 'shipping', 'line_1' => '123 Example Street', 'city' => 'Victorville', 'state' => 'CA', 'postcode' => '90001', ]); $billing = $shipping->replicate()->fill([ 'type' => 'billing' ]); $billing->save(); ``` 查询作用域 全局作用域 全局作用域可以给模型的查询都添加上约束。Laravel 的 软删除 功能就是利用此特性从数据库中获取 「未删除」的模型。你可以编写你自己的全局作用域,很简单、方便的为每个模型查询都加上约束条件: 编写全局作用域 编写全局作用域很简单。定义一个实现 Illuminate\Database\Eloquent\Scope 接口的类,并实现 apply 这个方法。根据你的需求,在 apply 方法中加入查询的 where 条件: ``` <?php namespace App\Scopes; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; class AgeScope implements Scope { /** * 把约束加到 Eloquent 查询构造中 * * @param \Illuminate\Database\Eloquent\Builder $builder * @param \Illuminate\Database\Eloquent\Model $model * @return void */ public function apply(Builder $builder, Model $model) { $builder->where('age', '>', 200); } } ``` 技巧:如果你需要在 select 语句里添加字段,应使用 addSelect 方法,而不是 select 方法。这将有效防止无意中替换现有 select 语句的情况。 应用全局作用域 要将全局作用域分配给模型,需要重写模型的 booted 方法并使用 addGlobalScope 方法: ``` <?php namespace App\Models; use App\Scopes\AgeScope; use Illuminate\Database\Eloquent\Model; class User extends Model { /** * 模型的 "booted" 方法 * * @return void */ protected static function booted() { static::addGlobalScope(new AgeScope); } } ``` 添加作用域后,对 User::all() 的查询会生成以下 SQL 查询语句: ``` select * from `users` where `age` > 200 ``` 匿名全局作用域 Eloquent 同样允许使用闭包定义全局作用域,这样就不需要为一个简单的作用域而编写一个单独的类: ``` <?php namespace App\Models; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; class User extends Model { /** * 模型的 "booted" 方法 * * @return void */ protected static function booted() { static::addGlobalScope('age', function (Builder $builder) { $builder->where('age', '>', 200); }); } } ``` 取消全局作用域 如果需要对当前查询取消全局作用域,需要使用 withoutGlobalScope 方法。 该方法仅接受全局作用域类名作为它唯一的参数: ``` User::withoutGlobalScope(AgeScope::class)->get(); ``` 或者,如果使用闭包定义全局作用域的话: ``` User::withoutGlobalScope('age')->get(); ``` 如果你需要取消部分或者全部的全局作用域的话,需要使用 withoutGlobalScopes 方法: ``` // 取消所有的全局作用域... User::withoutGlobalScopes()->get(); // 取消部分全局作用域... User::withoutGlobalScopes([ FirstScope::class, SecondScope::class ])->get(); ``` 局部作用域 局部作用域允许定义通用的约束集合以便在应用程序中重复使用。例如,你可能经常需要获取所有「流行」的用户。要定义这样一个范围,只需要在对应的 Eloquent 模型方法前添加 scope 前缀。 作用域总是返回一个查询构造器实例: ``` <?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { /** *只查询受欢迎的用户的作用域 * * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ public function scopePopular($query) { return $query->where('votes', '>', 100); } /** * 只查询 active 用户的作用域 * * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ public function scopeActive($query) { return $query->where('active', 1); } } ``` 使用本地作用域 一旦定义了作用域,就可以在查询该模型时调用作用域方法。不过,在调用这些方法时不必包含 scope 前缀。甚至可以链式调用多个作用域,例如: ``` $users = App\Models\User::popular()->active()->orderBy('created_at')->get(); ``` 借助 or 查询运行符整合多个 Eloquent 模型,可能需要使用闭包回调: ``` $users = App\Models\User::popular()->orWhere(function (Builder $query) { $query->active(); })->get(); ``` 因为这样可能会有点麻烦,Laravel 提供了「更高阶的」orWhere 方法,它允许你在链式调用作用域时不使用闭包: ``` $users = App\Models\User::popular()->orWhere->active()->get(); ``` 动态作用域 有时可能地希望定义一个可以接受参数的作用域。把额外参数传递给作用域就可以达到此目的。作用域参数要放在 $query 参数之后: ``` <?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { /** *将查询作用域限制为仅包含给定类型的用户 * * @param \Illuminate\Database\Eloquent\Builder $query * @param mixed $type * @return \Illuminate\Database\Eloquent\Builder */ public function scopeOfType($query, $type) { return $query->where('type', $type); } } ``` 这样就可以在调用作用域时传递参数了: ``` $users = App\Models\User::ofType('admin')->get(); ``` 模型比较 有时可能需要判断两个模型是否「相同」。is 方法可以用来快速校验两个模型是否拥有相同的主键、表和数据库连接: ``` if ($post->is($anotherPost)) { // } 当使用 belongsTo、hasOne、morphTo 和 morphOne 关系时,is 方法也可用。当你想要比较一个相关的模型而不需要发出检索该模型的查询时,这个方法特别有用: if ($post->author()->is($user)) { // } ``` 事件 Eloquent 模型触发几个事件,允许你挂接到模型生命周期的如下节点: retrieved、creating、created、updating、updated、saving、saved、deleting、deleted、restoring、restored、replicating。事件允许你每当特定模型保存或更新数据库时执行代码。每个事件通过其构造器接受模型实例。 retrieved 事件在现有模型从数据库中查找数据时触发。当新模型每一次保存时,creating 和 created 事件被触发。如果数据库中已经存在模型并且调用了 save 方法,updating / updated 事件被触发。这些情况下,saving / saved 事件也被触发。 注意:通过 Eloquent 进行批量更新时,被更新模型的 saved 和 updated, deleting 和 deleted 事件不会被触发。这是因为批量更新时,并没有真的获取模型。 首先,在 Eloquent 模型上定义一个 $dispatchesEvents 属性,将 Eloquent 模型生命周期的几个节点映射到你自己的 event 类: ``` <?php namespace App\Models; use App\Events\UserDeleted; use App\Events\UserSaved; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use Notifiable; /** * 模型的事件映射 * * @var array */ protected $dispatchesEvents = [ 'saved' => UserSaved::class, 'deleted' => UserDeleted::class, ]; } ``` 定义并且映射了 Eloquent 事件,就可以使用 事件监听处理这些事件了。 使用闭包 你可以注册在触发各种模型事件时执行的闭包,而不使用自定义事件类。 通常,你应该在模型的 booted 方法中注册这些闭包: ``` <?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { /** * 模型的 "booted" 方法 * * @return void */ protected static function booted() { static::created(function ($user) { // }); } } ``` 如果需要,可以在注册模型事件时利用 队列匿名事件侦听器 。 这将指示 Laravel 使用 queue 执行模型事件侦听器: ``` use function Illuminate\Events\queueable; static::created(queueable(function ($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」事件 * * @param \App\Models\User $user * @return void */ public function created(User $user) { // } /** * 处理 User「updated」事件 * * @param \App\Models\User $user * @return void */ public function updated(User $user) { // } /** * 处理 User「deleted」事件 * * @param \App\Models\User $user * @return void */ public function deleted(User $user) { // } /** * 处理 User「forceDeleted」事件 * * @param \App\Models\User $user * @return void */ public function forceDeleted(User $user) { // } } ``` 要注册观察者,您需要在要观察的模型上调用 observe 方法。您可以在应用程序的 App\Providers\EventServiceProvider 服务提供者的 boot 方法中注册观察者:: ``` use App\Models\User; use App\Observers\UserObserver; /** * 为您的应用程序注册任何事件。 * * @return void */ public function boot() { User::observe(UserObserver::class); } ``` 禁用事件 您可能偶尔希望暂时「禁用」模型触发的所有事件。 您可以使用 withoutEvents 方法来实现。 withoutEvents 方法接受闭包作为唯一的参数。 在此闭包中执行的任何代码都不会触发模型事件。 例如,以下代码将获取并删除一个 App\Models\User 实例,而不会触发任何模型事件。 给定闭包返回的任何值都将通过 withoutEvents 方法返回: ``` use App\Models\User; $user = User::withoutEvents(function () use () { User::findOrFail(1)->delete(); return User::find(2); }); ``` 无事件的单个模型保存 有时,您可能希望「保存」给定的模型而不会触发任何事件。 您可以使用 saveQuietly 方法完成此操作: ``` $user = User::findOrFail(1); $user->name = 'Victoria Faith'; $user->saveQuietly(); ```