Giới thiệu
Laravel Eloquent là một ORM(object-relational mapper) nhằm để tương tác (mapping) giữa object của ngôn ngữ với record trong DB. Khi sử dụng Eloquent thì mỗi bảng trong database ta lại có 1 "model" trong code để thao tác với bảng đó, ví dụ chúng ta có bảng users thì chúng ta sẽ có model tương ứng là User Chú ý: Để sử dụng được Model trước hết ta cần config database connection mà dự án dùng tới, config này sẽ ở file config/database.php
Tạo model bằng command
Nào chúng ta cùng nhau bắt đầu làm quen với Eloquent model. Trước hết chúng ta sẽ tạo Model có tên là User bằng command
php artisan make:model User -mfscrRp
Command trên sẽ tạo model với đường dẫn app/Model/User.php, ngoài ra thêm các tham số:
- -m(--migration): tạo thêm migration cho model User
- -f(--factory): tạo thêm UserFactory class
- -s(--seed): tạo thêm UserSeeder class
- -c(--controller): tạo thêm UserController class
- -r(--resource): tạo thêm resource class
- -R(--request): tạo thêm UserRequest class
- -p(--policy): tạo thêm file UserPolicy class
- --all: tạo tất cả các class trên
Những thuộc tính của Eloquent Model
Muốn xem những thuộc tính của model bạn có thể dùng lệnh php artisan model:show
λ php artisan model:show User App\Models\User .................................................................................................................................. Database ................................................................................................................................... mysql Table ...................................................................................................................................... users Attributes ........................................................................................................................... type / cast id increments, unique ...................................................................................................... bigint unsigned / int name fillable ........................................................................................................................ string(255) email unique, fillable ............................................................................................................... string(255) email_verified_at nullable ................................................................................................... datetime / datetime password fillable, hidden ................................................................................................... string(255) / hashed remember_token nullable, hidden ...................................................................................................... string(100) created_at nullable .......................................................................................................... datetime / datetime updated_at nullable .......................................................................................................... datetime / datetime Relations ........................................................................................................................................ tokens MorphMany ............................................................................................. Laravel\Sanctum\PersonalAccessToken notifications MorphMany ............................................................................ Illuminate\Notifications\DatabaseNotification Observers ........................................................................................................................................
Như thấy ở trên ta có thể thấy các thuộc tính của model bao gồm:
- Tên ( đường dẫn) của model
- Thuộc db connection nào
- Model này đại diện cho bảng nào
- Gồm các Attributes nào
- Gồm các Relations nào
Những quy ước của Eloquent Model
Table names
Cùng xem xét model User
<?php namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable
{ use HasApiTokens, HasFactory, Notifiable; /** * The attributes that are mass assignable. * * @var array<int, string> */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for serialization. * * @var array<int, string> */ protected $hidden = [ 'password', 'remember_token', ]; /** * The attributes that should be cast. * * @var array<string, string> */ protected $casts = [ 'email_verified_at' => 'datetime', 'password' => 'hashed', ];
}
Như thấy trong model này ko có chỗ nào khai báo để biết model này ứng với table nào trong DB, nhưng nó tự động liên kết với bảng users. Bởi vì trong model eloquent thì table nếu ko chỉ định sẽ lấy table theo tên model, model là User -> table là users Ngoài ra nếu ta có thể chỉ định lại table name này bằng cách thêm thược tính $table
/** * The table associated with the model. * * @var string */ protected $table = 'users';
Primary Keys
Cũng tương tự như Tables Names ở trên, theo mặc định thì Model Eloquent sẽ tự nhận khóa chính là trường id. Nếu muốn thay đổi ta sẽ sửa thuộc tính $primaryKey
/** * The primary key associated with the table. * * @var string */ protected $primaryKey = 'user_id';
Giống như trong Db Mysql thì thường khóa chính ta sẽ để thêm thuộc tính auto_increment ( tự động tăng), trong Model Eloquent này thì Primary Keys cũng mặc định tự tăng. Như vậy khi thực hiện insert data vào bảng users thì ta ko cần truyền giá trị cho trường Primary Key. Để tắt chức năng này ta sẽ chỉ định thuộc tính $incrementing:
/** * Indicates if the model's ID is auto-incrementing. * * @var bool */ public $incrementing = false;
Bình thường Primary Keys trong Db là số nguyên, Model Eloquent cũng để Primary Keys là int, nếu muốn thay đổi giá trị mặc định này ta sẽ thay đổi thuộc tính $keyType:
/** * The data type of the auto-incrementing ID. * * @var string */ protected $keyType = 'string';
UUID & ULID Keys
Bình thường khóa chính chúng ta hay để là số nguyên và có thuộc tính auto-increment, id sẽ tăng từ 1, 2, 3, ... Tuy nhiên Db hiện đại hay sử dụng khóa chính là UUID. UUID là 1 string độ dài 36 ký tự ngẫu nhiên, và xác suất lặp lại bằng 0. Muốn dùng UUID này thay cho Auto_increment thì ta sẽ dùng như sau
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model; class Article extends Model
{ use HasUuids; // ...
} $article = Article::create(['title' => 'Traveling to Europe']); $article->id; // "8f8e8478-9035-4d23-b9a7-62f4d2612ce5"
Thay vì dùng $article->id là mặc định là tự tăng thì $article->id là 1 uuid
Timestamps
Mặc định nếu trong tables có các trường created_at, updated_at thì khi thực hiện insert/update data thông qua Model Eloquent thì nó sẽ tự động set giá trị cho 2 trường này. Nếu là insert data thì sẽ set created_at, updated_at là thời gian hiện tại. Nếu là update data thì sẽ set updated_at là thời gian hiện tại. Để bỏ chức năng mặc định timestamps này thì ta sẽ sửa thuộc tính của model
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model
{ /** * Indicates if the model should be timestamped. * * @var bool */ public $timestamps = false;
}
Nếu ta muốn customize format của timestamps, ta thay đổi thuộc tính $dateFormat
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model
{ /** * Indicates if the model should be timestamped. * * @var bool */ protected $dateFormat = 'U';
}
Nếu muốn thay đổi trường lưu trữ timestamps thì ta thay đổi 2 const của model
<?php class User extends Model
{ const CREATED_AT = 'creation_date'; const UPDATED_AT = 'updated_date';
}
Database Connections
Theo mặc định Model Eloquent sẽ lấy default database connection được khai báo trong config/database.php là connection mặc định. Trường hợp dự án của ta có tới tận 2 DB thì sao, hãy xem cách giải quyết sau: Trong file config/database.php ta khai báo thêm 1 connection ứng tới DB thứ 2 như sau
'mysql1' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL1'), 'host' => env('DB_HOST1', '127.0.0.1'), 'port' => env('DB_PORT1', '3306'), 'database' => env('DB_DATABASE1', 'forge'), 'username' => env('DB_USERNAME1', 'forge'), 'password' => env('DB_PASSWORD1', ''), 'unix_socket' => env('DB_SOCKET1', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ],
'mysql2' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL2'), 'host' => env('DB_HOST2', '127.0.0.1'), 'port' => env('DB_PORT2', '3306'), 'database' => env('DB_DATABASE2', 'forge'), 'username' => env('DB_USERNAME2', 'forge'), 'password' => env('DB_PASSWORD2', ''), 'unix_socket' => env('DB_SOCKET2', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [],
],
Ta có 2 Model User và Admin, User dùng bảng users của DB1, Admin dùng bảng admins của DB2, khi đó ta khai báo mỗi model ứng với những connection khác nhau app/Models/User.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable
{ use HasApiTokens, HasFactory, Notifiable; /** * The table associated with the model. * * @var string */ protected $table = 'users'; /** * The database connection that should be used by the model. * * @var string */ protected $connection = 'mysql1';
}
app/Models/Admin.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens; class Admin extends Authenticatable
{ use HasApiTokens, HasFactory, Notifiable; /** * The table associated with the model. * * @var string */ protected $table = 'admins'; /** * The database connection that should be used by the model. * * @var string */ protected $connection = 'mysql2';
}
Bằng việc sử dụng $connection ta đã giải quyết được việc dùng nhiều DB trong dự án.
Default Attribute Values
Nếu bạn muốn model của bạn thao tác với những trường nào trong table thì bạn cần khai báo trường đó vào trong $attributes
<?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', ];
}
Nếu muốn truyền giá trị default cho các giá trị này thì chúng ta có thể viết
<?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, ];
}
Truy xuất từ Model
Building Queries
Eloquent có thể tạo query một cách linh hoạt
$flights = Flight::where('active', 1) ->orderBy('name') ->take(10) ->get();
Như ví dụ trên ta sẽ lấy $flights theo logic sau: lấy data trong bảng Flight với điều kiện trường active = 1, order by asc theo trường name, lấy 10 giá trị đầu tiên. Đây là thế mạnh của Eloquent, tạo ra các truy vấn sql rất trực quan và linh hoạt.
Refreshing Models
Ví dụ bạn có 1 model trích xuất data từ DB, rồi bạn để đó đi chơi 1 tiếng, trong 1 tiếng đó db trong db đã bị thay đổi, bạn muốn update giá trị mới cho model lúc trước, khi đó bạn có thể dùng fresh() hoặc refresh(). Cùng xem ví dụ sau nhé Dùng fresh()
$flight = Flight::where('number', 'FR 900')->first(); // đi chơi 1 tiếng // update lại data bằng cách dùng fresh()
$freshFlight = $flight->fresh();
// dùng fresh() thì từ instance $flight sẽ tạo instance $freshFlight mới với giá trị mới nhất từ DB. Như vậy $flight lưu giá trị cũ, còn $freshFlight lưu những giá trị mới
Dùng refresh()
$flight = Flight::where('number', 'FR 900')->first(); $flight->number = 'FR 456'; $flight->refresh();
// update lại chính instance cũ, khi này giá trị cũ bị thay thế bởi giá trị mới từ db $flight->number; // "FR 900"
Collections
Mọi data truy suất dữ liệu dùng Model Eloquent đều trả về dữ liệu Collection. Nghĩa là sau functioin ->get(), ->all() thì ta sẽ có collection . Như ta biết Collection là 1 dạng "array nâng cao" cung cấp rất nhiều function để làm việc linh hoạt, do đó khi trả dữ liệu về dạng Collection sẽ giúp chúng ta dễ sử dụng
$flights = Flight::where('destination', 'Paris')->get();
//$flights là 1 Collection
Chunking Results
Nếu lấy tất cả dữ liệu làm chậm performance và làm tràn bộ nhớ thì chungking là cách chia lấy data thành từng phần từng phần, giải quyết bài toán performance hiệu quả. Ví dụ
use App\Models\Flight;
use Illuminate\Database\Eloquent\Collection; Flight::chunk(200, function (Collection $flights) { foreach ($flights as $flight) { // ... }
});
Ở trên ta thấy việc lấy data chia thành từng phần nhỏ, mỗi phần nhỏ gồm 200 record, và sẽ lấy tới hết
Advanced Subqueries
Subquery Selects
<?php
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
<?php
return Destination::orderByDesc( Flight::select('arrived_at') ->whereColumn('destination_id', 'destinations.id') ->orderByDesc('arrived_at') ->limit(1)
)->get();
Truy suất Single Model và các hàm thống kê
Truy suất Single Model
Ngoài các truy suất get(), all() Eloquent còn cung cấp các phương thức lấy 1 record: find(1), first(), firstWhere(), findOr(1, ), firstOr(), findOrFail(1,..), firstOrFail()
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);
$flight = Flight::findOr(1, function () { // ...
}); $flight = Flight::where('legs', '>', 3)->firstOr(function () { // ...
});
$flight = Flight::findOrFail(1); $flight = Flight::where('legs', '>', 3)->firstOrFail();
Retrieving Or Creating Models
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']
)
Retrieving Aggregates
$count = Flight::where('active', 1)->count(); $max = Flight::where('active', 1)->max('price');
Inserting & Updating Models
Inserts
Để insert data vào DB ta tạo new Model và dùng hàm save()
<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller;
use App\Models\Flight;
use Illuminate\Http\Request; class FlightController extends Controller
{ /** * Store a new flight in the database. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { // Validate the request... $flight = new Flight; $flight->name = $request->name; $flight->save(); }
}
Updates
Nếu model đã tồn tại thì hàm save() sẽ update lại data
use App\Models\Flight; $flight = Flight::find(1); $flight->name = 'Paris to London'; $flight->save();
Mass Updates
Update hàng loạt, ta dùng function update()
Flight::where('active', 1) ->where('destination', 'San Diego') ->update(['delayed' => 1]);
Chú ý khi dùng mass update thì các event của model: saving, saved, updating, updated sẽ ko được gọi
Delete Models
Để delete data trong DB ta dùng hàm delete()
use App\Models\Flight; $flight = Flight::find(1); $flight->delete();
Để truncate bảng ta dùng hàm truncate()
Flight::truncate();
Để delete data theo Primary Key ta dùng hàm destroy
Flight::destroy(1); Flight::destroy(1, 2, 3); Flight::destroy([1, 2, 3]); Flight::destroy(collect([1, 2, 3]));
Ngoài ra hàm delete còn dùng được với where
$deleted = Flight::where('active', 0)->delete();
Soft Deleting Xóa mềm
Thay vì xóa cứng bản ghi trong Db, ta có thể update trường deleted_at giá trị hiện tại, khi đó ta gọi là xóa mềm Trong laravel để làm điều này ta dùng SoftDeletes
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; class Flight extends Model
{ use SoftDeletes;
}
Ngược lại với xóa mềm, ta có restore mềm
$flight->restore();
Pruning Models
Pruning Model là lên lịch xóa định kì những data trong model. Để làm điều này ta cần làm 2 việc
- Sử dung use Illuminate\Database\Eloquent\Prunable; viết lại function prunable() để viết điều kiện xóa data
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Prunable; class Flight extends Model
{ use Prunable; /** * Get the prunable model query. * * @return \Illuminate\Database\Eloquent\Builder */ public function prunable() { return static::where('created_at', '<=', now()->subMonth()); }
}
- Đăng ký vào command model:prune vào Schedule, sửa file app/Console/Kernel.php
/** * Define the application's command schedule. * * @param \Illuminate\Console\Scheduling\Schedule $schedule * @return void */
protected function schedule(Schedule $schedule)
{ $schedule->command('model:prune', [ '--model' => [Address::class, Flight::class], ])->daily();
}
Như đăng ký ở trên thì hằng ngày vào lúc 0h00 thì comand model:prune chạy việc xóa data trong 2 bảng address và flights với kiều kiện create_at nhỏ hơn 1 tháng trước đó
Replicating Models
Có tác dụng nhân bản model
$shipping = Address::create([ 'type' => 'shipping', 'line_1' => '123 Example Street', 'city' => 'Victorville', 'state' => 'CA', 'postcode' => '90001',
]); $billing = $shipping->replicate()->fill([ 'type' => 'billing'
]); $billing->save();
Query Scopes
Global Scopes
Ví dụ soft delete là 1 global scopes
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; class Flight extends Model
{ use SoftDeletes;
}
Áp dụng được cho tất cả model, SoftDeletes scope là 1 global scope mà Laravel có sẵn, vậy để viết global scope của riêng mình thì ta cần làm thế nào? Ta cần làm 2 bước sau:
- Tạo 1 global 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. * * @param \Illuminate\Database\Eloquent\Builder $builder * @param \Illuminate\Database\Eloquent\Model $model * @return void */ public function apply(Builder $builder, Model $model) { $builder->where('created_at', '<', now()->subYears(2000)); }
}
- Dùng global scope trê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. * * @return void */ protected static function booted() { static::addGlobalScope(new AncientScope); }
}
Local Scopes
Là những scope viết thẳng trong model, và phạm vi sử dụng chỉ trong model đó mà thôi
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model
{ /** * Scope a query to only include popular users. * * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ public function scopePopular($query) { return $query->where('votes', '>', 100); } /** * Scope a query to only include active users. * * @param \Illuminate\Database\Eloquent\Builder $query * @return void */ public function scopeActive($query) { $query->where('active', 1); }
}
// sử dụng 2 scope là popular và active
$users = User::popular()->orWhere->active()->get();
Comparing Models
Đôi khi ta muốn so sánh 2 model có giống nhau không, ta dùng function is() và isNot()
if ($post->is($anotherPost)) { //
} if ($post->isNot($anotherPost)) { //
}
Events
Dưới đây là các event có sẵn của model eloquent theo một vòng đời: retrieved, creating, created, updating, updated, saving, saved, deleting, deleted, trashed, forceDeleting, forceDeleted, restoring, restored, and replicating.