Học Laravel – Bài 30: Eloquent

Eloquent là 1 object-relational mapper (ORM) để tương tác với db. Mỗi db table sẽ có một model tương ứng.

Tạo model class:

Tạo Eloquent model trong thư mục app/Models, class này extend Illuminate\Database\Eloquent\Model. Sử dụng lệnh artisan để tạo model:

php artisan make:model Flight

Nếu muốn khởi tạo db migration khi tạo model, sử dụng option --migration hoặc -m:

php artisan make:model Flight --migration

Bạn có thể tạo nhiều kiểu class khác nhau khi khởi tạo model, ví dụ factory, seeder, policy, controller, form request. Những option này có thể kết hợp để tạo nhiều class cùng 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
 
# Generate a pivot model...
php artisan make:model Member --pivot
php artisan make:model Member -p

Lệnh model:show sẽ cung cấp tổng quan về các thuộc tính và mối quan hệ của model:

php artisan model:show Flight

Eloquent Model Conventions:

Model khởi tạo bởi make:model sẽ nằm trong thư mục app/Models:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    // ...
}

Table name:

Theo quy ước, “snake case”, tên số nhiều của class sẽ được sử dụng làm table name. Trong trường hợp này Eloquent Flight sẽ chứa record của table flights. Tương tự AirTrafficController model sẽ chứa bản ghi của table air_traffic_controllers.

Nếu table tương ứng của model không theo quy ước, bạn cần chỉ định bằng thuộc tính table trong model:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'my_flights';
}

Primary keys:

Eloquent giả định mỗi model có 1 primary key tên là id. Nếu cần thiết bạn có thể define 1 protected $primaryKey:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
     * The primary key associated with the table.
     *
     * @var string
     */
    protected $primaryKey = 'flight_id';
}

Timestamps:

Mặc định Eloquent có 2 column created_atupdated_at, Eloquent tự động set giá trị những cột này khi create hoặc update. Nếu bạn không muốn, set thuộc tính $timestamps thành false:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
     * Indicates if the model should be timestamped.
     *
     * @var bool
     */
    public $timestamps = false;
}

Database connections:

Eloquent sử dụng kết nối db mặc định, nếu bạn muốn chỉ định 1 connect khác, bạn cần set thuộc tính $connection trong model:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
     * The database connection that should be used by the model.
     *
     * @var string
     */
    protected $connection = 'sqlite';
}

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

Mặc định 1 instance model mới khởi tạo sẽ không có giá trị mặc định. Nếu bạn muốn set giá trị mặc định cho 1 số thuộc tính của model, bạn cần define thuộc tính $attribute. Giá trị mặc định phải ở nguyên bản, định dạng có thể lưu trữ được:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
     * The model's default values for attributes.
     *
     * @var array
     */
    protected $attributes = [
        'options' => '[]',
        'delayed' => false,
    ];
}

Cấu hình nghiêm ngặt:

Laravel cung cấp nhiều method cho phép bạn cấu hình hành vi và mức độ nghiệm ngặt của Eloquent trong nhiều tình huống khác nhau. Đầu tiên, method preventLazyLoading chấp nhận 1 tham số tùy chọn là boolean biểu thị lazy loading có bị chặn hay không. Ví dụ bạn muốn chỉ disable lazy loading ở môi trường non-production để môi trường product có thể thực hiện bình thường thậm chí cả khi lazy load relationship vô tình có mặt trong production code. Thông thường method này được gọi ở boot của AppServiceProvider:

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

Bạn cũng có thể hướng dẫn Laravel ném 1 ngoại lệ khi thử fill 1 thuộc tính không thể fill bằng cách gọi method preventSilentlyDiscardingAttributes. Điều này giúp ngăn chặn những lỗi không mong muốn trong quá trình local dev khi thử set 1 thuộc tính mà không được add vào fillable array:

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

Truy xuất model:

Truy xuất toàn bộ record:

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

Building query:

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

Vì Eloquent chính là query builder, bạn có thể sử dụng tất cả các method của Laravel query builder.

Refresh models:

Nếu bạn đã có 1 instance của 1 Eloquent model đã được truy xuất từ database, bạn có thể “refresh” model sử dụng freshrefresh method.

Method fresh sẽ truy xuất lại model từ database. Model instance đã tồn tại sẽ không bị ảnh hưởng:

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

Method refresh sẽ làm mới lại model đã tồn tại sử dụng data mới nhất từ database. Tất cả những relationship đã load cũng sẽ được refresh:

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

Collections:

Eloquent method allget truy xuất nhiều record từ database. Tuy nhiên những method này không trả về PHP array mà trả về 1 instance Illuminate\Database\Eloquent\Collection. Class này extend Illuminate\Support\Collection, cung cấp nhiều method để xử lý data collections. Ví dụ method reject để remove model từ 1 collection dựa trên kết quả của 1 closure được gọi:

$flights = Flight::where('destination', 'Paris')->get();
 
$flights = $flights->reject(function (Flight $flight) {
    return $flight->cancelled;
});

Một số method tại: https://laravel.com/docs/10.x/eloquent-collections#available-methods

Phân đoạn kết quả:

Ứng dụng có thể tràn bộ nhớ nếu cố gắng load hàng chục nghìn dữ liệu thông qua method all hoặc get. Dùng chunk method để xử lý dữ liệu lớn. Method này truy xuất 1 phần Eloquent model, pass nó vào 1 closure để xử lý:

use App\Models\Flight;
use Illuminate\Database\Eloquent\Collection;
 
Flight::chunk(200, function (Collection $flights) {
    foreach ($flights as $flight) {
        // ...
    }
});

Nếu bạn lọc kết quả của chunk dựa trên 1 column mà bạn cũng có thể update trong khi loop, bạn nên sử dụng method chunkById. Method này truy xuất model với column id:

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

Chunk sử dụng lazy collections:

Method lazy tương tự chunk, tuy nhiên thay vì pass mỗi chunk đến 1 callback, lazy return 1 LazyCollection của Eloquent, cho phép bạn xử lý kết quả như 1 single stream:

use App\Models\Flight;
 
foreach (Flight::lazy() as $flight) {
    $flight->update(['departed' => false]);
}

Nếu bạn muốn lọc kết quả dựa trên column mà bạn cũng sẽ update khi loop, bạn nên sử dụng lazyById. Method này sẽ truy xuất model với 1 id column:

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ự DESC của id sử dụng method lazyByIdDesc.

Cursors:

Tương tự lazy method, cursor method có thể sử dụng để giảm được bộ nhớ ứng dụng khi loop hàng chục ngàn Eloquent model. Method cursor chỉ thực thi 1 query, tuy nhiên Eloquent model sẽ không hydrated cho đến khi loop được hoàn thành. Nhờ vậy chỉ 1 Eloquent được giữ trong bộ nhớ trong khi loop cursor.

Do method cursor chỉ giữ 1 single Eloquent model trong bộ nhớ, nó không thể load các relationship. Nếu bạn muốn load relationship, sử dụng method lazy. Method cursor sử dụng PHP generators:

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

Method cursor trả về 1 instance của Illuminate\Support\LazyCollection, cho phép bạn sử dụng nhiều method trong Laravel collection:

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ù method cursor sử dụng ít bộ nhớ hơn bằng cách sử dụng duy nhất 1 Eloquent trong bộ nhớ), nó vẫn có thể tràn bộ nhớ. Lý do là vì PHP PDO cache tất cả kết quả query vào buffer. Nếu bạn muốn truy vấn lượng dữ liệu lớn hơn, sử dụng method lazy.

Subquery:

Subquery select:

Eloquent hỗ trợ subquery, cho phép bạn thêm thông tin từ table liên quan vào trong single query. Ví dụ bạn có 1 table flight destinations và table flights. Table flights có column arrived_at chỉ ra khi nào chuyến bay đến đích. Sử dụng subquery selectaddSelect method, bạn có thể select toàn bộ những destinations và tên chuyến bay gần đây nhất đến destination đó:

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 order:

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

Truy xuất single model / tập hợp:

Để truy xuất single model, sử dụng các method find, first, firstWhere. Những method này trả về 1 single model thay vì trả về 1 collection:

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ực thi 1 tác vụ khác nếu không có kết quả trả về: sử dụng method findOr, firstOr. Nếu không có kết quả, method này sẽ thực thi closure, kết quả trả về bởi closure sẽ là kết quả của method:

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

Not Found Exception:

Thỉnh thoảng bạn muốn ném 1 ngoại lệ nếu model không có. Method findOrFailfirstOrFail sẽ ném 1 ngoại lệ Illuminate\Database\Eloquent\ModelNotFoundException nếu không có bản ghi nào được tìm thấy:

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

Nếu ngoại lệ ModelNotFoundException không được bắt, 404 response sẽ được trả về user:

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

Truy xuất hoặc tạo model:

