Khi bạn đang mơ thì người khác đang nỗ lực.

Laravel Eloquent ORM phần 3: xử lý dữ liệu đầu ra

Laravel Eloquent ORM là một trong những phần tốn giấy mực khi viết về nó, là một phần trọng điểm trong mô hình MVC với Eloquent Model. Chúng ta cùng điểm lại những gì đã được giới thiệu trong phần 1 và 2 của Laravel Eloquent ORM. Trong khối Model của mô hình MVC là một xã hội thu nhỏ với rất nhiều các thực thể có quan hệ với nhau, chúng ta đã xem xét khía cạnh nội bộ chính là việc tạo ra các Model và sử dụng chúng để truy vấn dữ liệu trong phần 1, chúng ta cũng đã xem xét ở góc độ bên ngoài là các mối quan hệ giữa các Model, nó phản ảnh mối quan hệ trong cơ sở dữ liệu trong phần 2. Nếu nhìn trên mô hình MVC, các phần 1 và 2 chủ yếu xử lý các tương tác giữa Model và Database.

Mô hình MVC

Trong phần 3 này chúng ta sẽ tập trung vào các tương tác giữa Model với Controller, trước khi dữ liệu đi lên Controller rất cần thiết phải được tính toán, sàng lọc và chuyển định dạng. Bắt đầu vào các chi tiết cụ thể nào! Chú ý: Các bạn nên tham khảo các bài viết theo thứ tự vì các kiến thức của bài trước là nền tảng cho các bài tiếp theo:

1. Kết quả truy vấn Eloquent Model là một Collection

Kết quả các truy vấn Eloquent Model thông qua phương thức get() hoặc truy nhập thông qua mối quan hệ là một đối tượng của lớp Illuminate\Database\Eloquent\Collection, lớp này là một mở rộng của Lớp cơ bản Collection, do vậy nó được kế thừa hàng chục các phương thức để xử lý tập dữ liệu. Chúng ta sẽ bắt đầu một ví dụ giúp bạn dễ hình dung hơn sức mạnh của Eloquent Collection:

$users = App\User::where('active', 1)->get();

$names = $users->reject(function ($user) {
    return $user->active === false;
})->map(function ($user) {
    return $user->name;
});

Đoạn code này nhằm loại bỏ tất cả các user đang không hoạt động và lấy ra tên các user đang hoạt động. Chúng ta sẽ cùng nhau thực hiện một ví dụ khác phức tạp hơn, chúng ta có một tập dữ liệu về hoạt động của người dùng, chúng ta muốn đưa ra số lượng các click của người dùng theo ngày:

$summaryData = $userRequests->groupBy('value_date')
    ->map(function($objs, $key){
        return $objs->sum('click');
    })
    ->values();

Thực sự nếu thực hiện ví dụ thứ 2 này bằng cách viết các vòng lặp và xử lý là cực kỳ phức tạp, nhưng bằng cách sử dụng các phương thức thao tác với tập dữ liệu của Laravel Collection thì trở lên hết sức đơn giản.

Nếu bạn đã từng làm việc với thư viện Lodash khi lập trình Javascript, thì Laravel Collection cũng chính một thư viện tương tự cho PHP, nó bổ sung rất nhiều các hàm được sử dụng rất nhiều trong tính toán, và kết quả của truy vấn là một đối tượng Eloquent Collection đã thừa hưởng tất cả sức mạnh đó từ Laravel Collection. Trong bài viết này chúng ta sẽ không đi vào chi tiết các phương thức được xây dựng sẵn trong Laravel Collection mà chỉ liệt kê ra để chúng ta thấy được nó có thể xử lý những gì: - Nhóm các phương thức sử dụng cho tìm kiếm dữ liệu trong tập dữ liệu: contains(), where(), search(), has().

  • Nhóm các phương thức lọc dữ liệu: filter(), all()
  • Nhóm các phương thức tách ghép, so sánh tập dữ liệu: take(), chunk(), collapse(), combine(), zip(), diff(), implode(), merge(), reverse(), split(), union()
  • Nhóm các phương thức tính toán, thống kê tập dữ liệu: average(), avg(), max(), min(), sum(), count(), every(), median(), random(), unique()
  • Nhóm các phương thức thực hiện thông qua lặp các phần tử của tập dữ liệu: map(), transform(), reduce(), each(), flatMap, mapWithKey(), reject()
  • Nhóm các phương thức chuyển đổi dạng dữ liệu: toArray(), toJson()

(Thông tin và ví dụ chi tiết cho từng phương thức bạn có thể tham khảo trong Laravel Collection xử lý tập dữ liệu lớn) ## 2. Tự động định dạng trường trong cơ sở dữ liệu với Mutator và Accessor

Đôi khi chúng ta muốn tự động định dạng các giá trị khi lấy lên hoặc trước khi lưu vào cơ sở dữ liệu, ví dụ: bạn muốn một trường nào đó trước khi lưu xuống cơ sở dữ liệu sẽ được mã hóa lại và khi lấy từ cơ sở dữ liệu lên sẽ tự động thực hiện giải mã.

