Giới thiệu

Laravel có chứa Eloquent, một mapper object-relational (ORM) giúp tương tác với cơ sở dữ liệu của bạn trở nên thú vị hơn. Khi sử dụng Eloquent, mỗi table cơ sở dữ liệu có một "Model" tương ứng được sử dụng để tương tác với bảng đó. Ngoài việc truy xuất các bản ghi từ bảng cơ sở dữ liệu, thì các model Eloquent còn cho phép bạn thêm, sửa và xóa các bản ghi ra khỏi bảng.

[!NOTE] Trước khi bắt đầu, bạn hãy chắc chắn là đã cấu hình kết nối cơ sở dữ liệu trong file cấu hình config/database.php của application của bạn. Để biết thêm thông tin về cách cấu hình cơ sở dữ liệu của bạn, hãy xem tài liệu cấu hình cơ sở dữ liệu.

Tạo class model

Để bắt đầu, bạn hãy tạo một model Eloquent. Các model thường được lưu trong thư mục app\Models và extend class Illuminate\Database\Eloquent\Model. Bạn có thể sử dụng lệnh make:model Artisan command để tạo một model mới:

php artisan make:model Flight

Nếu bạn muốn tạo cả file migration cho cơ sở dữ liệu khi bạn tạo model, bạn có thể sử dụng tùy chọn --migration hoặc -m:

php artisan make:model Flight --migration

Bạn có thể tạo nhiều loại class khác nhau khi tạo model, chẳng hạn như factory, seeder, policy, controller và form request. Ngoài ra, các tùy chọn này cũng có thể được kết hợp với nhau để tạo nhiều class cùng một lúc:

# Generate a model and a FlightFactory class...
php artisan make:model Flight --factory
php artisan make:model Flight -f

# Generate a model and a FlightSeeder class...
php artisan make:model Flight --seed
php artisan make:model Flight -s

# Generate a model and a FlightController class...
php artisan make:model Flight --controller
php artisan make:model Flight -c

# Generate a model, FlightController resource class, and form request classes...
php artisan make:model Flight --controller --resource --requests
php artisan make:model Flight -crR

# Generate a model and a FlightPolicy class...
php artisan make:model Flight --policy

# Generate a model and a migration, factory, seeder, and controller...
php artisan make:model Flight -mfsc

# Shortcut to generate a model, migration, factory, seeder, policy, controller, and form requests...
php artisan make:model Flight --all
php artisan make:model Flight -a

# Generate a pivot model...
php artisan make:model Member --pivot
php artisan make:model Member -p

Inspecting Models

Thỉnh thoảng có thể khó xác định tất cả các thuộc tính và các quan hệ sẵn có của một model chỉ bằng cách đọc lướt qua code của nó. Thay vào đó, bạn hãy thử lệnh Artisan model:show, lệnh này sẽ cung cấp một cái nhìn tổng quan về tất cả các thuộc tính và các quan hệ của model:

php artisan model:show Flight

Quy ước tên Eloquent Model

Các model được tạo bởi lệnh make:model sẽ được lưu trong thư mục app/Models. Hãy xem qua một class model cơ bản và thảo luận về một số quy ước chính của Eloquent:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    // ...
}

Table Names

Sau khi xem qua ví dụ trên, bạn có thể nhận thấy rằng chúng ta đã không cho Eloquent biết bảng cơ sở dữ liệu nào tương ứng với model Flight của chúng ta. Mặc định, "snake case" cộng với tên số nhiều của class sẽ được sử dụng làm tên bảng trừ khi bạn khai báo một tên khác. Vì vậy, trong trường hợp này, Eloquent sẽ giả định rằng: model Flight sẽ lưu các bản ghi vào trong bảng flights, trong khi model AirTrafficController sẽ lưu các bản ghi vào trong bảng air_traffic_controllers.

Nếu bảng cơ sở dữ liệu tương ứng của model của bạn không phù hợp với quy ước này, bạn có thể chỉ định tên bảng của model theo cách thủ công bằng cách sử dụng thuộc tính Table:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Model;

#[Table('my_flights')]
class Flight extends Model
{
    // ...
}

Primary Keys

Eloquent cũng sẽ giả định rằng mỗi bảng cơ sở dữ liệu tương ứng của model có một cột khóa chính có tên là id. Nếu cần thiết, bạn có thể chỉ định một cột khác đóng vai trò làm khóa chính của model bằng cách sử dụng tham số key trong thuộc tính Table:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Model;

#[Table(key: 'flight_id')]
class Flight extends Model
{
    // ...
}

Ngoài ra, Eloquent cũng giả định rằng khóa chính là một giá trị integer tăng dần đều, có nghĩa là Eloquent sẽ được tự động cast khóa chính thành một số nguyên. Nếu bạn muốn sử dụng khóa chính không tăng dần hoặc không phải dạng integer, bạn có thể chỉ định tham số keyTypeincrementing ở trong thuộc tính Table:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Model;

#[Table(key: 'uuid', keyType: 'string', incrementing: false)]
class Flight extends Model
{
    // ...
}

Nếu bạn chỉ cần vô hiệu hóa ID tự động tăng, bạn có thể sử dụng attribute WithoutIncrementing:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\WithoutIncrementing;
use Illuminate\Database\Eloquent\Model;

#[WithoutIncrementing]
class Flight extends Model
{
    // ...
}

"Composite" Primary Keys

Eloquent yêu cầu mỗi model phải có ít nhất một "ID" nhận dạng duy nhất để có thể làm khóa chính. Các khóa chính "Composite" không được hỗ trợ bởi các model Eloquent. Tuy nhiên, bạn có thể tự do thêm các index, nhiều cột vào các bảng cơ sở dữ liệu của bạn ngoài khóa chính để xác định tính duy nhất của bảng.

UUID và ULID Keys

Thay vì sử dụng một số tự động tăng làm khóa chính cho model Eloquent, bạn có thể chọn sử dụng UUID thay thế. UUID là một mã định danh chữ và số duy nhất trên toàn cầu có độ dài 36 ký tự.

Nếu bạn muốn một model sử dụng khóa UUID thay vì khóa số nguyên tự động tăng, bạn có thể sử dụng trait Illuminate\Database\Eloquent\Concerns\HasUuids trên model. Tất nhiên, bạn nên đảm bảo rằng model có một cột khóa chính tương ứng với 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; // "018f2b5c-6a7f-7b12-9d6f-2f8a4e0c9c11"

Mặc định, trait HasUuids sẽ tạo ra id dạng UUIDv7 cho model của bạn. Các UUID này hiệu quả cho việc lưu trữ index trong cơ sở dữ liệu vì chúng có thể được sắp xếp theo kiểu từ điển.

Bạn có thể ghi đè process tạo UUID cho một model nhất định bằng cách định nghĩa một phương thức newUniqueId trên model. Ngoài ra, bạn có thể chỉ định cột nào đó sẽ nhận UUID bằng cách định nghĩa phương thức uniqueIds trên model:

use Ramsey\Uuid\Uuid;

/**
 * Generate a new UUID for the model.
 */
public function newUniqueId(): string
{
    return (string) Uuid::uuid4();
}

/**
 * Get the columns that should receive a unique identifier.
 *
 * @return array<int, string>
 */
public function uniqueIds(): array
{
    return ['id', 'discount_code'];
}

Nếu muốn, bạn có thể chọn sử dụng "ULIDs" thay vì dùng UUID. ULID tương tự như UUID; tuy nhiên, chúng chỉ có độ dài 26 ký tự. Giống như các UUID có thể được sắp xếp, các ULID có thể được sắp xếp theo kiểu từ điển để lập index cho cơ sở dữ liệu một cách hiệu quả hơn. Để sử dụng ULID, bạn nên sử dụng trait Illuminate\Database\Eloquent\Concerns\HasUlids trên model của bạn. Bạn cũng nên đảm bảo rằng model có cột khóa chính tương ứng 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"

Timestamps

Mặc định, Eloquent mong đợi các cột created_atupdated_at tồn tại trong bảng cơ sở dữ liệu tương ứng với model của bạn. Eloquent sẽ tự động set các giá trị của các cột này khi các model được tạo hoặc cập nhật. Nếu bạn không muốn Eloquent tự động quản lý các cột này, bạn có thể set timestamps thành false trong thuộc tính Table của model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Model;

#[Table(timestamps: false)]
class Flight extends Model
{
    // ...
}

Nếu bạn chỉ cần vô hiệu hóa timestamp, bạn có thể sử dụng attribute WithoutTimestamps:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\WithoutTimestamps;
use Illuminate\Database\Eloquent\Model;

#[WithoutTimestamps]
class Flight extends Model
{
    // ...
}

Nếu bạn cần tùy biến định dạng timestamp của model của bạn, bạn có thể sử dụng tham số dateFormat trong thuộc tính Table. Điều này sẽ định nghĩa cách mà các thuộc tính date được lưu vào trong cơ sở dữ liệu cũng như định dạng của chúng khi model được chuyển đổi thành các loại dữ liệu như một mảng hoặc một dạng JSON:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Model;

#[Table(dateFormat: 'U')]
class Flight extends Model
{
    // ...
}

Nếu bạn chỉ cần định nghĩa định dạng ngày, bạn có thể sử dụng attribute DateFormat:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\DateFormat;
use Illuminate\Database\Eloquent\Model;

#[DateFormat('U')]
class Flight extends Model
{
    // ...
}

Nếu bạn cần tùy biến tên của các cột được sử dụng để lưu timestamp, bạn có thể định nghĩa các hằng số CREATED_ATUPDATED_AT trên model của bạn:

<?php

class Flight extends Model
{
    /**
     * The name of the "created at" column.
     *
     * @var string|null
     */
    public const CREATED_AT = 'creation_date';

    /**
     * The name of the "updated at" column.
     *
     * @var string|null
     */
    public const UPDATED_AT = 'updated_date';
}

Nếu bạn muốn thực hiện các thao tác trên model mà không cần sửa timestamp updated_at của model, bạn có thể thao tác trên model trong một closure được cung cấp trong phương thức withoutTimestamps:

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

Database Connection

Mặc định, tất cả các model Eloquent sẽ sử dụng kết nối cơ sở dữ liệu mặc định đã được cấu hình trong application của bạn. Nếu bạn muốn khai báo một kết nối khác sẽ được sử dụng khi tương tác với một model cụ thể, thì bạn có thể sử dụng thuộc tính Connection:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Connection;
use Illuminate\Database\Eloquent\Model;

#[Connection('mysql')]
class Flight extends Model
{
    // ...
}

Giá trị thuộc tính mặc định

Mặc định, một instance model mới khi được tạo sẽ không chứa bất kỳ giá trị thuộc tính nào. Nếu bạn muốn định nghĩa giá trị mặc định cho một số thuộc tính của model, bạn có thể định nghĩa thuộc tính $attributes trên model của bạn. Các giá trị thuộc tính được set trong mảng $attributes phải ở định dạng raw, "lưu trữ được" giống như chúng vừa được đọc ra từ cơ sở dữ liệu:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * The model's default values for attributes.
     *
     * @var array<string, mixed>
     */
    protected $attributes = [
        'options' => '[]',
        'delayed' => false,
    ];
}

Cấu hình Eloquent Strictness

Laravel cung cấp một số phương thức cho phép bạn cấu hình hành vi và "sự nghiêm ngặt" của Eloquent trong nhiều tình huống khác nhau.

Đầu tiên, phương thức preventLazyLoading sẽ chấp nhận một tham số boolean tùy chọn để cho biết xem liệu có nên chặn việc lazy loading hay không. Ví dụ: bạn có thể muốn tắt lazy loading trong môi trường không phải production để môi trường production của bạn có thể tiếp tục hoạt động bình thường ngay cả khi một lazy load quan hệ vô tình xuất hiện trong code production. Thông thường, phương thức này nên được gọi trong phương thức boot của AppServiceProvider trong ứng dụng của bạn:

use Illuminate\Database\Eloquent\Model;

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

Ngoài ra, bạn có thể hướng dẫn Laravel đưa ra một ngoại lệ khi cố gắng đưa vào một thuộc tính không thể đưa bằng cách gọi phương thức preventSilentlyDiscardingAttributes. Điều này có thể giúp ngăn ngừa các lỗi không mong muốn trong quá trình phát triển ở local khi cố gắng set một thuộc tính mà chưa được thêm vào trong mảng fillable của model:

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

Lấy ra Model

Khi bạn đã tạo một model và bảng cơ sở dữ liệu được liên kết với model đó, bạn có thể bắt đầu truy xuất dữ liệu từ cơ sở dữ liệu của bạn. Bạn có thể nghĩ về mỗi model Eloquent như là một query builder cho phép bạn truy vấn vào bảng cơ sở dữ liệu được liên kết với model đó. Phương thức all của model sẽ lấy ra tất cả các bản ghi từ bảng cơ sở dữ liệu được liên kết của model:

use App\Models\Flight;

foreach (Flight::all() as $flight) {
    echo $flight->name;
}

Building Queries

Phương thức all của Eloquent sẽ trả về tất cả các bản ghi có trong bảng của model. Tuy nhiên, vì mỗi model Eloquent đóng vai trò là một query builder, nên bạn có thể thêm các ràng buộc bổ sung cho các truy vấn của bạn, và sau đó gọi phương thức get để lấy ra kết quả:

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

[!NOTE] Vì các model Eloquent là các query builder, nên bạn nên xem lại tất cả các phương thức đã được cung cấp bởi query builder của Laravel. Bạn có thể sử dụng bất kỳ phương thức nào khi viết các truy vấn Eloquent của bạn.

Refreshing Models

Nếu bạn đã có một instance của model Eloquent được lấy từ cơ sở dữ liệu, bạn có thể "refresh" các model bằng cách sử dụng các phương thức freshrefresh. Phương thức fresh sẽ lấy ra một model mới từ cơ sở dữ liệu. Instance model hiện tại sẽ không bị ảnh hưởng:

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

