翻译进度
32
分块数量
4
参与人数

快速入门

这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。


Eloquent: 快速入门

简介

Laravel 包含的 Eloquent 模块,是一个对象关系映射 (ORM),能使你更愉快地交互数据库。当你使用 Eloquent 时,数据库中每张表都有一个相对应的” 模型” 用于操作这张表。除了能从数据表中检索数据记录之外,Eloquent 模型同时也允许你新增,更新和删除这对应表中的数据。

[!注意]
在开始之前,请务必在应用程序的 config/database.php 配置文件中配置数据库连接。有关配置数据库的更多信息,请参阅数据库配置文档

zzznl 翻译于 1个月前

生成模型类

首先,让我们创建一个 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 Flight -a

# 生成一个 pivot 模型...
php artisan make:model Member --pivot
php artisan make:model Member -p
zzznl 翻译于 1个月前
Jader 审阅

模型检查

有时候,仅仅通过浏览其代码很难确定模型的所有可用属性和关系。相反,尝试使用 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 模型对应的是哪个数据库表。按照惯例,类的「snake case」、复数名称将被用作表名,除非明确指定了另一个名称。因此,在本例中,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';
}
zzznl 翻译于 1个月前
Jader 审阅

此外,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。当然,你应该确保模型有一个相当于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"
zzznl 翻译于 1个月前
Jader 审阅

默认情况下,HasUuids 特性将为您的模型生成 「有序」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 特性。您还应确保模型具有 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 自动管理这些列,您应该在模型上定义一个值为 false$timestamps 属性:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * 指示模型是否应该被时间戳标记。
     *
     * @var bool
     */
    public $timestamps = false;
}
slowlyo 翻译于 16小时前

如需自定义模型时间戳的格式,请在模型中设置 $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 = 'mysql';
}

默认属性值

默认情况下,新实例化的模型不包含任何属性值。如需为模型的某些属性定义默认值,可在模型中定义 $attributes 属性。$attributes 数组中的属性值应采用原始的「可存储」格式,如同刚从数据库中读取一样:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * 模型的默认属性值
     *
     * @var array
     */
    protected $attributes = [
        'options' => '[]',
        'delayed' => false,
    ];
}
slowlyo 翻译于 16小时前

配置 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;
}

slowlyo 翻译于 16小时前

Building Queries

The Eloquent all method will return all of the results in the model's table. However, since each Eloquent model serves as a query builder, you may add additional constraints to queries and then invoke the get method to retrieve the results:

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

[!NOTE]
Since Eloquent models are query builders, you should review all of the methods provided by Laravel's query builder. You may use any of these methods when writing your Eloquent queries.

Refreshing Models

If you already have an instance of an Eloquent model that was retrieved from the database, you can "refresh" the model using the fresh and refresh methods. The fresh method will re-retrieve the model from the database. The existing model instance will not be affected:

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

$freshFlight = $flight->fresh();

The refresh method will re-hydrate the existing model using fresh data from the database. In addition, all of its loaded relationships will be refreshed as well:

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

$flight->number = 'FR 456';

$flight->refresh();

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

Collections

As we have seen, Eloquent methods like all and get retrieve multiple records from the database. However, these methods don't return a plain PHP array. Instead, an instance of Illuminate\Database\Eloquent\Collection is returned.

The Eloquent Collection class extends Laravel's base Illuminate\Support\Collection class, which provides a variety of helpful methods for interacting with data collections. For example, the reject method may be used to remove models from a collection based on the results of an invoked closure:

$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');
slowlyo 翻译于 16小时前

由于 chunkByIdlazyById 方法会向正在执行的查询添加自己的 "where" 条件,你通常应该将你自己的条件逻辑分组在闭包内:

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 方法在幕后以块为单位执行查询,这与 chunk 方法类似。然而,lazy 方法不会直接将每个块传递给回调函数,而是返回一个扁平的 Eloquent 模型惰性集合,让你可以将结果作为单个流进行交互:

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 方法类似,cursor 方法可用于在遍历数万条 Eloquent 模型记录时显著减少应用程序的内存消耗。

slowlyo 翻译于 16小时前

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

slowlyo 翻译于 16小时前

高级子查询

子查询选择

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);
slowlyo 翻译于 16小时前

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

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

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

未找到异常

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

$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 = Flight::firstOrNew([
    'name' => 'London to Paris'
]);

// 按名称检索航班,或使用名称、延迟和到达时间属性实例化...
$flight = Flight::firstOrNew(
    ['name' => 'Tokyo to Sydney'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);
slowlyo 翻译于 16小时前

Retrieving Aggregates

When interacting with Eloquent models, you may also use the count, sum, max, and other aggregate methods provided by the Laravel query builder. As you might expect, these methods return a scalar value instead of an Eloquent model instance:

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

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

Inserting and Updating Models

Inserts

Of course, when using Eloquent, we don't only need to retrieve models from the database. We also need to insert new records. Thankfully, Eloquent makes it simple. To insert a new record into the database, you should instantiate a new model instance and set attributes on the model. Then, call the save method on the model instance:

<?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 the name field from the incoming HTTP request to the name attribute of the App\Models\Flight model instance. When we call the save method, a record will be inserted into the database. The model's created_at and updated_at timestamps will automatically be set when the save method is called, so there is no need to set them manually.

Alternatively, you may use the create method to "save" a new model using a single PHP statement. The inserted model instance will be returned to you by the create method:

use App\Models\Flight;

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

However, before using the create method, you will need to specify either a fillable or guarded property on your model class. These properties are required because all Eloquent models are protected against mass assignment vulnerabilities by default. To learn more about mass assignment, please consult the mass assignment documentation.

Updates

The save method may also be used to update models that already exist in the database. To update a model, you should retrieve it and set any attributes you wish to update. Then, you should call the model's save method. Again, the updated_at timestamp will automatically be updated, so there is no need to manually set its value:

use App\Models\Flight;

$flight = Flight::find(1);

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

$flight->save();

Occasionally, you may need to update an existing model or create a new model if no matching model exists. Like the firstOrCreate method, the updateOrCreate method persists the model, so there's no need to manually call the save method.

In the example below, if a flight exists with a departure location of Oakland and a destination location of San Diego, its price and discounted columns will be updated. If no such flight exists, a new flight will be created which has the attributes resulting from merging the first argument array with the second argument array:

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

Mass Updates

Updates can also be performed against models that match a given query. In this example, all flights that are active and have a destination of San Diego will be marked as delayed:

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

The update method expects an array of column and value pairs representing the columns that should be updated. The update method returns the number of affected rows.

[!WARNING]
When issuing a mass update via Eloquent, the saving, saved, updating, and updated model events will not be fired for the updated models. This is because the models are never actually retrieved when issuing a mass update.

Examining Attribute Changes

Eloquent provides the isDirty, isClean, and wasChanged methods to examine the internal state of your model and determine how its attributes have changed from when the model was originally retrieved.

The isDirty method determines if any of the model's attributes have been changed since the model was retrieved. You may pass a specific attribute name or an array of attributes to the isDirty method to determine if any of the attributes are "dirty". The isClean method will determine if an attribute has remained unchanged since the model was retrieved. This method also accepts an optional attribute argument:

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

The wasChanged method determines if any attributes were changed when the model was last saved within the current request cycle. If needed, you may pass an attribute name to see if a particular attribute was changed:

$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

The getOriginal method returns an array containing the original attributes of the model regardless of any changes to the model since it was retrieved. If needed, you may pass a specific attribute name to get the original value of a particular attribute:

$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...

The getChanges method returns an array containing the attributes that changed when the model was last saved, while the getPrevious method returns an array containing the original attribute values before the model was last saved:

$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',
    ]
*/

Mass Assignment

