Tìm hiểu Database Eloquent trong Laravel

Laravel

Eloquent ORM đi kèm với Laravel cung cấp một API ActiveRecord đơn giản và tuyệt vời khi làm việc với database. Mỗi database table sẽ có một “Model” tương ứng để tương tác với table đó. Model cho phép bạn query dữ liệu trong table, cũng như chèn thêm các dữ liệu mới. Trước khi bắt đầu, hãy đảm bảo cấu hình kết nối database trong file config/database.php.

Định nghĩa Models

Để bắt đầu, hãy cùng tạo một Eloquent model. Model về cơ bản nằm trong thư mục app nhưng bạn có thể tuỳ ý đặt chúng ở bất cứ đâu mà được cấu hình autoload trong file composer.json. Tất cả các Eloquent model đều kế thừa từ class Illuminate\Database\Eloquent\Model.

Cách đơn giản nhất để tạo một model là sử dụng make:model Artisan command:

php artisan make:model User

Nếu bạn muốn tạo database migration đi kèm với model khi tạo thì sử dụng thêm cờ --migrationhoặc -m option:

php artisan make:model User --migration
php artisan make:model User -m

Quy tắc cho Eloquent Model

Bây giờ, hãy cùng nhau coi ví dụ về class model Flight, mà chúng ta sẽ dùng để lấy và lưu thông tin vào trong bảng flights:

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    //
}

Tên bảng

Để ý là chúng ta không hề cho Eloquent biết là bảng nào được sử dụng cho model Flight. Vì kiểu “snake case”, tên class ở số nhiều sẽ được sử dụng như tên table trừ khi có một tên khác được khai báo. Vì thế, trong trường hợp này, Eloquent sẽ coi model Flight lưu dữ liệu vào trong bảng flightsBạn có thể chỉ định tên table khác cho model bằng cách khai báo thuộc tính table trong model:

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    protected $table = 'my_flights';
}

Primary Keys

Eloquent cũng coi mỗi table có một column là primary key tên là id. Bạn có thể định nghĩa một $primaryKey để đổi tên column này.Ngoài ra, Eloquent cũng coi primary key là một giá trị nguyên tăng dần, có nghĩa là về mặc định primary key sẽ được cast về kiểu int tự động. Nếu bạn muốn sử dụng primary không tăng dần hay không phải là dạng số, bạn cần thay đổi thuộc tính $incrementing trong model thành false.

Timestamps

Mặc định, Eloquent cần hai cột created_at và updated_at có mặt trong các bảng. Nếu bạn không muốn những columns này tự động được quản lý bởi Eloquent, thiết lập thuộc tính $timestampstrong model thành false:

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    public $timestamps = false;
}

Nếu bạn muốn thay đổi định dạng của timestamp, thiết lập vào thuộc tính $dateFormat trong model. Thuộc tính này xác định cách mà các thuộc tính kiểu date được lưu trong database cũng như cách format khi được serialize thành array hay JSON:

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{   
    protected $dateFormat = 'U';
}

Kết nối database

Tất cả các Eloquent model sẽ sử dụng kết nối database mặc định được cấu hình. Nếu bạn muốn sử dụng một kết nối khác cho model, sử dụng thuộc tính $connection:

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    protected $connection = 'connection-name';
}

Lấy nhiều Models

Khi bạn đã tạo được một model và its associated database table, bạn có thể sẵn sàng truy xuất dữ liệu từ database. Hãy coi mỗi Eloquent model như một query builder mạnh mẽ cho phép bạn thực hiện query tới database một cách liền mạch. Ví dụ:

<?php
use App\Flight;
$flights = App\Flight::all();
foreach ($flights as $flight) {
    echo $flight->name;
}

Thêm ràng buộc bổ sung

Phương thức all sẽ trả về tất cả các kết quả trong table của model. Vì mỗi Eloquent model phục vụ như một query builder, nên bạn có thể tạo ràng buộc cho các query, và cuối cùng sử dụng hàm getđể lấy kết quả:

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

Collections

Vì các hàm của Eloquent như all và get đều trả về nhiều kết quả, một instance từ Illuminate\Database\Eloquent\Collection sẽ được trả về. Class Collection cung cấp các hàm hữu ích cho phép làm việc với các kết quả Eloquent:

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

Collections

Vì các hàm của Eloquent như all và get đều trả về nhiều kết quả, một instance từ Illuminate\Database\Eloquent\Collection sẽ được trả về. Class Collection cung cấp các hàm hữu íchcho phép làm việc với các kết quả Eloquent:

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

Tất nhiên, bạn có thể foreach vòng lặp collection như một array:

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

Chunking Results

Nếu bạn muốn xử lý hàng ngàn kết quả từ Eloquent, sử dụng hàm chunk. Hàm chunk Hàm này sẽ lấy từng “chunk” của Eloquent models, cung cấp chúng thông qua Closure để xử lý. Sử dụng hàm chunk sẽ tiết kiệm được memory khi thao tác với tập dữ liệu kết quả lớn:

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

Tham số đầu truyền vào là số record bạn muốn lấy từng “chunk”. Closure truyền vào ở tham số thứ hai sẽ được gọi cho mỗi chunk được lấy từ database. Một database sẽ được thực thi để lấy mỗi “chuck” của records truyền vào Closure.

Sử dụng Cursors

Hàm cursor cho phép bạn duyệt qua records bằng cách sử dụng một cursor, nó chỉ thực thi cho một truy vấn. Khi dữ liệu lớn, hàm cursor có thể được sử dụng để giảm memory sử dụng:

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

Lấy một Models / Aggregates

Ngoài việc lấy tất cả dữ liệu, bạn có thể lấy một kết quả sử dụng hàm find hoặc first. Thay vì trả về một collection model, những hàm này trả về một model instance:

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

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

Bạn có thể gọi hàm find với một mảng các primary key, với kết quả trả về là một collection các kết quả tìm thấy:

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

Not Found Exceptions

Sẽ có lúc bạn muốn bắn ra một exception nếu một model không được tìm thấy. Điều này thực sự hữu ích khi làm việc trên route hay controller. Hàm findOrFail và firstOrFail sẽ trả lại kết quả đầu tiên của query. Tuy nhiên, nếu không có kết quả, thì Illuminate\Database\Eloquent\ModelNotFoundException sẽ được ném ra:

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

Nếu exception mà không được bắt, một HTTP response 404 sẽ tự động được gửi lại cho user, vì thế, không cần thiết phải viết code riêng để kiểm tra để trả về 404 khi sử dụng những hàm này:

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

Lấy Aggregates

Bạn cũng có thể sử dụng các hàm như countsummax, and other hàm aggregate cung cấp bởi query builder. Những hàm này trả về một kết quả thay vì một model instance:

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

Thêm & Cập nhật Models

Thêm

Để thêm dữ liệu mới vào database, đơn giản hãy tạo một model instance mới, thiết lập các attributes vào model rồi gọi hàm save:

<?php
namespace App\Http\Controllers;
use App\Flight;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class FlightController extends Controller
{
    public function store(Request $request)
    {
        $flight = new Flight;
        $flight->name = $request->name;
        $flight->save();
    }
}

Ở ví dụ này, chúng ta có thể đơn giản chỉ gán tham số name từ HTTP request vào thuộc tính namecủa model App\Flight. Khi gọi hàm save, một record sẽ được thêm vào database. Timestamp created_at và updated_at timestamps sẽ tự động được thêm khi hàm save được gọi, và bạn không cần thay đổi thủ công giá trị này.

Cập nhật

Hàm save cũng được dùng để cập nhật model đã tồn tại sẵn trong database. Để update, bạn cần lấy model instance ra trước, thay đổi các attribute ban muốn, rồi gọi hàm save. Một lần nữa, giá trị của updated_at sẽ tự động được cập nhật, và bạn không cần thay đổi thủ công giá trị này:

$flight = App\Flight::find(1);
$flight->name = 'New Flight Name';
$flight->save();

Mass Updates

Update cũng có thể được thực hiện cho nhiều model mà thoả mãn một điều kiện query. Ở ví dụ này, tất cả các flights mà active và có một destination là San Diego sẽ bị đánh dấu là delayed:

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

Hàm update nhận một mảng các cặp cột và giá trị tương ứng với các cột được cập nhật.

Khi thực hiện một mass update bằng Eloquent, hàm saved và updated model events sẽ không được bắn ra cho các model updated. Vì các model thực ra chưa bao giờ lấy khi mass update.

Mass Assignment

Bạn cũng có thể sử dụng hàm create để tạo một model mới chỉ trong một dòng. Model instance được thêm mới sẽ được trả lại từ hàm. Tuy nhiên, để làm được điều đó, bạn cần thiết phải chỉ định thuộc tính fillable hoặc guarded trong model, để Eloquent model được bảo vệ trước mass-assignment.

Lỗi bảo mật mass-assignment xảy ra khi một user truyền vào một tham số HTTP không mong muốn trong request, và tham số đó sẽ có thể thay đổi một column trong database mà bạn không ngờ tới. Ví dụ, một user xấu có thể gửi một tham số is_admin qua HTTP request, và khi giá trị này được map vào trong model qua hàm create, sẽ cho phép user thay đổi để biến thành một admin.

Vì thế, để bắt đầu, bạn cần khai báo thuộc tính bạn muốn cho phép mass-assignment. Bạn có thể thiết lập qua thuộc tính $fillable. Ví dụ, hãy làm cho thuộc tính name của model Flight mass assignable:

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    protected $fillable = ['name'];
}

Sau đó, chúng ta có thể sử dụng hàm create để tạo một record mới trong database. Hàm create sẽ trả về một model instance được lưu:

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

Thuộc tính guarding

Trong khi $fillable dùng để lưu danh sách các thuộc tính “white list” được mass assignable, bạn có thể sử dụng $guarded. Thuộc tính $guarded để lưu các thuộc tính mà không được phép mass assignable. Các thuộc tính khác không lưu trong nó sẽ dượcd mass assignable. Vì vậy, $guardedgiống như là một “black list”. Tất nhiên, Bạn có thể sử dụng một trong hai, $fillable hoặc $guarded– không cả hai. Trong ví dụ dưới, tất cả các thuộc tính ngoại trừ price sẽ được mass assignable:

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    protected $guarded = ['price'];
}

Nếu bạn muốn tất cả các thuộc tính mass assignable, bạn định nghĩa thuộc tính $guarded là một mảng rỗng:

protected $guarded = [];

Các hàm tạo khác

Còn hai hàm khác bạn có thể sử dụng để model bằng cách mass-assignment các thuộc tính: firstOrCreate và firstOrNew. Hàm firstOrCreate msẽ cố gắng tìm trong database sử dụng cặp column và giá trị truyền vào. Nếu model không được tìm thấy trong database, một dòng record mới sẽ được thêm vào với các attributes được truyền vào.

Hàm firstOrNew giống như hàm firstOrCreate sẽ cố gắng tìm record trong database khớp với các attribute truyền vào. Tuy nhiên, nếu model không tìm tháy, một model instance mới sẽ được trả về. Chú ý là model được trả về bởi firstOrNew vẫn chưa được lưu vào database. Bạn cần gọi hàm saveđể lưu nó lại:

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

Xóa Models

Để xóa một model, gọi hàm delete trong model instance:

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

Xoá model tồn tại bằng một key

Ví dụ trên, chúng ta lấy model từ database trước khi gọi hàm delete. Tuy nhiên, nếu bạn đã biết primary key của model, bạn có thể xoá model mà không cần lấy nó ra. Để làm được việc này, bạn chỉ cần gọi hàm destroy:

App\Flight::destroy(1);

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

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

Xóa các Models bằng truy vấn

Bạn cũng có thể thực hiện gọi một query để xoá một tập hợp các model. Ở ví dụ này, chúng ta sẽ xoá tất cả các flights được đánh dấu là inactive:

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

Khi thực thi một mass delete qua Eloquent, the deleting và deleted model events sẽ không được bắn ra cho các model deleted. Bởi vì các models thực ra chưa bao giờ lấy khi thực thi lệnh delete.

Soft Deleting

Thay vì thực sự xoá các record khỏi database, Eloquent cũng cung cấp kiểu “soft delete” models. Khi model được soft deleted, chúng chưa thực sự bị xoá khỏi database. Thay vì thế, một trường là deleted_at sẽ được thiết lập trong model và chèn vào trong database. Nếu model có giá trị deleted_at khác NULL, tức là model đã bị soft deleted. Để kích hoạt xoá mềm cho một model, sử dụng trait Illuminate\Database\Eloquent\SoftDeletes trên model và thêm vào cột deleted_at trong thuộc tính $dates:

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Flight extends Model
{
    protected $dates = ['deleted_at'];
}

Tất nhiên là bạn cần phải thêm cột deleted_at vào trong table. Điều này có thể thực hiện qua việc sử dụng một helper được cung cấp để tạo trên schema builder:

Schema::table('flights', function ($table) {
    $table->softDeletes();
});

Bây giờ, khi bạn gọi hàm delete trên model, cột deleted_at sẽ được set vào current date và time. Và, khi thực hiện query một model có sử dụng soft delete, thì model đó sẽ tự động bị loại khỏi tất cả các kết qủa query.

Để xác định nếu một model instance bị soft delete, sử dụng hàm trashed:

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

Truy vấn các soft delete model

Thêm các soft delete model vào kết quả

Như đã ghi chú ở trên, soft delete mode sẽ tự động bị tách khỏi các kết quả query. Tuy nhiên, bạn có thể ép các soft delete model xuất hiện trên tập kết quả sử dụng hàm withTrashed:

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

Hàm withTrashed ngoài ra có thể sử dụng cho truy vấn relationship:

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

Chỉ lấy các soft delete model vào kết quả

Hàm onlyTrashed sẽ chỉ lấy các soft delete model:

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

Phục hồi các soft delete model

Thi thoảng bạn cũng muốn “khôi phục” một soft deleted model. Để khôi phục lại một soft delete model về trạng thái active, hãy sử dụng hàm restore trong một model instance:

$flight->restore();

Bạn cũng có thể dùng hàm restore trên một query để nhanh chóng khôi phục nhiều model. Một lần nữa, giống như các lệnh “mass” khac, noa sẽ không được bắn ra bất kỳ model events cho models được restored:

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

Giống như hàm withTrashed, hàm restore ngoài ra cũng được sử dụng cho relationships:

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

Xoá các model vĩnh viễn

Bạn có thể cần thực xoá một model khỏi database. Để xoá vĩnh viễn một soft delete model, hãy sử dụng hàm forceDelete:

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

Query Scopes

Global Scopes

Global scope cho phép bạn thêm các constraint vào tất cả các truy vấn cho một model. Hàm soft delete của Laravel thực hiện trên global scope chỉ với các model “chưa bị xoá” trong database. Viết global scope riêng của bạn có thể tạo một cách dễ dàng để đảm bảo mỗi truy vấn cho một model nhận đúng constraint.

Viết Global Scopes

Viết một global scope khá đơn giản. Tạo một class triển khai từ interface Illuminate\Database\Eloquent\Scope. Interface này yêu cầu bạn viết mã cho một hàm: apply. Hàm apply có thể nhận constraint where vào query khi cần thiết:

<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class AgeScope implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        $builder->where('age', '>', 200);
    }
}

Không có thư mục định sẵn để lưu các scope trong Laravel, bạn hoàn toàn thoải mái tạo Scopes folder trong thư mục ứng dụng app.

Áp dụng Global Scopes

Để gán một global scope cho một model, bạn cần ghi đè lại hàm boot và sử dụng hàm addGlobalScope:

<?php
namespace App;
use App\Scopes\AgeScope;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected static function boot()
    {
        parent::boot();
        static::addGlobalScope(new AgeScope);
    }
}

Sau khi thêm vào scope, một truy vấn User::all() sẽ tạo ra câu SQL như sau:

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

Các global scope vô danh

Eloquent cũng cho phép bạn tạo các global scope sử dụng Closure, điều này khá hữu ích cho các scope đơn giản mà không cần tạo class riêng:

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class User extends Model
    protected static function boot()
    {
        parent::boot();
        static::addGlobalScope('age', function(Builder $builder) {
            $builder->where('age', '>', 200);
        });
    }
}

Tham số đầu tiên truyền vào addGlobalScope() là identifier để loại bỏ scope khi cần thiết:

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

Xóa các Global Scopes

Nếu bạn muốn bỏ một global scope cho một câu truy vấn, bạn có thể sử dụng hàm withoutGlobalScope. Hàm này nhận một tên class của global scope là đối số duy nhất:

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

Nếu bạn muốn bỏ một vài hoặc tất cả các global scope, bạn có thể dùng hàm withoutGlobalScopes:

User::withoutGlobalScopes()->get();
User::withoutGlobalScopes([
    FirstScope::class, SecondScope::class
])->get();

Local Scopes

Local scope cho phép bạn tạo tập hợp các ràng buộc thường dùng mà bạn có thể tái sử dụng trong chương trình. Ví dụ, bạn có thể hay lấy tất cả các “popular” users. Để tạo một scope, chỉ cần đặt tiền tố scope trong một hàm của Eloquent model.

Scope luôn luôn trả về một instance của query builder:

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    public function scopePopular($query)
    {
        return $query->where('votes', '>', 100);
    }
    public function scopeActive($query)
    {
        return $query->where('active', 1);
    }
}

Sử dụng một Local Scope

hi scope được khai báo, bạn có thể sử dụng hàm của scope khi thực hiện query model. Tuy nhiên, bạn không cần thêm vào tiền tố scope khi gọi hàm. Bạn ngoài ra có thể gọi móc nối các scope liên tiếp:

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

Scopes động

Thỉnh thoảng cần muốn định nghĩa một scope có nhận tham số. Để bắt đầu, thêm các tham số vào scope. Tham số của Scope sẽ được định nghĩa sau tham số $query:

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    public function scopeOfType($query, $type)
    {
        return $query->where('type', $type);
    }
}

Bây giờ, bạn có thể truyền vào tham số khi gọi scope:

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

Events

Eloquent model bắn ra một số các events, cho phép bạn có thể hook vào nhiều điểm của model lifecycle sử dụng các hàm sau: creatingcreatedupdatingupdatedsavingsaveddeletingdeletedrestoringrestored. Events cho phép bạn dễ dàng thực thi code mỗi lần class model class được lưu hoặc cập nhật vào database.

Bất cứ khi nào một model mới được lưu lần đầu tiên, hai event creating và created sẽ được bắn ra. Nếu model đã tồn tại trong database và hàm save được gọi, hàm updating / updated được bắt ra. Tuy nhiên, cả hai trường hợp, hàm saving / saved đều được bắn ra.

Ví dụ, cùng tạo một listener cho Eloquent event trong một service provider. Bên trong event listener, chúng ta sẽ gọi hàm isValid trên model, và trả về false nếu model không hợp lệ. Việc trả về giá trị false từ một Eloquent event listener sẽ huỷ bọ hai thao tác save / update:

<?php
namespace App\Providers;
use App\User;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        User::creating(function ($user) {
            return $user->isValid();
        });
    }
    public function register()
    {
        //
    }
}

Observers

Nếu bạn đang listening nhiều events trong model, bạn có thể sử dụng observers để nhóm tất cả các listeners thành một class. Observers classes có tên phương thức, nó sẽ tương tác với Eloquent events mà bạn muốn listen. Mỗi một phương thức nhận đối số duy nhất là tên model. Laravel không thêm thư mục mặc định cho observers, bạn có thể tạo chúng ở bất kỳ đâu để chứa observer classes:

<?php
namespace App\Observers;
use App\User;

class UserObserver
{
    public function created(User $user)
    {
        //
    }
    public function deleting(User $user)
    {
        //
    }
}

Để đăng ký một observer, sử dụng hàm observe trong model bạn muốn observe. Bạn có thể đăng ký observers trong hàm boot của service providers. Trong ví dụ này, chúng ta sẽ đăng ký observer trong AppServiceProvider:

<?php
namespace App\Providers;
use App\User;
use App\Observers\UserObserver;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        User::observe(UserObserver::class);
    }
    public function register()
    {
        //
    }
}

Trả lời

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 *