2.1 Định nghĩa Accessor

Accessor là phương thức sẽ được gọi đến khi truy xuất một thuộc tính của đối tượng, để định nghĩa Accessor, sử dụng phương thức có tên với quy tắc sau [get][Tên thuộc tính][Attribute], ví dụ chúng ta định nghĩa một Accessor để giải mã trường account_pin của User:

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

class User extends Model
{
    /**
     * Lấy trường mã PIN tài khoản đã được giải mã
     *
     * @param  string  $value
     * @return string
     */
    public function getAccountPinAttribute($value)
    {
        return decrypt($value);
    }
}

Giá trị gốc của trường account_pin sẽ được truyền cho Accessor, trong đó sẽ thực hiện các thay đổi dữ liệu và trả về giá trị mới cho trường account_pin khi được truy xuất. Các Accessor là ngầm định do đó khi truy vấn dữ liệu bạn vẫn thực hiện như bình thường:

$user = App\User::find(1);

$accountPin = $user->account_pin;

2.2 Định nghĩa Mutator

Accessor để thay đổi dữ liệu khi được tải ra từ cơ sở dữ liệu còn Mutator thì ngược lại nó thay đổi dữ liệu trước khi lưu xuống database:

<?php
namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Mã hóa account PIN trước khi lưu xuống database
     *
     * @param  string  $value
     * @return void
     */
    public function setAccountPinAttribute($value)
    {
        $this->attributes['account_pin'] = encrypt($value);
    }
}

Khi gán dữ liệu vào trường account_pin thì dữ liệu thực sự của trường này trong Model User sẽ được thông qua Mutator setAccountPinAttribute() mã hóa, và khi gọi phương thức save() dữ liệu mã hóa này sẽ được lưu xuống database.

$user = App\User::find(1);
$user->account_pin = '1234';
// Lưu account_pin đã mã hóa xuống database
$user->save();

2.3 Chuyển đổi dạng dữ liệu của thuộc tính

Thuộc tính $casts của Model cho phép bạn định nghĩa các quy tắc để chuyển đổi dạng dữ liệu của các thuộc tính của Model. $casts là một mảng với key chính là tên thuộc tính của Model và giá trị là các dạng dữ liệu integer, real, float, double, string, boolean, object, array, collection, date, datetime và timestamp. Ví dụ chúng ta chuyển đổi dạng dữ liệu của trường is_admin thành boolean, nó được lưu dưới database ở dạng integer.

<?php
namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'is_admin' => 'boolean',
    ];
}

Khi đó, trường is_admin được truy xuất có dạng boolean trong khi dưới database nó ở dạng dữ liệu Integer.

$user = App\User::find(1);

if ($user->is_admin) {
    //
}

2.3.1 Chuyển đổi dạng Array và JSON

Trong một số ứng dụng mới hiện nay, nhiều khi chúng ta lưu dữ liệu vào database ở dạng chuỗi JSON, tại sao vậy? Đây là một cách rất hay để mở rộng kiến trúc cơ sở dữ liệu mà không ảnh đến ứng dụng. Nếu bạn nào đã từng làm việc với hệ thống corebanking T24 của Temenos, một trong những điều thú vị là trường LOCAL_FIELD, trường này có ở tất cả các table và bạn có thể mở rộng dữ liệu tùy ý mà không cần sửa đổi code. Ví dụ: bảng users, sau một thời gian chúng ta cần thêm trường Chứng minh nhân dân (national_id) hoặc cần thêm trường họ tên bố đẻ (father_name), như vậy khi thêm các trường này vào bảng users chúng ta cần viết code để thêm sửa xóa cho chúng. Với JSON chúng ta có thể thêm thoải mái hơn, về ý tưởng là thế, với corebank T24 thì nó lưu dưới dạng XML.

Chuyển đổi dạng Array là rất hữu ích khi chúng ta làm việc với cột được lưu trữ ở dạng chuỗi JSON, khi thêm chuyển đổi dạng này vào $casts, nó sẽ tự động chuyển từ dữ liệu JSON sang thành mảng khi chúng ta truy cập vào thuộc tính của Model:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Chuyển đổi trường local_field từ JSON thành array khi truy xuất
     *
     * @var array
     */
    protected $casts = [
        'local_field' => 'array',
    ];
}

Khi việc chuyển đổi dạng được thực hiện, bạn truy nhập vào trường local_field sẽ có dạng mảng và nó sẽ tự động chuyển sang Json khi lưu xuống database.

$user = App\User::find(1);
$local_field = $user->local_field;
// Thêm các trường mở rộng vào local_field
$local_field['national_id'] = '0313049834';
$local_field['farther_name'] = 'Nguyễn Văn A';

$user->local_field = $local_field;
$user->save();

3. Chuyển đổi dạng dữ liệu từ Collection sang mảng hoặc chuỗi JSON

Vấn đề này gặp phải khi chúng ta xây dựng các web API, chúng thường có định dạng dữ liệu là JSON hoặc XML, hiện nay chủ yếu dữ liệu được định dạng ở dạng JSON. ### 3.1 Chuyển đổi Model và Collection thành mảng

Để chuyển đổi một Model và các dữ liệu truy vấn từ quan hệ của nó sang mảng chúng ta sử dụng phương thức toArray(), phương thức này sẽ thực hiện đệ quy và tất cả các thuộc tính và các quan hệ sẽ được chuyển đổi sang thành mảng:

$user = App\User::with('roles')->first();

return $user->toArray();

Ngoài ra, bạn cũng có thể chuyển đổi một Collection là kết quả của một truy vấn sang thành mảng cũng bằng phương thức toArray():

$users = App\User::all();

return $users->toArray();

3.2 Chuyển đổi Model và Collection thành JSON

Cũng tương tự như toArray(), toJson() chuyển đổi các một Model hoặc Collection thành chuỗi Json.

$user = App\User::find(1);

return $user->toJson();

3.3 Ẩn hoặc thêm một trường khi chuyển đổi sang dạng JSON

Trong chuyển đổi ở phần 3.2, đôi khi chúng ta muốn ẩn đi một trường hoặc thêm vào một trường trong kết quả là chuỗi JSON sau chuyển đổi. Ví dụ, chúng ta không muốn hiển thị trường password trong kết quả dạng JSON. Model cho phép làm việc đó bằng cách khai báo trong trường $hidden, khi muốn ẩn các dữ liệu quan hệ, sử dụng tên phương thức định nghĩa quan hệ để đưa vào $hidden.

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

class User extends Model
{
    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = ['password'];
}

Ngược lại, muốn hiển thị trường nào đó sử dụng thuộc tính $visible:

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

class User extends Model
{
    /**
     * The attributes that should be visible in arrays.
     *
     * @var array
     */
    protected $visible = ['first_name', 'last_name'];
}

Các trường không có trong mảng $visible sẽ được ẩn đi khi chuyển đổi sang dạng JSON. Chúng ta cũng có thể thiết lập dữ liệu ẩn hoặc hiện khi chuyển sang JSON một cách tạm thời bằng các phương thức makeVisible() và makeHidden():

return $user->makeVisible('attribute')->toArray();
return $user->makeHidden('attribute')->toArray();

Trên đây chúng ta bàn luận về việc ẩn một trường đi, ở chiều ngược lại đôi khi chúng ta muốn có thêm các trường khác khi chuyển đổi sang Array và Json. Chúng ta thực hiện chúng thông qua các Accessor như ở phần 2:

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

class User extends Model
{
    /**
     * Lấy tên đầy đủ của người dùng
     *
     * @return bool
     */
    public function getFullNameAttribute()
    {
        return $this->attributes['first_name'] . $this->attributes['last_name'];
    }
}

Sau đó chúng ta khai báo thuộc tính này với $appends:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * The accessors to append to the model's array form.
     *
     * @var array
     */
    protected $appends = ['full_name'];
}

4. Lời kết

Với các kiến thức trong phần 3 chúng ta có thể xào nấu dữ liệu sau khi lấy lên từ database tùy theo công thức của mình dù nó có phức tạp đến đâu chăng nữa, các công thức phụ (các phương thức được xây dựng sẵn) sẽ giúp bạn nhanh chóng có được một kết quả chính xác. Đến đây, chúng ta tạm nói lời chia tay với Laravel Eloquent ORM, thực sự tôi cũng còn một dự định viết phần 4 (một phần mà tôi đã hình dung được dàn bài, đây sẽ là một phần nâng cao rất hấp dẫn) vào một lúc nào đó có thời gian, tính tôi hay tham lam vậy đó. Rất có thể một ngày nào đó phần 4 sẽ được public với tiêu đề "Laravel Eloquent ORM phần 4: Mở rộng Eloquent Model, tại sao không?".

FirebirD

Đam mê Toán học, Lập trình. Sở thích chia sẻ kiến thức, Phim hài, Bóng đá, Cà phê sáng với bạn bè.

Null Object - Pattern đơn giản mà đâu cũng gặp

Laravel Artisan là gì, tại sao nói công cụ này cực hữu ích?

4 Bình luận trong "Laravel Eloquent ORM phần 3: xử lý dữ liệu đầu ra"

  1. truong

    1 year ago

    Phản hồi
    cam on nhung baif viet rat huu ich
  2. kyphung

    1 year ago

    Phản hồi
    Bài viết rất chất lượng. Hi vọng sớm được đón đọc phần 4 từ tác giả
  3. nmhttt

    1 year ago

    Phản hồi
    đợi bài 4 của bạn :D Mình đã coi nhiều tut laravel tiếng Việt thì rất đánh giá cao các bài viết của bạn, Cảm ơn nhiều.
  4. hk

    1 week ago

    Phản hồi

    Rất cảm ơn tác giả với bài viết hữu ích! Mong phần 4 ạ!

Thêm bình luận