You may use the create method to "save" a new model using a single PHP statement. The inserted model instance will be returned to you by the method:

use App\Models\Flight;

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

However, before using the create method, you will need to specify either a fillable or guarded property on your model class. These properties are required because all Eloquent models are protected against mass assignment vulnerabilities by default.

A mass assignment vulnerability occurs when a user passes an unexpected HTTP request field and that field changes a column in your database that you did not expect. For example, a malicious user might send an is_admin parameter through an HTTP request, which is then passed to your model's create method, allowing the user to escalate themselves to an administrator.

So, to get started, you should define which model attributes you want to make mass assignable. You may do this using the $fillable property on the model. For example, let's make the name attribute of our Flight model mass assignable:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = ['name'];
}

Once you have specified which attributes are mass assignable, you may use the create method to insert a new record in the database. The create method returns the newly created model instance:

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

If you already have a model instance, you may use the fill method to populate it with an array of attributes:

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

Mass Assignment and JSON Columns

When assigning JSON columns, each column's mass assignable key must be specified in your model's $fillable array. For security, Laravel does not support updating nested JSON attributes when using the guarded property:

/**
 * The attributes that are mass assignable.
 *
 * @var array<int, string>
 */
protected $fillable = [
    'options->enabled',
];

Allowing Mass Assignment

If you would like to make all of your attributes mass assignable, you may define your model's $guarded property as an empty array. If you choose to unguard your model, you should take special care to always hand-craft the arrays passed to Eloquent's fill, create, and update methods:

/**
 * The attributes that aren't mass assignable.
 *
 * @var array<string>|bool
 */
protected $guarded = [];

Mass Assignment Exceptions

By default, attributes that are not included in the $fillable array are silently discarded when performing mass-assignment operations. In production, this is expected behavior; however, during local development it can lead to confusion as to why model changes are not taking effect.

If you wish, you may instruct Laravel to throw an exception when attempting to fill an unfillable attribute by invoking the preventSilentlyDiscardingAttributes method. Typically, this method should be invoked in the boot method of your application's AppServiceProvider class:

use Illuminate\Database\Eloquent\Model;

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

Upserts

Eloquent's upsert method may be used to update or create records in a single, atomic operation. The method's first argument consists of the values to insert or update, while the second argument lists the column(s) that uniquely identify records within the associated table. The method's third and final argument is an array of the columns that should be updated if a matching record already exists in the database. The upsert method will automatically set the created_at and updated_at timestamps if timestamps are enabled on the model:

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

[!WARNING]
All databases except SQL Server require the columns in the second argument of the upsert method to have a "primary" or "unique" index. In addition, the MariaDB and MySQL database drivers ignore the second argument of the upsert method and always use the "primary" and "unique" indexes of the table to detect existing records.

Deleting Models

To delete a model, you may call the delete method on the model instance:

use App\Models\Flight;

$flight = Flight::find(1);

$flight->delete();

Deleting an Existing Model by its Primary Key

In the example above, we are retrieving the model from the database before calling the delete method. However, if you know the primary key of the model, you may delete the model without explicitly retrieving it by calling the destroy method. In addition to accepting the single primary key, the destroy method will accept multiple primary keys, an array of primary keys, or a collection of primary keys:

Flight::destroy(1);

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

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

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

If you are utilizing soft deleting models, you may permanently delete models via the forceDestroy method:

Flight::forceDestroy(1);

[!WARNING]
The destroy method loads each model individually and calls the delete method so that the deleting and deleted events are properly dispatched for each model.

Deleting Models Using Queries

Of course, you may build an Eloquent query to delete all models matching your query's criteria. In this example, we will delete all flights that are marked as inactive. Like mass updates, mass deletes will not dispatch model events for the models that are deleted:

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

To delete all models in a table, you should execute a query without adding any conditions:

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

[!WARNING]
When executing a mass delete statement via Eloquent, the deleting and deleted model events will not be dispatched for the deleted models. This is because the models are never actually retrieved when executing the delete statement.

Soft Deleting

In addition to actually removing records from your database, Eloquent can also "soft delete" models. When models are soft deleted, they are not actually removed from your database. Instead, a deleted_at attribute is set on the model indicating the date and time at which the model was "deleted". To enable soft deletes for a model, add the Illuminate\Database\Eloquent\SoftDeletes trait to the model:

<?php

namespace App\Models;

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

class Flight extends Model
{
    use SoftDeletes;
}

[!NOTE]
The SoftDeletes trait will automatically cast the deleted_at attribute to a DateTime / Carbon instance for you.

You should also add the deleted_at column to your database table. The Laravel schema builder contains a helper method to create this column:

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

Now, when you call the delete method on the model, the deleted_at column will be set to the current date and time. However, the model's database record will be left in the table. When querying a model that uses soft deletes, the soft deleted models will automatically be excluded from all query results.

To determine if a given model instance has been soft deleted, you may use the trashed method:

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

Restoring Soft Deleted Models

Sometimes you may wish to "un-delete" a soft deleted model. To restore a soft deleted model, you may call the restore method on a model instance. The restore method will set the model's deleted_at column to null:

$flight->restore();

You may also use the restore method in a query to restore multiple models. Again, like other "mass" operations, this will not dispatch any model events for the models that are restored:

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

The restore method may also be used when building relationship queries:

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

Permanently Deleting Models

Sometimes you may need to truly remove a model from your database. You may use the forceDelete method to permanently remove a soft deleted model from the database table:

$flight->forceDelete();

You may also use the forceDelete method when building Eloquent relationship queries:

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

Querying Soft Deleted Models

Including Soft Deleted Models

As noted above, soft deleted models will automatically be excluded from query results. However, you may force soft deleted models to be included in a query's results by calling the withTrashed method on the query:

use App\Models\Flight;

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

The withTrashed method may also be called when building a relationship query:

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

Retrieving Only Soft Deleted Models

The onlyTrashed method will retrieve only soft deleted models:

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

Pruning Models

Sometimes you may want to periodically delete models that are no longer needed. To accomplish this, you may add the Illuminate\Database\Eloquent\Prunable or Illuminate\Database\Eloquent\MassPrunable trait to the models you would like to periodically prune. After adding one of the traits to the model, implement a prunable method which returns an Eloquent query builder that resolves the models that are no longer needed:

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

When marking models as Prunable, you may also define a pruning method on the model. This method will be called before the model is deleted. This method can be useful for deleting any additional resources associated with the model, such as stored files, before the model is permanently removed from the database:

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

After configuring your prunable model, you should schedule the model:prune Artisan command in your application's routes/console.php file. You are free to choose the appropriate interval at which this command should be run:

use Illuminate\Support\Facades\Schedule;

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

Behind the scenes, the model:prune command will automatically detect "Prunable" models within your application's app/Models directory. If your models are in a different location, you may use the --model option to specify the model class names:

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

If you wish to exclude certain models from being pruned while pruning all other detected models, you may use the --except option:

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

You may test your prunable query by executing the model:prune command with the --pretend option. When pretending, the model:prune command will simply report how many records would be pruned if the command were to actually run:

php artisan model:prune --pretend

[!WARNING]
Soft deleting models will be permanently deleted (forceDelete) if they match the prunable query.

Mass Pruning

When models are marked with the Illuminate\Database\Eloquent\MassPrunable trait, models are deleted from the database using mass-deletion queries. Therefore, the pruning method will not be invoked, nor will the deleting and deleted model events be dispatched. This is because the models are never actually retrieved before deletion, thus making the pruning process much more efficient:

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

Replicating Models

You may create an unsaved copy of an existing model instance using the replicate method. This method is particularly useful when you have model instances that share many of the same attributes:

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

To exclude one or more attributes from being replicated to the new model, you may pass an array to the replicate method:

$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'
]);

Query Scopes

Global Scopes

Global scopes allow you to add constraints to all queries for a given model. Laravel's own soft delete functionality utilizes global scopes to only retrieve "non-deleted" models from the database. Writing your own global scopes can provide a convenient, easy way to make sure every query for a given model receives certain constraints.

Generating Scopes

To generate a new global scope, you may invoke the make:scope Artisan command, which will place the generated scope in your application's app/Models/Scopes directory:

php artisan make:scope AncientScope

Writing Global Scopes

Writing a global scope is simple. First, use the make:scope command to generate a class that implements the Illuminate\Database\Eloquent\Scope interface. The Scope interface requires you to implement one method: apply. The apply method may add where constraints or other types of clauses to the query as needed:

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

[!NOTE]
If your global scope is adding columns to the select clause of the query, you should use the addSelect method instead of select. This will prevent the unintentional replacement of the query's existing select clause.

Applying Global Scopes

To assign a global scope to a model, you may simply place the ScopedBy attribute on the model:

<?php

namespace App\Models;

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

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

Or, you may manually register the global scope by overriding the model's booted method and invoke the model's addGlobalScope method. The addGlobalScope method accepts an instance of your scope as its only argument:

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

After adding the scope in the example above to the App\Models\User model, a call to the User::all() method will execute the following SQL query:

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

Anonymous Global Scopes

Eloquent also allows you to define global scopes using closures, which is particularly useful for simple scopes that do not warrant a separate class of their own. When defining a global scope using a closure, you should provide a scope name of your own choosing as the first argument to the addGlobalScope method:

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

Removing Global Scopes

If you would like to remove a global scope for a given query, you may use the withoutGlobalScope method. This method accepts the class name of the global scope as its only argument:

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

Or, if you defined the global scope using a closure, you should pass the string name that you assigned to the global scope:

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

If you would like to remove several or even all of the query's global scopes, you may use the withoutGlobalScopes method:

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

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

Local Scopes

Local scopes allow you to define common sets of query constraints that you may easily re-use throughout your application. For example, you may need to frequently retrieve all users that are considered "popular". To define a scope, add the Scope attribute to an Eloquent method.

Scopes should always return the same query builder instance or void:

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

Utilizing a Local Scope

Once the scope has been defined, you may call the scope methods when querying the model. You can even chain calls to various scopes:

use App\Models\User;

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

Combining multiple Eloquent model scopes via an or query operator may require the use of closures to achieve the correct logical grouping:

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

However, since this can be cumbersome, Laravel provides a "higher order" orWhere method that allows you to fluently chain scopes together without the use of closures:

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

Dynamic Scopes

Sometimes you may wish to define a scope that accepts parameters. To get started, just add your additional parameters to your scope method's signature. Scope parameters should be defined after the $query parameter:

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

Once the expected arguments have been added to your scope method's signature, you may pass the arguments when calling the scope:

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

Pending Attributes

If you would like to use scopes to create models that have the same attributes as those used to constrain the scope, you may use the withAttributes method when building the scope query:

<?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,
        ]);
    }
}

The withAttributes method will add where conditions to the query using the given attributes, and it will also add the given attributes to any models created via the scope:

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

$draft->hidden; // true

To instruct the withAttributes method to not add where conditions to the query, you may set the asConditions argument to false:

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

Comparing Models

Sometimes you may need to determine if two models are the "same" or not. The is and isNot methods may be used to quickly verify two models have the same primary key, table, and database connection or not:

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

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

The is and isNot methods are also available when using the belongsTo, hasOne, morphTo, and morphOne relationships. This method is particularly helpful when you would like to compare a related model without issuing a query to retrieve that model:

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

Events

[!NOTE]
Want to broadcast your Eloquent events directly to your client-side application? Check out Laravel's model event broadcasting.

