12/08/2018, 17:41

20 Laravel Eloquent Tips and Tricks

Bài dịch từ trang laravel-news.com Có vẻ như Eloquent ORM trong Laravel có cơ chế hoạt động đơn giản, nhưng bên cạnh đó có những tính năng không phải ai cũng biết. Trong bài viết này, tôi sẽ chỉ cho bạn một vài thủ thuật hay ho. Đừng làm thế này: $article = Article::find($article_id); $art ...

Bài dịch từ trang laravel-news.com

Có vẻ như Eloquent ORM trong Laravel có cơ chế hoạt động đơn giản, nhưng bên cạnh đó có những tính năng không phải ai cũng biết. Trong bài viết này, tôi sẽ chỉ cho bạn một vài thủ thuật hay ho.

Đừng làm thế này:

$article = Article::find($article_id);
$article->read_count++;
$article->save();

Bạn có thể làm như thế này:

$article = Article::find($article_id);
$article->increment('read_count');

Hoặc thế này:

Article::find($article_id)->increment('read_count');
Article::find($article_id)->increment('read_count', 10); // +10
Product::find($produce_id)->decrement('stock'); // -1

Eloquent có khá nhiều hàm kết hợp cả hai method như là "làm X đi, không thì làm Y". Example 1 – findOrFail():

Thay vì viết kiểu này:

$user = User::find($id);
if (!$user) { abort (404); }

Hãy viết thế này:

$user = User::findOrFail($id);

Example 2 – firstOrCreate(): Viết kiểu này rất dài:

$user = User::where('email', $email)->first();
if (!$user) {
  User::create([
    'email' => $email
  ]);
}

Viết ngắn lại:

$user = User::firstOrCreate(['email' => $email]);

Trong Eloquent có một thứ rất kì diệu được gọi là boot(), bạn có thể ghi đè các hành động mặc định.

class User extends Model
{
    public static function boot()
    {
        parent::boot();
        static::updating(function($model)
        {
            // do some logging
            // override some property like $model->something = transform($something);
        });
    }
}

Có lẽ một trong những ví dụ phổ biến nhất là tạo một số trường với giá trị mới tại thời điểm tạo model object.

public static function boot()
{
  parent::boot();
  self::creating(function ($model) {
    $model->uuid = (string)Uuid::generate();
  });
}

Đây là một cách điển hình để xác định mối quan hệ:

public function users() {
    return $this->hasMany('AppUser');    
}

Nhưng bạn có biết rằng chúng ta đã có thể thêm where hoặc orderBy:

public function approvedUsers() {
    return $this->hasMany('AppUser')->where('approved', 1)->orderBy('email');
}

Có một vài tham số của một Eloquent model, dưới dạng các thuộc tính của class đó.

class User extends Model {
    protected $table = 'users';
    protected $fillable = ['email', 'password']; // which fields can be filled with User::create()
    protected $dates = ['created_at', 'deleted_at']; // which fields will be Carbon-ized
    protected $appends = ['field1', 'field2']; // additional values returned in JSON
}

Nhưng còn nữa cơ

protected $primaryKey = 'uuid'; // it doesn't have to be "id"
public $incrementing = false; // and it doesn't even have to be auto-incrementing!
protected $perPage = 25; // Yes, you can override pagination count PER MODEL (default 15)
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at'; // Yes, even those names can be overridden
public $timestamps = false; // or even not used at all

Ai cũng biết cách dùng method find() rồi:

$user = User::find(1);

Nhưng chúng ta có thể tìm theo một mảng các giá trị nữa cơ:

$users = User::find([1,2,3]);

Chúng ta hay truy vấn kiểu này:

$users = User::where('approved', 1)->get();

Thử kiểu này đi:

$users = User::whereApproved(1)->get(); 

Đừng ngạc nhiên, bạn có thể thêm tên của bất kì trường nào vào sau từ where và truy vấn như bình thường. ^^

À, còn vài cái nữa, liên quan đến date/time:

User::whereDate('created_at', date('Y-m-d'));
User::whereDay('created_at', date('d'));
User::whereMonth('created_at', date('m'));
User::whereYear('created_at', date('Y'));

Một "mẹo nhỏ" phức tạp hơn một chút. Điều gì sẽ xảy ra nếu bạn có chủ đề trên diễn đàn nhưng muốn order chúng theo bài đăng mới nhất? Yêu cầu khá phổ biến trong các diễn đàn với các chủ đề cập nhật mới nhất được ở trên cùng, phải không? Đầu tiên, mô tả mối quan hệ riêng biệt cho bài đăng mới nhất.

public function latestPost()
{
    return $this->hasOne(AppPost::class)->latest();
}

Và sau đó, trong controller chúng ta sẽ làm điều kì diệu này:

$users = Topic::with('latestPost')->get()->sortByDesc('latestPost.created_at');

Nhiều người viết các câu query với if-else kiều này:

if (request('filter_by') == 'likes') {
    $query->where('likes', '>', request('likes_amount', 0));
}
if (request('filter_by') == 'date') {
    $query->orderBy('created_at', request('ordering_rule', 'desc'));
}

Tuy nhiên viết kiểu này tốt hơn:

$query = Author::query();
$query->when(request('filter_by') == 'likes', function ($q) {
    return $q->where('likes', '>', request('likes_amount', 0));
});
$query->when(request('filter_by') == 'date', function ($q) {
    return $q->orderBy('created_at', request('ordering_rule', 'desc'));
});

Không ngắn hơn nhiều lắm, nhưng rõ ràng việc truyền tham số sẽ ổn hơn:

$query = User::query();
$query->when(request('role', false), function ($q, $role) { 
    return $q->where('role_id', $role);
});
$authors = $query->get();

Giả sử bạn có Post thuộc về Author và sau đó là Blade code:

{{ $post->author->name }}

Nhưng nếu author bị xóa hoặc không được set vì lý do nào đó thì sao? Bạn sẽ nhận được một lỗi property of non-object. Tất nhiên, bạn có thể ngăn chặn nó như thế này:

{{ $post->author->name ?? ' }}

Nhưng bạn có thể làm điều đó với Eloquent relationship:

public function author()
{
    return $this->belongsTo('AppAuthor')->withDefault();
}

Trong ví dụ trên, author() sẽ trả về một AppAuthor model rỗng nếu không có giá trị nào thuộc về Post. Chúng ta còn có thể trả về một giá trị mặc định, kiểu như:

public function author()
{
    return $this->belongsTo('AppAuthor')->withDefault([
        'name' => 'Guest Author'
    ]);
}

Giả sử bạn có đoạn code này:

function getFullNameAttribute()
{
  return $this->attributes['first_name'] . ' ' . $this->attributes['last_name'];
}

Giờ bạn muốn order by full_name phải không? Nhưng viết kiểu này không chạy.

$clients = Client::orderBy('full_name')->get(); // doesn't work

Cách giải quyết rất đơn giản, chúng ta chỉ cần order kết quả thôi.

$clients = Client::get()->sortBy('full_name'); // works!

Chú ý, chúng ta dùng sortBy, không phải là orderBy.

Điều gì sẽ xảy ra nếu bạn muốn có User :: all () luôn được sắp xếp theo trường name? Bạn có thể sử dụng global scope, thứ mà chúng ta đã từng sử dụng ở trên.

protected static function boot()
{
    parent::boot();

    // Order by name ASC
    static::addGlobalScope('order', function (Builder $builder) {
        $builder->orderBy('name', 'asc');
    });
}

Đôi khi, chúng ta cần thêm các raw queries vào các Eloquent statements. May mắn thay, chúng ta có functions cho điều đó.

// whereRaw
$orders = DB::table('orders')
    ->whereRaw('price > IF(state = "TX", ?, 100)', [200])
    ->get();

// havingRaw
Product::groupBy('category_id')->havingRaw('COUNT(*) > 1')->get();

// orderByRaw
User::where('created_at', '>', '2016-01-01')
  ->orderByRaw('(updated_at - created_at) desc')
  ->get();

Chẳng cần phải giải thích nhiều, đây là cách ngắn nhất để copy một database entry:

$task = Tasks::find(1);
$newTask = $task->replicate();
$newTask->save();

Liên quan nhiều hơn đến Collection hơn là Eloquent, nhưng vẫn rất hữu dụng khi xử lý các tập dữ liệu lớn:

User::chunk(100, function ($users) {
    foreach ($users as $user) {
        // ...
    }
});

Thay vì làm kiểu này:

$users = User::all();
foreach ($users as $user) {
    // ...

Chúng ta đều biết câu lệnh này:

php artisan make:model Company

Nhưng chúng ta có thêm các phần mở rộng nữa cơ:

php artisan make:model Company -mcr
  • -m sẽ tạo migration file
  • -c sẽ tạo controller
  • -r sẽ xác nhận controller là resourceful

Bạn có biết rằng -> phương thức save () có thể chấp nhận các tham số không? Kết quả là, chúng ta có thể yêu cầu nó “bỏ qua” chức năng mặc định updated_at để điền vào một giá trị nhất định.

$product = Product::find($id);
$product->updated_at = '2019-01-01 10:00:00';
$product->save(['timestamps' => false]);

Đã bao giờ bạn tự hỏi đoạn code này thực sự trả về cái gì?

$result = $products->whereNull('category_id')->update(['category_id' => 2]);

Ý tôi là, cập nhật trong cơ sở dữ liệu thì nhất định rồi. Vấn đề là $$esult sẽ chứa cái gì? Câu trả lời đó là: affected rows, đó chính là giá trị mà update() method sẽ trả về.

Giả sử bạn có một truy vấn như thế này:

... WHERE (gender = 'Male' and age >= 18) or (gender = 'Female' and age >= 65)

Làm thế nào để chuyển chúng thành Eloquent?

$q->where('gender', 'Male');
$q->orWhere('age', '>=', 18);
$q->where('gender', 'Female');
$q->orWhere('age', '>=', 65);

Sai rồi, phải sử dụng closure functions như một sub-queries:

$q->where(function ($query) {
    $query->where('gender', 'Male')
        ->where('age', '>=', 18);
})->orWhere(function($query) {
    $query->where('gender', 'Female')
        ->where('age', '>=', 65); 
})

Bạn hoàn toàn có thể truyền một array vào orWhere():

$q->where('a', 1);
$q->orWhere(['b' => 2, 'c' => 3]);
0