Method firstOrCreate sẽ truy xuất 1 bản ghi đầu tiên, hoặc nếu không có, 1 bản ghi sẽ được chèn vào với thuộc tính là kết quả từ việc merge tham số array đầu tiên với tham số tùy chọn thứ 2. Method firstOrNew tương tự firstOrCreate, tuy nhiên nếu model không tìm thấy, 1 model instance mới sẽ được trả về. Model này không được chèn vào database. Bạn có thể gọi hàm save để chèn vào db:

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

Truy vấn tập hợp:

Sử dụng method count, sum, max và 1 số method khác của query builder để truy vấn tập hợp. Những method này trả về 1 giá trị vô hướng thay vì Eloquent model:

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

Insert và Update:

Insert: sử dụng method save:

<?php
 
namespace App\Http\Controllers;
 
use App\Http\Controllers\Controller;
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');
    }
}

Sử dụng method create để lưu 1 model mới vào database, model được insert này sẽ được trả về bởi method:

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

Update:

Method save có thể dùng để update model đã tồn tại trong database. Để update model, bạn nên truy vấn nó từ database và sau đó set các thuộc tính cần thay đổi. Thuộc tính updated_at sẽ tự động được update:

use App\Models\Flight;
 
$flight = Flight::find(1);
 
$flight->name = 'Paris to London';
 
$flight->save();

Mass Update:

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

Kiểm tra thay đổi thuộc tính:

Eloquent cung cấp method isDirty, isClean, wasChanged để kiểm tra trạng thái bên trong của model và xác minh cách mà thuộc tính đã thay đổi từ khi model được truy xuất.

Method isDirty xác minh có thuộc tính nào đã thay đổi kể từ khi model được truy xuất. Bạn có thể pass 1 tên thuộc tính hoặc 1 array để xác minh có thuộc tính nào đã thay đổi. Method isClean sẽ xác định 1 thuộc tính vẫn giữ nguyên kể từ khi được truy xuất:

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

Method wasChanged xác minh có thuộc tính nào đã thay đổi khi model đã lưu lần cuối. Bạn có thể pass 1 tên thuộc tính để xác minh nó có thay đổi 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

Method getOriginal trả về 1 array chứa thuộc tính nguyên bản của model bất kể thay đổi gì kể từ khi nó được truy xuất:

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

Mass Assignment:

Bạn có thể sử dụng method create để lưu model mới, model này được trả về bởi method:

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

Tuy nhiên trước khi sử dụng method create bạn cần chỉ định thuộc tính là fillable hay guarded. Những thuộc tính này là yêu cầu vì Eloquent model được bảo vệ chống lại các lỗ hổng mass assignment.

Vì vậy bạn cần define thuộc tính nào bạn muốn make mass assignable. Bạn có thể thực hiện điều đó sử dụng thuộc tính $fillable. Ví dụ:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['name'];
}

Một khi bạn chỉ định thuộc tính nào là có thể mass assignment, bạn có thể sử dụng method create để chèn record mới. Method này trả về model mới được tạo:

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

Nếu bạn đã có 1 model instance, bạn có thể sử dụng method fill để lưu nó với 1 array thuộc tính:

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

Method fill giúp bạn lọc những thuộc tính không fillable, nghĩa là chỉ những thuộc tính fillable mới được update.

Cho phép mass assignment:

Nếu bạn muốn tất cả thuộc tính đều mass assignable, bạn có thể define thuộc tính $guarded là empty array. Nếu bạn chọn unguard model, bạn luôn xử lý thủ công array được pass vào method fill, create, update:

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

Mass assignment exception:

Mặc định thuộc tính không include trong $fillable sẽ được bỏ đi khi thực hiện mass assignment. Trong production đó là hành vi được mong đợi, tuy nhiên ở local dev có thể gây ra nhầm lẫn như vì sao model không update. Bạn có thể ném 1 ngoại lệ khi thử fill những thuộc tính unfillable bằng cách gọi method preventSilentlyDiscardingAttributes. Method này được gọi ở boot của service provider:

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

Upsert:

Method updateOrCreate sẽ update 1 model có sẵn hoặc tạo model mới nếu không có model khớp:

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

Nếu bạn muốn thực hiện nhiều “upserts” trong 1 single query, sử dụng method upsert. Tham số đầu tiên bao gồm các giá trị để insert hoặc update, tham số thứ 2 là list các column unique xác định record. Tham số thứ 3 là array các column sẽ được update nếu có record khớp. Method này cũng tự động update created_atupdated_at:

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