Eloquent models dispatch several events, allowing you to hook into the following moments in a model's lifecycle: retrieved, creating, created, updating, updated, saving, saved, deleting, deleted, trashed, forceDeleting, forceDeleted, restoring, restored, and replicating.

The retrieved event will dispatch when an existing model is retrieved from the database. When a new model is saved for the first time, the creating and created events will dispatch. The updating / updated events will dispatch when an existing model is modified and the save method is called. The saving / saved events will dispatch when a model is created or updated - even if the model's attributes have not been changed. Event names ending with -ing are dispatched before any changes to the model are persisted, while events ending with -ed are dispatched after the changes to the model are persisted.

To start listening to model events, define a $dispatchesEvents property on your Eloquent model. This property maps various points of the Eloquent model's lifecycle to your own event classes. Each model event class should expect to receive an instance of the affected model via its constructor:

<?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,
    ];
}

After defining and mapping your Eloquent events, you may use event listeners to handle the events.

[!WARNING]
When issuing a mass update or delete query via Eloquent, the saved, updated, deleting, and deleted model events will not be dispatched for the affected models. This is because the models are never actually retrieved when performing mass updates or deletes.

Using Closures

Instead of using custom event classes, you may register closures that execute when various model events are dispatched. Typically, you should register these closures in the booted method of your model:

<?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) {
            // ...
        });
    }
}

If needed, you may utilize queueable anonymous event listeners when registering model events. This will instruct Laravel to execute the model event listener in the background using your application's queue:

use function Illuminate\Events\queueable;

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

Observers

Defining Observers

If you are listening for many events on a given model, you may use observers to group all of your listeners into a single class. Observer classes have method names which reflect the Eloquent events you wish to listen for. Each of these methods receives the affected model as their only argument. The make:observer Artisan command is the easiest way to create a new observer class:

php artisan make:observer UserObserver --model=User

This command will place the new observer in your app/Observers directory. If this directory does not exist, Artisan will create it for you. Your fresh observer will look like the following:

<?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
    {
        // ...
    }
}

To register an observer, you may place the ObservedBy attribute on the corresponding model:

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

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

Or, you may manually register an observer by invoking the observe method on the model you wish to observe. You may register observers in the boot method of your application's AppServiceProvider class:

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

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

[!NOTE]
There are additional events an observer can listen to, such as saving and retrieved. These events are described within the events documentation.

Observers and Database Transactions

When models are being created within a database transaction, you may want to instruct an observer to only execute its event handlers after the database transaction is committed. You may accomplish this by implementing the ShouldHandleEventsAfterCommit interface on your observer. If a database transaction is not in progress, the event handlers will execute immediately:

<?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
    {
        // ...
    }
}

Muting Events

You may occasionally need to temporarily "mute" all events fired by a model. You may achieve this using the withoutEvents method. The withoutEvents method accepts a closure as its only argument. Any code executed within this closure will not dispatch model events, and any value returned by the closure will be returned by the withoutEvents method:

use App\Models\User;

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

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

Saving a Single Model Without Events

Sometimes you may wish to "save" a given model without dispatching any events. You may accomplish this using the saveQuietly method:

$user = User::findOrFail(1);

$user->name = 'Victoria Faith';

$user->saveQuietly();

You may also "update", "delete", "soft delete", "restore", and "replicate" a given model without dispatching any events:

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

本文章首发在 LearnKu.com 网站上。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
贡献者:4
讨论数量: 4
发起讨论 只看当前版本


zy5666
swoole
0 个点赞 | 6 个回复 | 代码速记 | 课程版本 8.x
誓言玄夏
正文 模型检索模块层级结构与左侧树结构不一致
0 个点赞 | 4 个回复 | 问答 | 课程版本 5.8
Rxy-development
laravel8.x upsert问题
0 个点赞 | 2 个回复 | 问答 | 课程版本 8.x
adsdawa
id ii
0 个点赞 | 0 个回复 | 分享 | 课程版本 8.5