$freshFlight = $flight->fresh();

Phương thức refresh sẽ tái tạo lại model hiện tại bằng cách sử dụng dữ liệu mới từ cơ sở dữ liệu. Ngoài ra, tất cả các quan hệ đã được load cũng sẽ bị refresh:

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

$flight->number = 'FR 456';

$flight->refresh();

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

Collection

Như chúng ta đã thấy, các phương thức Eloquent như allget sẽ lấy nhiều record từ cơ sở dữ liệu. Tuy nhiên, các phương thức này không trả về một mảng PHP. Thay vì, một instance của Illuminate\Database\Eloquent\Collection sẽ được trả về.

Class Eloquent Collection được mở rộng từ class Illuminate\Support\Collection của Laravel, trong đó cung cấp nhiều phương thức hữu ích để tương tác với các dữ liệu collection. Ví dụ: phương thức reject có thể được sử dụng để xóa các model ra khỏi collection dựa trên kết quả của một closure được gọi:

$flights = Flight::where('destination', 'Paris')->get();

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

Ngoài các phương thức được cung cấp bởi class collection base của Laravel, class collection Eloquent cũng cung cấp một vài phương thức bổ sung được dành riêng để tương tác với các collection của những Eloquent model.

Vì tất cả các collection của Laravel đều implement các interface iterable của PHP, nên bạn có thể lặp qua các collection như thể chúng là một mảng:

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

Phân kết quả

Ứng dụng của bạn có thể hết memory nếu bạn cố load hàng chục nghìn record Eloquent thông qua các phương thức all hoặc get. Thay vì sử dụng các phương thức này, phương thức chunk có thể được sử dụng để xử lý số lượng lớn model một cách hiệu quả hơn.

Phương thức chunk sẽ lấy ra một số ít các model Eloquent, và truyền chúng đến một closure để xử lý. Vì mỗi lần chỉ lấy ra chunk hiện tại của các model Eloquent, nên phương thức chunk sẽ giúp giảm đáng kể mức sử dụng memory khi làm việc với một số lượng lớn model:

use App\Models\Flight;
use Illuminate\Database\Eloquent\Collection;

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

Tham số đầu tiên được truyền cho phương thức chunk là số lượng bản ghi mà bạn muốn nhận vào cho mỗi đoạn "chunk". Closure sẽ được truyền thông qua tham số thứ hai, và nó sẽ được gọi cho mỗi đoạn được lấy từ cơ sở dữ liệu. Các truy vấn cơ sở dữ liệu sẽ được thực hiện để truy xuất từng đoạn của bản ghi và được chuyển vào cho closure.

Nếu bạn đang lọc kết quả từ phương thức chunk dựa trên một cột mà cột đó bạn cũng sẽ cập nhật trong khi lặp lại kết quả, thì bạn nên sử dụng phương thức chunkById. Việc sử dụng phương thức chunk trong các trường hợp này có thể dẫn đến kết quả không mong muốn và không nhất quán. Phương thức chunkById sẽ luôn lấy ra các model có cột id lớn hơn model cuối cùng trong đoạn chunk trước:

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

Vì các phương thức chunkByIdlazyById sẽ thêm các điều kiện "where" của riêng chúng vào truy vấn đang được thực thi, nên thông thường bạn nên logic nhóm các điều kiện riêng của bạn trong một closure:

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

Chunking dùng Lazy Collections

Phương thức lazy hoạt động tương tự như phương thức chunk theo nghĩa là, nó cũng thực thi truy vấn theo chunk. Tuy nhiên, thay vì truyền từng chunk trực tiếp vào một hàm callback như hiện tại, thì phương thức lazy trả về LazyCollection của các model Eloquent, cho phép bạn tương tác với các kết quả dưới dạng một luồng duy nhất:

use App\Models\Flight;

foreach (Flight::lazy() as $flight) {
    // ...
}

Nếu bạn đang lọc kết quả của phương thức lazy dựa trên một cột mà bạn cũng sẽ cập nhật cột đó trong khi lặp, thì bạn nên sử dụng phương thức lazyById. Phương thức lazyById sẽ luôn lấy ra các model có cột id lớn hơn model cuối cùng trong đoạn chunk trước:

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

Bạn có thể lọc kết quả dựa trên thứ tự giảm dần của id bằng cách sử dụng phương thức lazyByIdDesc.

Cursors

Tương tự như phương thức lazy, phương thức cursor cũng có thể được sử dụng để giảm đáng kể mức tiêu thụ bộ nhớ của ứng dụng khi lặp qua hàng chục nghìn bản ghi model Eloquent.

Phương thức cursor sẽ chỉ thực hiện một truy vấn vào cơ sở dữ liệu; tuy nhiên, các model Eloquent sẽ không được cung cấp bộ nhớ cho đến khi chúng thực sự được lặp qua. Do đó, chỉ có một model Eloquent được lưu trong bộ nhớ tại bất kỳ thời điểm nào trong khi lặp.

[!WARNING] Vì phương thức cursor sẽ chỉ lưu một model Eloquent trong bộ nhớ tại một thời điểm nên nó không thể eager load các quan hệ. Nếu bạn cần eager load các quan hệ, hãy cân nhắc sử dụng phương pháp lazy để thay thế.

Phương thức cursor sử dụng PHP generators để implement chức năng này:

use App\Models\Flight;

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

Phương thức cursor sẽ trả về một instance Illuminate\Support\LazyCollection. Lazy collections cho phép bạn sử dụng nhiều phương thức có sẵn trên các collection Laravel cở bản trong khi chỉ load một model duy nhất vào bộ nhớ tại một thời điểm:

use App\Models\User;

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

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

Mặc dù phương thức cursor sử dụng ít bộ nhớ hơn nhiều so với truy vấn thông thường (bằng cách chỉ giữ một model Eloquent duy nhất trong bộ nhớ tại một thời điểm), nhưng cuối cùng nó vẫn sẽ hết bộ nhớ. Điều này là do driver PDO của PHP sẽ lưu nội bộ tất cả các kết quả truy vấn trong bộ cache của nó. Nếu bạn đang xử lý một số lượng rất lớn các bản ghi Eloquent, hãy cân nhắc sử dụng phương thức lazy để thay thế.

Advanced Subqueries

Subquery Selects

Eloquent cũng cung cấp hỗ trợ các truy vấn con nâng cao, cho phép bạn lấy thông tin từ các bảng liên quan trong một truy vấn duy nhất. Ví dụ, hãy tưởng tượng rằng chúng ta có một bảng các chuyến bay destinations và một bảng flights đến các destination. Bảng flights chứa một cột arrived_at cho biết thời điểm chuyến bay đến destination.

Sử dụng chức năng truy vấn phụ có trong phương thức selectaddSelect của query builder, chúng ta có thể lấy ra tất cả các destinations và tên của chuyến bay đã đến điểm đến đó gần đây nhất chỉ bằng một câu lệnh truy vấn duy nhất:

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

Subquery Ordering

Ngoài ra, hàm orderBy của query builder cũng hỗ trợ các truy vấn con. Tiếp tục sử dụng ví dụ trên, chúng ta có thể sử dụng chức năng này để sắp xếp tất cả các điểm đến dựa trên thời điểm chuyến bay cuối cùng đến điểm đến đó. Một lần nữa, điều này có thể được thực hiện chỉ trong một truy vấn cơ sở dữ liệu:

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

Lấy ra một Model / một thống kê

Ngoài việc truy xuất tất cả các bản ghi phù hợp với một truy vấn, bạn cũng có thể truy xuất một bản ghi bằng cách sử dụng phương thức find, first, hoặc firstWhere. Thay vì trả về một tập hợp các model, thì các phương thức này sẽ trả về một instance model duy nhất:

 use App\Models\Flight;

// Retrieve a model by its primary key...
$flight = Flight::find(1);

// Retrieve the first model matching the query constraints...
$flight = Flight::where('active', 1)->first();

// Alternative to retrieving the first model matching the query constraints...
$flight = Flight::firstWhere('active', 1);

Thỉnh thoảng, bạn có thể muốn thực hiện một số hành động khác nếu không tìm thấy kết quả nào. Phương thức findOr và phương thức firstOr sẽ trả về một instance model hoặc nếu không tìm thấy kết quả nào khác, thì sẽ thực hiện một closure đã cho. Giá trị được trả về bởi closure sẽ được coi là kết quả của phương thức:

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

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

Not Found Exceptions

Thỉnh thoảng bạn có thể muốn đưa ra một ngoại lệ nếu không tìm thấy model. Điều này đặc biệt hữu ích trong các route hoặc controller. Các phương thức findOrFailfirstOrFail sẽ lấy ra kết quả đầu tiên của truy vấn; tuy nhiên, nếu không tìm thấy kết quả nào, một Illuminate\Database\Eloquent\ModelNotFoundException sẽ được đưa ra:

$flight = Flight::findOrFail(1);

$flight = Flight::where('legs', '>', 3)->firstOrFail();

Nếu ngoại lệ ModelNotFoundException không được xử lý, một HTTP response 404 sẽ tự động được gửi về cho người dùng:

use App\Models\Flight;

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

Lấy hoặc tạo model

Phương thức firstOrCreate sẽ thử lấy một bản ghi cơ sở dữ liệu bằng cách sử dụng mảng cột và giá trị đã cho. Nếu không thể tìm thấy model trong cơ sở dữ liệu, một record mới sẽ được thêm vào với thuộc tính là từ mảng của tham số thứ nhất cộng với tham số thứ hai.

Phương thức firstOrNew cũng giống như phương thức firstOrCreate là cũng sẽ cố gắng thử xác định một bản ghi có trong cơ sở dữ liệu có khớp với các thuộc tính đã cho hay không. Tuy nhiên, nếu không tìm thấy model, một instance model mới sẽ được trả về. Lưu ý rằng model được trả về bởi firstOrNew vẫn chưa được lưu vào trong cơ sở dữ liệu. Bạn sẽ cần phải gọi phương thức save để lưu nó:

use App\Models\Flight;

// Retrieve flight by name or create it if it doesn't exist...
$flight = Flight::firstOrCreate([
    'name' => 'London to Paris'
]);

// Retrieve flight by name or create it with the name, delayed, and arrival_time attributes...
$flight = Flight::firstOrCreate(
    ['name' => 'London to Paris'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);

// Retrieve flight by name or instantiate a new Flight instance...
$flight = Flight::firstOrNew([
    'name' => 'London to Paris'
]);

// Retrieve flight by name or instantiate with the name, delayed, and arrival_time attributes...
$flight = Flight::firstOrNew(
    ['name' => 'Tokyo to Sydney'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);

Lấy ra một thống kê

Khi tương tác với các model Eloquent, bạn cũng có thể sử dụng các phương thức count, sum, max, và các phương thức thống kê khác được cung cấp bởi query builder của Laravel. Như bạn có thể cảm thấy, các phương thức này trả về giá trị thay vì một instance model Eloquent:

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

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

Thêm và cập nhật Model

Thêm

Tất nhiên, khi sử dụng Eloquent, chúng ta không chỉ cần lấy các model từ cơ sở dữ liệu. Chúng ta cũng cần thêm các bản ghi mới. Rất may, Eloquent làm cho nó đơn giản. Để thêm một bản ghi mới vào cơ sở dữ liệu, bạn nên khởi tạo một instance model mới và set các thuộc tính cho model đó. Sau đó, gọi phương thức save trên instance model đó:

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

Trong ví dụ này, chúng ta đã gán field name trong HTTP request cho một thuộc tính name của instance model App\Flight. Khi chúng ta gọi phương thức save, một bản ghi sẽ được thêm vào cơ sở dữ liệu. Các timestamp created_atupdate_at của model cũng sẽ tự động được set khi gọi phương thức save, do đó bạn không cần phải set chúng.

Nếu bạn muốn lưu model trong một database transaction, bạn có thể sử dụng phương thức saveOrFail. Nếu một ngoại lệ được đưa ra trong quá trình lưu, transaction sẽ tự động được rollback:

$flight->saveOrFail();

Ngoài ra, bạn có thể sử dụng phương thức create để "lưu" một model mới bằng cách sử dụng một câu lệnh PHP. Instance model được thêmn vào sẽ được trả về cho bạn bằng phương thức create:

use App\Models\Flight;

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

Tuy nhiên, trước khi làm như vậy, bạn sẽ cần phải khai báo thuộc tính Fillable hoặc Guarded trên model, vì mặc định, tất cả các model Eloquent đều được bảo vệ để chống lại việc mass-assignment. Để tìm hiểu thêm về mass assignment, vui lòng tham khảo tài liệu mass assignment.

Cập nhật

Phương thức save cũng có thể được sử dụng để cập nhật một model đã tồn tại trong cơ sở dữ liệu. Để cập nhật một model, bạn nên truy xuất nó ra và set lại bất kỳ các thuộc tính nào mà bạn muốn cập nhật. Then, you should gọi phương thức save của model. Timestamp update_at sẽ tự động được cập nhật lại theo nó, do đó bạn không cần phải tự phải set lại giá trị cho nó:

use App\Models\Flight;

$flight = Flight::find(1);

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

$flight->save();

Nếu bạn muốn cập nhật model trong một database transaction, bạn có thể sử dụng phương thức updateOrFail. Nếu một ngoại lệ được đưa ra trong quá trình cập nhật, transaction sẽ tự động được rollback:

$flight->updateOrFail(['name' => 'Paris to London']);

Đôi khi, bạn có thể cần cập nhật model hiện có hoặc tạo một model mới nếu không có model nào phù hợp. Giống như phương thức firstOrCreate, thì phương thức updateOrCreate cũng sẽ lưu model, và do đó bạn không cần phải gọi phương thức save.

Trong ví dụ dưới đây, nếu một chuyến bay tồn tại với điểm khởi hành là Oakland và điểm đến là San Diego, thì các cột pricediscounted của chuyến bay đó sẽ được cập nhật. Nếu không có chuyến bay nào như vậy, một chuyến bay mới sẽ được tạo với các thuộc tính có được từ việc hợp nhất mảng tham số thứ nhất với mảng tham số thứ hai:

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

Khi sử dụng các phương thức như firstOrCreate hoặc updateOrCreate, bạn có thể không biết một model mới đã được tạo ra hay là cập nhật một model hiện có. Thuộc tính wasRecentlyCreated cho biết model đó có được tạo mới hay không:

$flight = Flight::updateOrCreate(
    // ...
);

if ($flight->wasRecentlyCreated) {
    // New flight record was inserted...
}

Mass Updates

Cập nhật cũng có thể được thực hiện đối với các model tương ứng với một câu lệnh truy vấn duy nhất. Trong ví dụ này, tất cả các flight đang hoạt động và có điểm đếnSan Diego sẽ bị đánh dấu là delay:

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

Phương thức update yêu cầu một mảng gồm các cặp: tên cột và giá trị cần được cập nhật. Phương thức update này sẽ trả về số hàng bị ảnh hưởng.

[!WARNING] Khi chạy một mass update thông qua Eloquent, thì các event của model như saving, saved, updating, và updated sẽ không được kích hoạt. Điều này là do các model đã không được lấy ra khi chạy một mass update.

Examining Attribute Changes

Eloquent cung cấp các phương thức isDirty, isCleanwasChanged để kiểm tra xem trạng thái của model của bạn và xác định các thuộc tính của model đã bị thay đổi như thế nào so với khi model được lấy ra.

Phương thức isDirty sẽ xác định xem có bất kỳ thuộc tính nào bị thay đổi kể từ khi model được lấy ra hay không. Bạn có thể truyền vào tên một thuộc tính cụ thể hoặc một mảng các thuộc tính vào phương thức isDirty để xác định xem có thuộc tính nào bị "thay đổi" hay không. Phương thức isClean sẽ xác định xem một thuộc tính có thay đổi không kể từ khi model được lấy ra. Phương thức này cũng chấp nhận một tùy chọn tham số cho tên thuộc tính:

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

Phương thức wasChanged sẽ xác định xem đã có bất kỳ thuộc tính nào bị thay đổi khi model được lưu vào lần cuối trong request hiện tại hay không. Nếu cần, bạn có thể truyền tên một thuộc tính để xem liệu thuộc tính đó có bị thay đổi hay không:

$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

Phương thức getOriginal sẽ trả về một mảng chứa các thuộc tính ban đầu của model bất kể có thay đổi nào kể từ khi nó được lấy ra. Nếu cần, bạn có thể truyền vào tên của một thuộc tính cụ thể để nhận về giá trị ban đầu của một thuộc tính đó:

$user = User::find(1);

$user->name; // John
$user->email; // [email protected]

$user->name = 'Jack';
$user->name; // Jack

$user->getOriginal('name'); // John
$user->getOriginal(); // Array of original attributes...

Phương thức getChanges sẽ trả về một mảng chứa các thuộc tính đã bị thay đổi khi model được lưu ở lần cuối, trong khi phương thức getPrevious sẽ trả về một mảng chứa các giá trị thuộc tính ban đầu trước khi model được lưu:

$user = User::find(1);

$user->name; // John
$user->email; // [email protected]

$user->update([
    'name' => 'Jack',
    'email' => '[email protected]',
]);

$user->getChanges();

/*
    [
        'name' => 'Jack',
        'email' => '[email protected]',
    ]
*/

$user->getPrevious();

/*
    [
        'name' => 'John',
        'email' => '[email protected]',
    ]
*/

Mass Assignment

Bạn có thể sử dụng phương thức create để "lưu" một model mới với một câu lệnh PHP duy nhất. Model được thêm vào và sẽ được trả về cho bạn bằng phương thức đó.

use App\Models\Flight;

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

Tuy nhiên, trước khi làm như vậy, bạn sẽ cần phải khai báo thuộc tính Fillable hoặc Guarded trên model, vì mặc định, tất cả các model Eloquent đều được bảo vệ để chống lại việc mass-assignment.

Lỗ hổng mass assignment xảy ra khi người dùng truyền một field HTTP request và các field này thay đổi giá trị một cột trong cơ sở dữ liệu của bạn mà bạn không mong muốn. Ví dụ: kẻ xấu có thể gửi một tham số is_admin thông qua một request HTTP, sau đó được truyền đến phương thức create trong model của bạn, cho phép người đó có thể nâng quyền lên quyền admin.

Vì vậy, để bắt đầu, bạn nên định nghĩa các thuộc tính mà bạn muốn mass assignable. Bạn có thể làm điều này bằng cách sử dụng thuộc tính Fillable trên model. Ví dụ: hãy tạo thuộc tính name trong model Flight có thể được sử dụng để mass assignable:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;

#[Fillable(['name'])]
class Flight extends Model
{
    // ...
}

Khi you have specified which các thuộc tính có thể được sử dụng để mass assignable, thì bạn có thể sử dụng phương thức create để thêm một bản ghi mới vào trong cơ sở dữ liệu. Phương thức create sẽ trả về instance model mà đã được tạo mới:

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

Nếu bạn đã có một instance model, bạn có thể sử dụng phương thức fill để thêm vào model một mảng các thuộc tính:

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

Mass Assignment và JSON Columns

Khi gán các cột JSON, mỗi khóa mass assignable của cột đó phải được chỉ định trong mảng $fillable trong model của bạn. Để bảo mật, Laravel không hỗ trợ cập nhật các thuộc tính JSON lồng nhau khi sử dụng thuộc tính guarded:

use Illuminate\Database\Eloquent\Attributes\Fillable;

#[Fillable(['options->enabled'])]
class Flight extends Model
{
    // ...
}

Allowing Mass Assignment

Nếu bạn muốn làm cho tất cả các thuộc tính của bạn đều có thể được sử dụng mass assignable, bạn có thể định nghĩa thuộc tính $guarded trong model của bạn là một mảng trống. Nếu bạn chọn không dùng guard model, bạn nên đặc biệt cẩn thận với các mảng được truyền cho các phương thức fill, createupdate của Eloquent:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Unguarded;
use Illuminate\Database\Eloquent\Model;

#[Unguarded]
class Flight extends Model
{
    // ...
}

Mass Assignment Exceptions

Mặc định, các thuộc tính không có trong mảng $fillable sẽ bị loại ra khi thực hiện các thao tác mass-assignment. Trong môi trường production, đây là hành vi được mong đợi; tuy nhiên, trong quá trình phát triển ở local, điều này có thể dẫn đến sự nhầm lẫn về lý do tại sao có những thay đổi về mặt model lại không có hiệu lực.

Nếu muốn, bạn có thể hướng dẫn Laravel đưa ra một ngoại lệ khi cố gắng đưa vào một thuộc tính không thể đưa bằng cách gọi phương thức preventSilentlyDiscardingAttributes. Thông thường, phương thức này nên được gọi trong phương thức boot của class AppServiceProvider của ứng dụng của bạn:

use Illuminate\Database\Eloquent\Model;

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

Upserts

Phương thức upsert của Eloquent có thể được sử dụng để cập nhật hoặc tạo bản ghi trong một thao tác duy nhất. Tham số đầu tiên của phương thức sẽ chứa các giá trị để thêm hoặc cập nhật, trong khi tham số thứ hai là liệt kê (các) cột để xác định tính duy nhất của các bản ghi trong bảng cơ sở dữ liệu. Tham số thứ ba và cũng là tham số cuối cùng của phương thức là một mảng các cột sẽ được cập nhật nếu một bản ghi phù hợp đã tồn tại trong cơ sở dữ liệu. Phương thức upsert sẽ tự động set timestamp cho cột created_atupdated_at nếu timestamp được enabled trên model:

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

[!WARNING] Tất cả các cơ sở dữ liệu ngoại trừ SQL Server đều yêu cầu các cột trong tham số thứ hai của phương thức upsert phải có một cột "primary" hoặc một "unique" index trong đó. Ngoài ra, driver cơ sở dữ liệu MariaDB và MySQL sẽ bỏ qua tham số thứ hai của phương thức upsert và luôn sử dụng các "primary" và "unique" indexe của bảng để phát hiện các bản ghi hiện có.

Xoá Model

Để xóa một model, bạn có thể gọi phương thức delete trên instance model đó:

use App\Models\Flight;

$flight = Flight::find(1);

$flight->delete();

Nếu bạn muốn xóa model trong một database transaction, bạn có thể sử dụng phương thức deleteOrFail. Nếu một ngoại lệ được đưa ra trong quá trình xóa, transaction sẽ tự động được rollback:

$flight->deleteOrFail();

Deleting An Existing Model By Its Primary Key

Trong ví dụ trên, chúng ta đang lấy một model từ cơ sở dữ liệu trước khi gọi phương thức delete. Tuy nhiên, nếu bạn biết khóa chính của model, bạn có thể xóa trực tiếp model này mà không cần phải truy xuất nó ra bằng cách gọi phương thức destroy. Ngoài một khóa chính làm tham số của nó ra, phương thức destroy cũng sẽ chấp nhận nhiều khóa chính cùng một lúc như một mảng khóa chính hoặc một collection khóa chính:

Flight::destroy(1);

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

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

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

Nếu bạn đang sử dụng model soft delete, bạn có thể xóa vĩnh viễn các model thông qua phương thức forceDestroy:

Flight::forceDestroy(1);

[!WARNING] Phương thức destroy sẽ load từng model và gọi phương thức delete trên từng model đó để kích hoạt các event deletingdeleted.

Deleting Models Using Queries

Bạn cũng có thể chạy một câu lệnh xóa trên một tập các model. Trong ví dụ này, chúng ta sẽ xóa tất cả các flight có đánh dấu là không hoạt động. Giống như mass update, mass delete cũng sẽ không kích hoạt bất kỳ event nào của model khi các model bị xóa:

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

Để xóa tất cả các model có trong một bảng, bạn nên thực hiện truy vấn mà không cần thêm bất kỳ điều kiện nào:

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

[!WARNING] Khi thực hiện câu lệnh mass delete thông qua Eloquent, các event model như là deletingdeleted sẽ không được kích hoạt cho các model đã bị xóa. Điều này là do các model đã không được lấy ra khi thực hiện câu lệnh xóa.

Soft Delete

Ngoài việc xóa các bản ghi ra khỏi cơ sở dữ liệu của bạn, Eloquent cũng có thể sử dụng "soft delete" cho các model. Khi các model bị soft delete, thì chúng sẽ không thực sự bị xóa ra khỏi cơ sở dữ liệu của bạn. Thay vào đó, một thuộc tính deleted_at sẽ được set vào model cho biết ngày và giờ model bị "xóa". Để kích hoạt soft delete cho một model, hãy thêm trait Illuminate\Database\Eloquent\SoftDeletes trên model:

<?php

namespace App\Models;

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

class Flight extends Model
{
    use SoftDeletes;
}

[!NOTE] Trait SoftDeletes sẽ tự động cast thuộc tính deleted_at thành một instance DateTime / Carbon cho bạn.

Bạn cũng cần thêm cột deleted_at vào bảng cơ sở dữ liệu của bạn. Schema builder của Laravel có chứa một phương thức helper để tạo cột này:

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

Bây giờ, khi bạn gọi phương thức delete trên model, cột deleted_at sẽ được set thành ngày giờ của hiện tại. Tuy nhiên, bản ghi cơ sở dữ liệu của model sẽ được để lại trong bảng. Khi truy vấn một model mà có sử dụng soft delete, thì các model mà đã bị soft delete thì sẽ bị tự động loại khỏi ra tất cả các kết quả truy vấn.

Để xác định xem một instance model đã cho có bị soft delete hay chưa, Bạn có thể sử dụng phương thức trashed:

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

Restoring Soft Deleted Models

Thỉnh thoảng bạn có thể muốn "un-delete" một model đã soft delete. Để khôi phục một model đã soft delete, bạn có thể gọi phương thức restore trên một instance model. Phương thức restore sẽ set lại cột deleted_at của model thành null:

$flight->restore();

Bạn cũng có thể sử dụng phương thức restore trong truy vấn để khôi phục nhiều model. Một lần nữa, giống như các hoạt động "mass" khác, thao tác này sẽ không gửi bất kỳ event model nào cho các model được khôi phục:

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

Phương thức restore cũng có thể được sử dụng khi xây dựng các truy vấn relationship:

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

Permanently Deleting Models

Thỉnh thoảng bạn có thể cần phải thực sự xóa một model ra khỏi cơ sở dữ liệu của bạn. Bạn có thể sử dụng phương thức forceDelete để xóa vĩnh viễn model soft delete ra khỏi bảng cơ sở dữ liệu đó:

$flight->forceDelete();

Bạn cũng có thể sử dụng phương thức forceDelete trên các query quan hệ của Eloquent:

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

Query Model Soft Deleted

Including Soft Deleted Models

Như đã lưu ý ở trên, các model bị soft delete sẽ bị tự động loại ra khỏi tất cả các kết quả truy vấn. Tuy nhiên, bạn có thể cho các model đã bị soft delete vào kết quả của truy vấn bằng cách gọi phương thức withTrashed trong câu truy vấn:

use App\Models\Flight;

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

Phương thức withTrashed cũng có thể được gọi khi tạo query cho quan hệ:

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

Retrieving Only Soft Deleted Models

Phương thức onlyTrashed sẽ chỉ truy xuất vào các model đã bị soft deleted:

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

Pruning Models

Thỉnh thoảng bạn có thể muốn xóa định kỳ các model không còn cần thiết nữa. Để thực hiện điều này, bạn có thể thêm trait Illuminate\Database\Eloquent\Prunable hoặc trait Illuminate\Database\Eloquent\MassPrunable cho các model mà bạn muốn thực hiện xoá định kỳ. Sau khi thêm một trong các trait vào model, hãy implement phương thức prunable và trả về một Eloquent query builder để tìm ra các model không còn cần thiết:

<?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()->minus(months: 1));
    }
}

Khi đánh dấu model là Prunable, bạn cũng có thể định nghĩa một thức phương thức pruning trong model. Phương thức này sẽ được gọi trước khi model bị xóa. Phương thức này có thể hữu ích để xóa thêm bất kỳ resource nào được liên kết với model, chẳng hạn như các file được lưu trữ, trước khi model bị xóa vĩnh viễn khỏi cơ sở dữ liệu:

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

Sau khi cấu hình model prunable của bạn, bạn nên tạo schedule chạy lệnh model:prune Artisan trong file routes/console.php của ứng dụng của bạn. Bạn có thể tự do chọn khoảng thời gian thích hợp để chạy lệnh này:

use Illuminate\Support\Facades\Schedule;

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

Hậu trường, lệnh model:prune sẽ tự động tìm các model "Prunablec" trong thư mục app/Models của ứng dụng của bạn. Nếu các model của bạn ở một vị trí khác, thì bạn có thể sử dụng tùy chọn --model để chỉ định tên class của model:

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

Nếu bạn muốn bỏ qua một số model ra khỏi pruned trong khi đang pruning tất cả các model khác, thì bạn có thể sử dụng tùy chọn --except:

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

Bạn có thể kiểm tra truy vấn prunable của bạn bằng cách thực hiện lệnh model:prune với tùy chọn --pretend. Khi chạy với tùy chọn đó, lệnh model:prune sẽ chỉ báo cáo ra là có bao nhiêu record sẽ bị pruned nếu lệnh này thực sự chạy:

php artisan model:prune --pretend

[!WARNING] Các model soft delete sẽ bị xóa vĩnh viễn (forceDelete) nếu chúng phù hợp với câu lệnh truy vấn prunable.

Mass Pruning

Khi các model được đánh dấu bằng trait Illuminate\Database\Eloquent\MassPrunable, thì các model đó sẽ bị xóa ra khỏi cơ sở dữ liệu bằng truy vấn mass-deletion. Do đó, phương thức pruning sẽ không được gọi, cũng như các event model deletingdeleted cũng sẽ không được gọi. Điều này là do các model không thực sự được lấy ra trước khi xóa, do đó làm cho quá trình pruning hiệu quả hơn:

<?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()->minus(months: 1));
    }
}

Replicating Models

Bạn có thể tạo một bản sao chưa lưu của một instance model đã tồn tại bằng cách sử dụng phương thức replicate. Phương thức này đặc biệt hữu ích khi bạn có các instance model dùng chung nhiều thuộc tính giống nhau:

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

Để bỏ qua một hoặc nhiều thuộc tính sẽ không replicate lại sang model mới, bạn có thể truyền một mảng cho phương thức replicate:

$flight = Flight::create([
    'destination' => 'LAX',
    'origin' => 'LHR',
    'last_flown' => '2020-03-04 11:00:00',
    'last_pilot_id' => 747,
]);

$flight = $flight->replicate([
    'last_flown',
    'last_pilot_id'
]);

Query Scope

Global Scope

Global scope cho phép bạn thêm các ràng buộc cho tất cả các truy vấn của một model nhất định. Chức năng soft delete của Laravel cũng sử dụng global scope để chỉ lấy ra các model "non-deleted" ra khỏi cơ sở dữ liệu. Viết global scope của riêng bạn có thể cung cấp một cách thuận tiện và dễ dàng để đảm bảo rằng mọi truy vấn cho một model nhất định đều có được các ràng buộc nhất định.

Generating Scopes

Để tạo một global scope mới, bạn có thể gọi lệnh Artisan make:scope, lệnh này sẽ lưu scope đã tạo vào thư mục app/Models/Scopes của ứng dụng:

php artisan make:scope AncientScope

Writing Global Scopes

Viết một global scope rất đơn giản. Đầu tiên, dùng lệnh make:scope để tạo một class implement từ interface Illuminate\Database\Eloquent\Scope. Interface Scope sẽ yêu cầu bạn implement một phương thức: apply. Phương thức apply có thể thêm các ràng buộc where hoặc các điều kiện khác cho các truy vấn khi cần:

<?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()->minus(years: 2000));
    }
}

[!NOTE] Nếu global scope của bạn đang thêm các cột vào trong câu lệnh select, thì bạn nên sử dụng phương thức addSelect thay vì select. Điều này sẽ ngăn việc bạn vô tình thay thế lệnh select hiện tại của truy vấn.

Applying Global Scopes

Để gán một global scope cho một model, bạn có thể chỉ cần đặt thêm một thuộc tính ScopedBy vào model:

<?php

namespace App\Models;

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

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

Hoặc, bạn có thể tự đăng ký global scope bằng cách ghi đè phương thức booted của model và gọi phương thức addGlobalScope của model. Phương thức addGlobalScope chấp nhận một instance scope làm tham số duy nhất của nó:

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

Sau khi thêm scope trong ví dụ trên vào model App\Models\User, lệnh gọi phương thức User::all() sẽ chạy truy vấn SQL sau:

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

Anonymous Global Scopes

Eloquent cũng cho phép bạn định nghĩa global scope bằng cách sử dụng closures, điều này đặc biệt hữu ích cho các scope đơn giản không cần phải tạo một class riêng cho nó. Khi định nghĩa global scope bằng closure xong, bạn nên cung cấp tên scope của bạn làm tham số đầu tiên cho phương thức addGlobalScope:

<?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()->minus(years: 2000));
        });
    }
}

Removing Global Scopes

Nếu bạn muốn xóa một global trong cho một truy vấn nhất định, bạn có thể sử dụng phương thức withoutGlobalScope. Phương thức này chấp nhận tên class của global scope làm tham số duy nhất của nó:

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

Hoặc, nếu bạn đã định nghĩa global scope mà dùng closure, thì bạn nên truyền tên của scope đó:

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

Nếu bạn muốn xóa một vài hoặc thậm chí là tất cả các global scope của query, bạn cũng có thể sử dụng phương thức withoutGlobalScopes và phương thức withoutGlobalScopesExcept:

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

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

// Remove all global scopes except the given ones...
User::withoutGlobalScopesExcept([
    SecondScope::class,
])->get();

Local Scope

Local scope cho phép bạn định nghĩa một nhóm ràng buộc query chung mà bạn có thể dễ dàng sử dụng lại trong suốt qua trình xử lý của application của bạn. Ví dụ: bạn có thể cần thường xuyên truy xuất tất cả người dùng được coi là "popular". Để định nghĩa một scope, hãy thêm thuộc tính Scope vào một phương thức Eloquent.

Scope sẽ luôn phải trả về một instance query builder giống nhau hoặc 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

Khi một scope đã được định nghĩa xong, bạn có thể gọi phương thức scope khi truy vấn model. Bạn thậm chí có thể kết hợp nó với các scope khác:

use App\Models\User;

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

Việc kết hợp nhiều scope cho model Eloquent thông qua truy vấn or có thể yêu cầu sử dụng closure để nhóm logic một cách chính xác nhất:

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

Tuy nhiên, vì điều này có thể phức tạp, Laravel cung cấp một phương thức "higher order" là orWhere cho phép bạn kết hợp các scope với nhau một cách thuận tiện mà không cần sử dụng closure:

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

Dynamic Scopes

Thỉnh thoảng bạn cũng có thể muốn định nghĩa một scope nhận vào các tham số. Để bắt đầu, chỉ cần thêm các tham số bổ sung vào trong câu lệnh trong phương thức scope của bạn. Các tham số scope cần được định nghĩa sau tham số $query:

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

Khi các tham số được thêm vào định dạng của phương thức scope, bạn có thể truyền tham số khi gọi scope:

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

Pending Attributes

Nếu bạn muốn sử dụng scope để tạo các model có cùng thuộc tính với các thuộc tính được sử dụng để hạn chế scope, bạn có thể sử dụng phương thức withAttributes khi xây dựng truy vấn scope:

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

Phương thức withAttributes sẽ thêm các điều kiện where vào truy vấn bằng cách sử dụng các thuộc tính đã cho và cũng sẽ thêm các thuộc tính đó vào bất kỳ model nào được tạo thông qua scope:

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

$draft->hidden; // true

Để hướng dẫn phương thức withAttributes không thêm các điều kiện where vào truy vấn, bạn có thể set tham số asConditions thành false:

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

So sánh Model

Thỉnh thoảng bạn có thể cần xác định xem hai model có "giống nhau" hay không. Phương thức isisNot có thể được sử dụng để xác minh hai model đó có cùng khóa chính,cùng bảng và cùng kết nối cơ sở dữ liệu:

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

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

Các phương thức isisNot cũng khả dụng khi sử dụng các quan hệ belongsTo, hasOne, morphTomorphOne . Phương thức này đặc biệt hữu ích khi bạn muốn so sánh một model quan hệ mà không cần chạy truy vấn để lấy model đó ra:

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

Event

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

Các eloquent model sẽ kích hoạt một số event, cho phép bạn hook đến các chỗ khác trong vòng đời của một model: retrieved, creating, created, updating, updated, saving, saved, deleting, deleted, trashed, forceDeleting, forceDeleted, restoring, restored, và replicating.

Event retrieved sẽ được kích hoạt khi một model được lấy ra khỏi cơ sở dữ liệu. Khi một model mới được lưu vào lần đầu tiên, các event creatingcreated sẽ kích hoạt. Các event updating / updated sẽ kích hoạt khi một model đang tồn tại có sửa đổi và gọi đến phương thức save. Các event saving / saved sẽ kích hoạt khi một model mới được tạo hoặc cập nhật - thậm chí cả khi các thuộc tính của model đó không bị thay đổi. Các tên event kết thúc bằng -ing được gửi đi trước khi bất kỳ thay đổi nào của model được lưu, trong khi các event kết thúc bằng -ed sẽ được gửi sau khi các thay đổi của model được lưu.

Để bắt đầu, hãy định nghĩa một thuộc tính $dispatchesEvents trên model Eloquent của bạn để nối các thời điểm khác nhau trong vòng đời của model Eloquent đó vào các event classes của bạn. Mỗi class event của model sẽ nhận được một instance của model bị ảnh hưởng thông qua hàm tạo của nó:

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

Sau khi định nghĩa và ánh xạ các event Eloquent của bạn, bạn có thể sử dụng event listeners để xử lý các event đó.

[!WARNING] Khi bạn cập nhật một loạt dữ liệu thông qua Eloquent, thì các event của model như saved, updated, deleting, và deleted sẽ không được kích hoạt cho các model đó. Điều này là do các model không thực sự được lấy ra khi bạn chạy các cập nhật hoặc xoá bỏ.

Dùng Closures

Thay vì sử dụng các class event tùy biến, bạn có thể đăng ký một closures để được chạy khi các event model khác nhau được gửi. Thông thường, bạn nên đăng ký các closures này trong phương thức booted của model của bạn:

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

Nếu cần, bạn có thể sử dụng một queue event listener ẩn danh khi đăng ký event model. Thao tác này sẽ hướng dẫn Laravel thực thi event listener của model trong background bằng cách sử dụng queue của ứng dụng của bạn:

use function Illuminate\Events\queueable;

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

Observer

Defining Observers

Nếu bạn đang listen nhiều event trên một model, bạn có thể sử dụng các observer để nhóm tất cả các listen của bạn vào trong một class duy nhất. Các class observer có tên phương thức chính là tên các event Eloquent mà bạn muốn listen. Mỗi phương thức này nhận vào model bị ảnh hưởng làm tham số duy nhất của chúng. Lệnh Artisan make:Observer là cách dễ nhất để tạo một class observer mới:

php artisan make:observer UserObserver --model=User

Lệnh này sẽ lưu file observer mới vào trong thư mục app/Observers của bạn. Nếu thư mục này không tồn tại, Artisan sẽ tạo nó cho bạn. Class observer mới của bạn sẽ trông giống như sau:

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

Để đăng ký một observer, bạn có thể thêm thuộc tính ObservedBy vào model:

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

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

Hoặc, bạn có thể tự đăng ký một observer bằng cách gọi phương thức observe trên model mà bạn muốn observe. Bạn có thể đăng ký observe trong phương thức boot của class AppServiceProvider của application:

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

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

[!NOTE] Có thêm các event mà observer có thể listen, chẳng hạn như savingretrieved. Những event này được mô tả trong tài liệu events.

Observers và Database Transactions

Khi các model đang được tạo trong một database transaction, bạn có thể muốn hướng dẫn một observer chỉ thực hiện các event của nó sau khi database transaction được thực hiện. Bạn có thể thực hiện việc này bằng cách implement interface ShouldHandleEventsAfterCommittrên observer của bạn. Nếu một database transaction không được thực hiện, thì event đó sẽ được thực thi ngay lập tức:

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

Tắt event

Đôi khi bạn có thể cần tạm thời "tắt" tất cả các event do một model kích hoạt. Bạn có thể làm được điều này bằng cách sử dụng phương thức withoutEvents. Phương thức withoutEvents chấp nhận một closure làm tham số duy nhất của nó. Bất kỳ code nào được chạy trong closure này sẽ không gửi bất kỳ event nào của model và bất kỳ giá trị nào được trả về bởi closure cũng là giá trị sẽ được trả về bởi phương thức withoutEvents:

use App\Models\User;

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

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

Saving A Single Model Without Events

Thỉnh thoảng bạn có thể muốn "lưu" một model nhất định mà không gửi bất kỳ event nào. Bạn có thể thực hiện việc này bằng cách sử dụng phương thức saveQuietly:

$user = User::findOrFail(1);

$user->name = 'Victoria Faith';

$user->saveQuietly();

Bạn cũng có thể "update", "delete", "soft delete", "restore", và "replicate" một model nhất định mà không gửi bất kỳ event nào:

$user->deleteQuietly();
$user->forceDeleteQuietly();
$user->restoreQuietly();
MongoDB Relationships
© 2023 by Logo page doc-vn