Tất cả database ngoại trừ SQL server yêu cầu các column trong tham số thứ 2 là unique hoặc primary. Ngoài ra, MySQL database bỏ qua tham số thứ 2 và luôn sử dụng primary và unique index để xác định record tồn tại.

Delete model: để xóa một model, sử dụng method delete:

use App\Models\Flight;
 
$flight = Flight::find(1);
 
$flight->delete();

Sử dụng method truncate để xóa tất cả bản ghi và reset auto-increment id:

Flight::truncate();

Xóa 1 model tồn tại bằng primary key:

Sử dụng method destroy, method này chấp nhận nhiều primary key, hoặc 1 mảng primary key, hoặc 1 collection primary key:

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

Method này load từng model riêng và gọi delete method do đó event deletingdeleted được gửi đi cho mỗi model.

Xóa những model khớp query:

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

Khi mass delete, event không được kích hoạt do không có model nào được truy xuất.

Soft delete: Eloquent cung cấp “soft delete” cho phép đánh dấu xóa mà không thực sự xóa bản ghi khỏi database. Thay vào đó 1 thuộc tính deleted_at sẽ được set để xác định thời gian xóa. Thêm Illuminate\Database\Eloquent\SoftDeletes vào model:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
 
class Flight extends Model
{
    use SoftDeletes;
}

Bạn cũng cần thêm cột deleted_at vào bảng sử dụng schema:

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

Để xác minh 1 model đã “soft delete” chưa, sử dụng method trashed:

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

Khôi phục Soft deleted model: sử dụng method restore, method này sẽ set deleted_at thành null:

$flight->restore();

Bạn có thể dùng restore trong query để restore nhiều model (và cũng không kích hoạt bất kỳ sự kiện nào):

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

Xóa hoàn toàn model:

$flight->forceDelete();

Query soft deleted models:

Include soft deleted models:

Mặc định soft deleted model sẽ exclude từ query. Tuy nhiên bạn có thể include bằng method withTrashed:

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

Truy xuất chỉ soft deleted models:

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

Pruning models:

Xóa định kỳ những model không cần thiết bằng Illuminate\Database\Eloquent\Prunable hoặc Illuminate\Database\Eloquent\MassPrunable. Sau khi add vào model, implement prunable method mà trả về 1 Eloquent query builder truy xuất những models không dùng nữa:

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

Khi mark 1 model là Prunable, bạn có thể define 1 pruning method cho model. Method này sẽ được gọi trước khi model bị xóa. Method này sẽ xóa các tài nguyên liên quan đến model, ví dụ file lưu trữ, trước khi model được xóa hoàn toàn khỏi database:

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

Sau khi config prunable model, bạn nên lập lịch model:prune bằng lệnh Artisan command trong App\Console\Kernel class. Bạn có thể chọn khoảng thời gian thích hợp để command được chạy:

/**
 * Define the application's command schedule.
 */
protected function schedule(Schedule $schedule): void
{
    $schedule->command('model:prune')->daily();
}

Bên trong model:prune lệnh sẽ tự động xác định các model “prunable” trong app/Models. Nếu model ở trong thư mục khác, sử dụng option --model để chỉ định tên class:

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

Nếu bạn muốn exclude model:

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

Bạn có thể test prunable query bằng cách thực thi command model:prune với tham số --pretend. Lệnh sẽ show số record sẽ được xóa nếu command run:

php artisan model:prune --pretend

Soft deleting model sẽ được xóa hoàn toàn (forceDelete) nếu khớp query prunable.

Mass pruning:

Khi 1 model mark với Illuminate\Database\Eloquent\MassPrunable, model được xóa từ database sử dụng mass-delete query. Tuy nhiên, method pruning sẽ không được gọi, và cả event deleting, deleted không được kích hoạt. Lý do là vì model không được truy xuất trước khi xóa:

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

Replicate model:

Bạn có thể tạo 1 bản copy chưa lưu của 1 model có sẵn sử dụng method replicate:

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

Để exclude 1 số thuộc tính từ replicate model:

$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 ràng buộc cho tất cả query cho 1 model nào đó. Laravel có soft delete sử dụng global scope để chỉ truy vấn những model chưa “xóa” từ database. Code riêng global scope giúp bạn dễ dàng kiểm soát query cho một ràng buộc nào đó.

Tạo scope: dùng lệnh artisan để tạo file trong thư mục app/Models/Scopes:

php artisan make:scope AncientScope

Class cần implement method apply của interface Illuminate\Database\Eloquent\Scope:

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

