Skip to content

Eloquent:入门

介绍

Laravel 附带的 Eloquent ORM 提供了一种美观、简单的 ActiveRecord 实现,用于与数据库交互。每个数据库表都有一个对应的“模型”,用于与该表交互。模型允许您查询表中的数据,以及向表中插入新记录。

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

定义模型

要开始,请创建一个 Eloquent 模型。模型通常位于 app 目录中,但您可以将它们放置在 composer.json 文件中可以自动加载的任何位置。所有 Eloquent 模型都扩展 Illuminate\Database\Eloquent\Model 类。

创建模型实例的最简单方法是使用 make:model Artisan 命令

php
php artisan make:model Flight

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

php
php artisan make:model Flight --migration

php artisan make:model Flight -m

Eloquent 模型约定

现在,让我们看一个 Flight 模型的示例,我们将使用它从 flights 数据库表中检索和存储信息:

php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    //
}

表名

请注意,我们没有告诉 Eloquent 使用哪个表来存储 Flight 模型。按照约定,类的“蛇形命名法”复数名称将用作表名,除非显式指定了其他名称。因此,在这种情况下,Eloquent 将假定 Flight 模型存储在 flights 表中。您可以通过在模型上定义 table 属性来指定自定义表:

php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * 与模型关联的表。
     *
     * @var string
     */
    protected $table = 'my_flights';
}

主键

Eloquent 还假定每个表都有一个名为 id 的主键列。您可以定义一个受保护的 $primaryKey 属性来覆盖此约定:

php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * 与表关联的主键。
     *
     * @var string
     */
    protected $primaryKey = 'flight_id';
}

此外,Eloquent 假定主键是一个递增的整数值,这意味着默认情况下主键将自动转换为 int。如果您希望使用非递增或非数字的主键,必须将模型上的公共 $incrementing 属性设置为 false

php
<?php

class Flight extends Model
{
    /**
     * 指示 ID 是否自动递增。
     *
     * @var bool
     */
    public $incrementing = false;
}

如果您的主键不是整数,您应该将模型上的受保护 $keyType 属性设置为 string

php
<?php

class Flight extends Model
{
    /**
     * 自动递增 ID 的“类型”。
     *
     * @var string
     */
    protected $keyType = 'string';
}

时间戳

默认情况下,Eloquent 期望表中存在 created_atupdated_at 列。如果您不希望 Eloquent 自动管理这些列,请将模型上的 $timestamps 属性设置为 false

php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * 指示模型是否应被时间戳。
     *
     * @var bool
     */
    public $timestamps = false;
}

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

php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * 模型日期列的存储格式。
     *
     * @var string
     */
    protected $dateFormat = 'U';
}

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

php
<?php

class Flight extends Model
{
    const CREATED_AT = 'creation_date';
    const UPDATED_AT = 'last_update';
}

数据库连接

默认情况下,所有 Eloquent 模型将使用为应用程序配置的默认数据库连接。如果您希望为模型指定不同的连接,请使用 $connection 属性:

php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * 模型的连接名称。
     *
     * @var string
     */
    protected $connection = 'connection-name';
}

默认属性值

如果您希望为模型的某些属性定义默认值,可以在模型上定义一个 $attributes 属性:

php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * 模型的默认属性值。
     *
     * @var array
     */
    protected $attributes = [
        'delayed' => false,
    ];
}

检索模型

一旦创建了模型和其关联的数据库表,您就可以开始从数据库中检索数据。将每个 Eloquent 模型视为一个强大的查询构建器,允许您流畅地查询与模型关联的数据库表。例如:

php
<?php

$flights = App\Flight::all();

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

添加额外的约束

Eloquent 的 all 方法将返回模型表中的所有结果。由于每个 Eloquent 模型都充当查询构建器,您还可以向查询添加约束,然后使用 get 方法检索结果:

php
$flights = App\Flight::where('active', 1)
               ->orderBy('name', 'desc')
               ->take(10)
               ->get();
lightbulb

由于 Eloquent 模型是查询构建器,您应该查看查询构建器上可用的所有方法。您可以在 Eloquent 查询中使用这些方法。

刷新模型

您可以使用 freshrefresh 方法刷新模型。fresh 方法将重新从数据库中检索模型。现有的模型实例不会受到影响:

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

$freshFlight = $flight->fresh();

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

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

$flight->number = 'FR 456';

$flight->refresh();

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

集合

对于像 allget 这样检索多个结果的 Eloquent 方法,将返回 Illuminate\Database\Eloquent\Collection 的实例。Collection 类提供了多种有用的方法来处理您的 Eloquent 结果:

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

您还可以像数组一样遍历集合:

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

分块结果

如果您需要处理成千上万的 Eloquent 记录,请使用 chunk 命令。chunk 方法将检索一块 Eloquent 模型,并将它们传递给给定的 Closure 进行处理。使用 chunk 方法可以在处理大型结果集时节省内存:

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

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

使用游标

cursor 方法允许您使用游标遍历数据库记录,这将只执行一个查询。在处理大量数据时,cursor 方法可以大大减少内存使用:

php
foreach (Flight::where('foo', 'bar')->cursor() as $flight) {
    //
}

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

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

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

高级子查询

子查询选择

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

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

php
use App\Destination;
use App\Flight;

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

子查询排序

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

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

检索单个模型/聚合

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

php
// 根据主键检索模型...
$flight = App\Flight::find(1);

// 检索符合查询约束的第一个模型...
$flight = App\Flight::where('active', 1)->first();

// 检索符合查询约束的第一个模型的简写...
$flight = App\Flight::firstWhere('active', 1);

您还可以使用主键数组调用 find 方法,这将返回匹配记录的集合:

php
$flights = App\Flight::find([1, 2, 3]);

有时您可能希望检索查询的第一个结果或在未找到结果时执行其他操作。firstOr 方法将返回找到的第一个结果,或者如果未找到结果,则执行给定的回调。回调的结果将被视为 firstOr 方法的结果:

php
$model = App\Flight::where('legs', '>', 100)->firstOr(function () {
        // ...
});

firstOr 方法还接受要检索的列数组:

php
$model = App\Flight::where('legs', '>', 100)
            ->firstOr(['id', 'legs'], function () {
                // ...
            });

未找到异常

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

php
$model = App\Flight::findOrFail(1);

$model = App\Flight::where('legs', '>', 100)->firstOrFail();

如果未捕获异常,将自动向用户发送 404 HTTP 响应。在使用这些方法时,不需要编写显式检查以返回 404 响应:

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

检索聚合

您还可以使用查询构建器提供的 countsummax 和其他聚合方法。这些方法返回适当的标量值,而不是完整的模型实例:

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

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

插入和更新模型

插入

要在数据库中创建新记录,请创建一个新的模型实例,在模型上设置属性,然后调用 save 方法:

php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\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 方法时,将在数据库中插入一条记录。调用 save 方法时,created_atupdated_at 时间戳将自动设置,因此无需手动设置它们。

更新

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

php
$flight = App\Flight::find(1);

$flight->name = 'New Flight Name';

$flight->save();

批量更新

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

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

update 方法期望一个表示应更新的列的列和值对数组。

exclamation

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

检查属性更改

Eloquent 提供了 isDirtyisCleanwasChanged 方法来检查模型的内部状态,并确定其属性自加载以来如何更改。

isDirty 方法确定自模型加载以来是否更改了任何属性。您可以传递特定的属性名称以确定特定属性是否已更改。isClean 方法是 isDirty 的反义词,也接受可选的属性参数:

php
$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 方法确定在当前请求周期内最后一次保存模型时是否更改了任何属性。您还可以传递属性名称以查看特定属性是否已更改:

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('first_name'); // false

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(); // 原始属性数组...

批量赋值

您还可以使用 create 方法在一行中保存新模型。插入的模型实例将从方法中返回给您。但是,在这样做之前,您需要在模型上指定 fillableguarded 属性,因为所有 Eloquent 模型默认情况下都防止批量赋值。

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

因此,要开始,您应该定义要批量赋值的模型属性。您可以使用模型上的 $fillable 属性来执行此操作。例如,让我们使 Flight 模型的 name 属性可批量赋值:

php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * 可批量赋值的属性。
     *
     * @var array
     */
    protected $fillable = ['name'];
}

一旦我们使属性可批量赋值,我们就可以使用 create 方法在数据库中插入新记录。create 方法返回保存的模型实例:

php
$flight = App\Flight::create(['name' => 'Flight 10']);

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

php
$flight->fill(['name' => 'Flight 22']);

允许批量赋值

如果您希望使所有属性可批量赋值,可以将 $guarded 属性定义为空数组:

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

其他创建方法

firstOrCreate/ firstOrNew

您还可以使用两种其他方法通过批量赋值属性来创建模型:firstOrCreatefirstOrNewfirstOrCreate 方法将尝试使用给定的列/值对定位数据库记录。如果在数据库中找不到模型,将使用第一个参数中的属性插入一条记录,以及可选的第二个参数中的属性。

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

php
// 根据名称检索航班,如果不存在则创建它...
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);

// 根据名称检索航班,或使用名称、延迟和到达时间属性创建它...
$flight = App\Flight::firstOrCreate(
    ['name' => 'Flight 10'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);

// 根据名称检索,或实例化...
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);

// 根据名称检索,或使用名称、延迟和到达时间属性实例化...
$flight = App\Flight::firstOrNew(
    ['name' => 'Flight 10'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);

updateOrCreate

您可能还会遇到需要更新现有模型或在不存在时创建新模型的情况。Laravel 提供了一个 updateOrCreate 方法来一步完成此操作。与 firstOrCreate 方法一样,updateOrCreate 持久化模型,因此无需调用 save()

php
// 如果有从奥克兰到圣地亚哥的航班,将价格设置为 $99。
// 如果不存在匹配的模型,则创建一个。
$flight = App\Flight::updateOrCreate(
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
    ['price' => 99, 'discounted' => 1]
);

删除模型

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

php
$flight = App\Flight::find(1);

$flight->delete();

根据键删除现有模型

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

php
App\Flight::destroy(1);

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

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

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

destroy 方法会单独加载每个模型并在其上调用 delete 方法,以便触发 deletingdeleted 事件。

通过查询删除模型

您还可以对一组模型运行删除语句。在此示例中,我们将删除所有标记为非活动的航班。与批量更新一样,批量删除不会为删除的模型触发任何模型事件:

php
$deletedRows = App\Flight::where('active', 0)->delete();
exclamation

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

软删除

除了实际从数据库中删除记录外,Eloquent 还可以“软删除”模型。当模型被软删除时,它们实际上并没有从数据库中删除。相反,deleted_at 属性被设置在模型上并插入到数据库中。如果模型具有非空的 deleted_at 值,则该模型已被软删除。要为模型启用软删除,请在模型上使用 Illuminate\Database\Eloquent\SoftDeletes trait:

php
<?php

namespace App;

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

class Flight extends Model
{
    use SoftDeletes;
}
lightbulb

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

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

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

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

查询软删除模型

包含软删除模型

如上所述,软删除的模型将自动从查询结果中排除。但是,您可以使用查询上的 withTrashed 方法强制软删除的模型出现在结果集中:

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

withTrashed 方法也可以在关系查询上使用:

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

仅检索软删除模型

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

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

恢复软删除模型

有时您可能希望“取消删除”软删除的模型。要将软删除的模型恢复到活动状态,请在模型实例上使用 restore 方法:

php
$flight->restore();

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

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

withTrashed 方法一样,restore 方法也可以在关系上使用:

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

永久删除模型

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

php
// 强制删除单个模型实例...
$flight->forceDelete();

// 强制删除所有相关模型...
$flight->history()->forceDelete();

复制模型

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

php
$shipping = App\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 接口的类。此接口要求您实现一个方法:applyapply 方法可以根据需要向查询添加 where 约束:

php
<?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);
    }
}
lightbulb

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

应用全局范围

要将全局范围分配给模型,您应该覆盖给定模型的 booted 方法并使用 addGlobalScope 方法:

php
<?php

namespace App;

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:

php
select * from `users` where `age` > 200

匿名全局范围

Eloquent 还允许您使用闭包定义全局范围,这对于不需要单独类的简单范围特别有用:

php
<?php

namespace App;

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 方法。该方法接受全局范围的类名作为其唯一参数:

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

或者,如果您使用闭包定义了全局范围:

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

如果您希望移除多个甚至所有全局范围,可以使用 withoutGlobalScopes 方法:

php
// 移除所有全局范围...
User::withoutGlobalScopes()->get();

// 移除某些全局范围...
User::withoutGlobalScopes([
    FirstScope::class, SecondScope::class
])->get();

本地范围

本地范围允许您定义可以在整个应用程序中轻松重用的常见约束集。例如,您可能需要频繁检索所有被认为是“受欢迎”的用户。要定义范围,请在 Eloquent 模型方法前加上 scope

范围应始终返回查询构建器实例:

php
<?php

namespace App;

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);
    }

    /**
     * 将查询范围限制为仅包含活跃用户。
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeActive($query)
    {
        return $query->where('active', 1);
    }
}

使用本地范围

定义范围后,您可以在查询模型时调用范围方法。但是,调用方法时不应包含 scope 前缀。您甚至可以将对各种范围的调用链接在一起,例如:

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

通过 or 查询运算符组合多个 Eloquent 模型范围可能需要使用闭包回调:

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

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

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

动态范围

有时您可能希望定义一个接受参数的范围。要开始,只需将额外的参数添加到范围中。范围参数应在 $query 参数之后定义:

php
<?php

namespace App;

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);
    }
}

现在,您可以在调用范围时传递参数:

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

比较模型

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

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

事件

Eloquent 模型触发多个事件,允许您在模型生命周期的以下点挂钩:retrievedcreatingcreatedupdatingupdatedsavingsaveddeletingdeletedrestoringrestored。事件允许您在每次特定模型类在数据库中保存或更新时轻松执行代码。每个事件通过其构造函数接收模型的实例。

retrieved 事件将在从数据库中检索现有模型时触发。当首次保存新模型时,将触发 creatingcreated 事件。updating / updated 事件将在修改现有模型并调用 save 方法时触发。saving / saved 事件将在创建或更新模型时触发。

exclamation

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

要开始,请在 Eloquent 模型上定义一个 $dispatchesEvents 属性,该属性将 Eloquent 模型生命周期的各个点映射到您自己的事件类

php
<?php

namespace App;

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
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * 模型的“booted”方法。
     *
     * @return void
     */
    protected static function booted()
    {
        static::created(function ($user) {
            //
        });
    }
}

观察者

定义观察者

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

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

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

php
<?php

namespace App\Observers;

use App\User;

class UserObserver
{
    /**
     * 处理用户“created”事件。
     *
     * @param  \App\User  $user
     * @return void
     */
    public function created(User $user)
    {
        //
    }

    /**
     * 处理用户“updated”事件。
     *
     * @param  \App\User  $user
     * @return void
     */
    public function updated(User $user)
    {
        //
    }

    /**
     * 处理用户“deleted”事件。
     *
     * @param  \App\User  $user
     * @return void
     */
    public function deleted(User $user)
    {
        //
    }

    /**
     * 处理用户“forceDeleted”事件。
     *
     * @param  \App\User  $user
     * @return void
     */
    public function forceDeleted(User $user)
    {
        //
    }
}

要注册观察者,请使用您希望观察的模型上的 observe 方法。您可以在服务提供者的 boot 方法中注册观察者。在此示例中,我们将在 AppServiceProvider 中注册观察者:

php
<?php

namespace App\Providers;

use App\Observers\UserObserver;
use App\User;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * 注册任何应用程序服务。
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * 启动任何应用程序服务。
     *
     * @return void
     */
    public function boot()
    {
        User::observe(UserObserver::class);
    }
}

静音事件

您可能偶尔希望暂时“静音”模型触发的所有事件。您可以使用 withoutEvents 方法实现此目的。withoutEvents 方法接受一个闭包作为其唯一参数。在此闭包中执行的任何代码都不会触发模型事件。例如,以下将获取并删除一个 App\User 实例,而不会触发任何模型事件。给定闭包返回的任何值都将由 withoutEvents 方法返回:

php
use App\User;

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

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