Xây dựng APIs bằng Laravel với Eloquent API Resources

Một chức năng mới được bổ sung trong Laravel 5.5 là API Resources, bạn có thể đọc tại liệu chính thống tại đây Trong khuôn khổ bài viết này mình sẽ tìm hiểu xem thằng này có rì ngon 😄


Giới thiệu trên docs chính chủ:

When building an API, you may need a transformation layer that sits between your Eloquent models and the JSON responses that are actually returned to your application's users. Laravel's resource classes allow you to expressively and easily transform your models and model collections into JSON.

Đại khái là khi build API chúng ta sẽ cần chuyển đổi data từ Eloquent models sang JSON, API Resources sẽ giúp chúng ta dễ dàng hơn trong việc này.

Tạo API Resources

Để sử dụng resource trước tiên chúng ta cần tạo 1 model và resource cho table của chúng ta bằng command (ở đây mình ví dụ với model User):

php artisan make:resource User

Command này sẽ tạo một class User ở app/Http/Resources với nội dung như sau:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\Resource;

class User extends Resource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request
     * @return array
     */
    public function toArray($request)
    {
        return parent::toArray($request);
    }
}

Có một tùy chọn khác cho command này là:

php artisan make:resource User --collection

Command này cũng tạo class User như trên nhưng extends từ class Illuminate\Http\Resources\Json\ResourceCollection thay vì Illuminate\Http\Resources\Json\Resource Một cách khác để tạo resource collection:

php artisan make:resource UserCollection

Hiểu đơn giản, resource bình thường thì làm việc với dữ liệu đơn còn resource collection thì làm việc vói dữ liệu là collection.

Sử dụng thế nào

Mặc định trong resource sẽ có sẵn phương thức toArray có nội dung như sau:

    public function toArray($request)
    {
        return parent::toArray($request);
    }

Bạn có thể custom lại phương thức này để phù hợp với mục đích của mình, nếu để nguyên thì trả về dữ liệu mặc định thôi, ví dụ:

    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => (string)$this->created_at,
            'updated_at' => $this->updated_at,
            'status' => 'success'
        ];
    }

Chúng ta sử dụng như sau:

use App\User;
use App\Http\Resources\User as UserResource;

Route::get('/user', function () {
    return new UserResource(User::find(1));
});

Kết quả trả về như ý muốn của chúng ta:

Hoặc đối với resource collection:

    public function toArray($request)
    {
        return [
            'data' => $this->collection,
            'response' => [
                'status' => 'success',
                'code' => 200
            ],
        ];
    }

Có 2 cách để sử dụng resource collection: Hoặc (*):

use App\User;
use App\Http\Resources\User as UserResource;

Route::get('/user', function () {
    return UserResource::collection(User::all());
});

hoặc (**)

use App\User;
use App\Http\Resources\UserCollection as UserResource;

Route::get('/user', function () {
    return new UserResource(User::all());
});

Bạn xem hai class khác nhau đấy nhé, coi chừng nhầm lẫn, nhắc lại nè, class App\Http\Resources\User extends từ Illuminate\Http\Resources\Json\Resource còn UserCollection thì extends từ ResourceCollection Kết quả của * sẽ như sau: Còn kết quả của ** sẽ là: Bạn xem lại cách viết phương thức toAray ở hai resource và so sanh nhé với kết quả nhé.

Sử dụng với relationship

Mình muốn include danh sách posts của User thì mình thêm vào thôi:

use App\Http\Resources\Post;
...
public function toArray($request)
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'posts' => Post::collection($this->posts),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}

nếu muốn chỉ thêm trường posts khi mà quan hệ đã được load thì dùng method whenLoaded

'posts' => Post::collection($this->whenLoaded('posts')),

Ngoài ra còn có một số phương thức hữu dụng khác như when, mergeWhen, whenPivotLoaded

// secret chỉ xuất hiện khi khi `$this->isAdmin()` trả về `true` và trả về 'secret-value'
// đối số thứ 2 có thể là 1 `Closure `
'secret' => $this->when($this->isAdmin(), 'secret-value'), 

// cũng gần giống như trên
$this->mergeWhen($this->isAdmin(), [
    'first-secret' => 'value',
    'second-secret' => 'value',
]),

// chỉ khi pivot `expires_at` đã được load thì trường này mới xuất hiện
'expires_at' => $this->whenPivotLoaded('role_users', function () {
    return $this->pivot->expires_at;
}),

Data Wrapping

Mặc định dữ liệu trả về từ resource sẽ được bao bọc bởi key data, nếu không muốn bạn có thể remove nó đi bằng cách sử dụng phương thức withoutWrapping của resource

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Http\Resources\Json\Resource;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Perform post-registration booting of services.
     *
     * @return void
     */
    public function boot()
    {
        Resource::withoutWrapping();
    }
...
}

Phương thức này chỉ remove key data mặc định đi, nếu bạn tự bao bọc dữ liệu của mình thì phương thức này sẽ không dám bỏ key của bạn đi đâu 😄

Lúc đầu mình đọc tài liệu về phần này thì mình không hiểu cái này để làm cái beep gì nhỉ vì đâu cần nó vẫn trả về JSON từ eloquent được mà, nhưng mà tìm hiểu ra thì mới thấy nó tuyệt vời lắm đấy mọi người.

All Rights Reserved