Nếu global scope thêm cột vào mệnh đề select trong query, bạn nên dùng addSelect thay vì select để tránh vô tình ghi đè mệnh đề select của query đang có.

Áp dụng global scope:

Để áp dụng global scope vào model, bạn cần ghi đè phương thức booted và gọi method addGlobalScope:

<?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 áp dụng global scope, khi gọi User::all() sẽ thực hiện SQL sau:

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

Global scope ẩn danh:

Sử dụng closure định nghĩa 1 global scope, sử dụng method addGlobalScope với tham số đầu tiên là tên của scope:

protected static function booted(): void
{
    static::addGlobalScope('ancient', function (Builder $builder) {
        $builder->where('created_at', '<', now()->subYears(2000));
    });
}

Xóa global scope:

Để xóa global scope cho 1 query, sử dụng method withoutGlobalScope:

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

Hoặc nếu global scope là closure, chỉ cần pass tên scope vào method:

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

Xóa nhiều hoặc toàn bộ global scope cho query:

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

Local scope: cho phép bạn định nghĩa 1 nhóm ràng buộc query mà có thể tái sử dụng. Ví dụ, bạn thường xuyên cần truy xuất những user “popular”. Để định nghĩa 1 scope, tiền tố Eloquent method với scope. Scope trả về query builder hoặc void:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
    /**
     * Scope a query to only include popular users.
     */
    public function scopePopular(Builder $query): void
    {
        $query->where('votes', '>', 100);
    }
 
    /**
     * Scope a query to only include active users.
     */
    public function scopeActive(Builder $query): void
    {
        $query->where('active', 1);
    }
}

Sử dụng local scope: chỉ cần gọi method scope khi query, lưu ý bỏ tiền tố scope:

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

Kết hợp nhiều scope thông qua or:

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

Hoặc viết ngắn gọn hơn:

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

Dynamic scope:

Thi thoảng bạn muốn định nghĩa 1 scope có tham số. Tham số của scope được định nghĩa sau tham số $query:

<?php
 
namespace App\Models;
 
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.
     */
    public function scopeOfType(Builder $query, string $type): void
    {
        $query->where('type', $type);
    }
}

Sử dụng:

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

So sánh model:

Thi thoảng bạn muốn xác định 2 model giống nhau hay không. Method isisNot so sánh 2 model có cùng primary key, table và database connection:

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

2 method này đi kèm với belongsTo, hasOne, morphTo, morphOne trong relationships. Hữu ích trong việc so sánh model liên quan mà không cần query để truy vấn model đó:

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

Events:

Eloquent kích hoạt nhiều event cho phép bạn hook vào sự kiện trong model: retrieved, creating, created, updating, updated, saving, saved, deleting, deleted, trashed, forceDeleting, forceDeleted, restoring, restored, replicating.

Event retrieved kích hoạt khi 1 model được truy xuất từ database. Event -ing kích hoạt trước khi thay đổi model, event -ed kích hoạt sau khi thay đổi model.

Để lắng nghe event, define 1 thuộc tính $dispatchesEvents trong model:

<?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
     */
    protected $dispatchesEvents = [
        'saved' => UserSaved::class,
        'deleted' => UserDeleted::class,
    ];
}

Sử dụng closure:

Thay vì sử dụng event class, bạn có thể register closure trong method booted của 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) {
            // ...
        });
    }
}

Nếu cần thiết bạn có thể sử dụng queueable anonymouse event listener khi đăng ký model event. Điều này hướng dẫn Laravel thực thi model listener trong background sử dụng queue:

use function Illuminate\Events\queueable;
 
static::created(queueable(function (User $user) {
    // ...
}));

Observers:

Define observers:

Nếu bạn muốn lắng nghe nhiều sự kiện cho 1 model, bạn có thể dùng observers để nhóm các listener vào 1 class. Sử dụng lệnh Artisan make:observer:

php artisan make:observer UserObserver --model=User

Lệnh trên tạo class observer tại thư mục app/Observers:

<?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ý observer, gọi method observe trong model. Bạn có thể đăng ký observer trong method boot của service provider:

use App\Models\User;
use App\Observers\UserObserver;
 
/**
 * Register any events for your application.
 */
public function boot(): void
{
    User::observe(UserObserver::class);
}

Hoặc cách khác, khai báo danh sách observer trong thuộc tính $observers của class App\Providers\EventServiceProvider:

use App\Models\User;
use App\Observers\UserObserver;
 
/**
 * The model observers for your application.
 *
 * @var array
 */
protected $observers = [
    User::class => [UserObserver::class],
];

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *