+1

[Laravel Certification] Laravel Eloquent - P1 Giới thiệu

Mayfest2023

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

  1. -m(--migration): tạo thêm migration cho model User
  2. -f(--factory): tạo thêm UserFactory class
  3. -s(--seed): tạo thêm UserSeeder class
  4. -c(--controller): tạo thêm UserController class
  5. -r(--resource): tạo thêm resource class
  6. -R(--request): tạo thêm UserRequest class
  7. -p(--policy): tạo thêm file UserPolicy class
  8. --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:

  1. Tên ( đường dẫn) của model
  2. Thuộc db connection nào
  3. Model này đại diện cho bảng nào
  4. Gồm các Attributes nào
  5. 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

  1. 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());
    }
}
  1. Đă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:

  1. 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));
    }
}
  1. 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.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí