Sử dụng API Resources trong Laravel

Sau buổi sharing API Security của anh MinhNV, các anh em Dev đã nhắc đến API Resource trong Laravel. Một chức năng rất hay của Laravel mà mọi dev nên cần biết và sử dụng nó.

Về nguồn gốc thì ở phiên bản  Laravel 5.5, chức năng API Resources được ra đời.

Trên trang docs chính chủ của Laravel có giới thiệu:

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. For example, you may wish to display certain attributes for a subset of users and not others, or you may wish to always include certain relationships in the JSON representation of your models. Eloquent'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

Hoặc

php artisan make:resource UserCollection

Đơn giản ta có thể hiểu 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 $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }

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

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

public function toArray(Request $request): array
    {
        return [
            'data' => $this->collection,
            'links' => [
                'self' => 'link-value',
            ],
        ];
    }

Data Wrapping

Theo mặc định, resource response được chuyển thành JSON. Ví dụ, một resource collection response điển hình hiển thị như sau:

{
    "data": [
        {
            "id": 1,
            "name": "Eladio Schroeder Sr.",
            "email": "therese28@example.com"
        },
        {
            "id": 2,
            "name": "Liliana Mayert",
            "email": "evandervort@example.com"
        }
    ]
}

Ví dụ lỗi thường gặp khi sử dụng

Lẫn lộn giữa Resouce và ResouceCollection

Yêu cầu bài toán: mong muốn lấy ra danh sách bài hát thông qua API và các thông tin tương ứng cần có

# routes/api.php
Route::get('/songs', function() {
    return new SongResource(Song::all());
});
class SongsResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'artist' => $this->artist
        ];
    }
}

Kết quả: Lỗi thông báo Property [id] does not exist on this collection instance. xuất hiện vì SongsResource chỉ cho phép nhận một giá trị đơn lẻ chứ không phải một tập giá trị collection =))

Vậy đến đây bạn sẽ giải quyết như thế nào ?
Mình có đưa ra 2 giải pháp tùy thuộc vào dữ liệu mong muốn cần hiển thị

Chỉ hiện có các trường đã được định nghĩa trong SongResource:

# routes/api.php
// Lấy danh sách các bài hát 
Route::get('/songs', function() {
    return SongsResource::collection(Songs::all());
});

Thêm trường mới vào dữ liệu trả về

class SongsCollection extends ResourceCollection
{
    public function toArray($request)
    {
        return [
            'data' => SongsResource::collection($this->collection),
            'meta' => [
                'count' => $this->collection->count(),
                'link' => 'value-link',
            ],
        ];
    }
}
Route::get('/songs', function() {
    return new SongsCollection(Songs::all());
});

Kết quả: trường thêm mới count và link được thêm mới vào và với việc sử dụng SongsResource::collection($this->collection) bạn có thể tùy chỉnh các trường dữ liệu trả về từ SongsResource.


Rất mong bài viết có thể giúp ích gì đó cho ae và nhận được sự đóng góp từ mọi người.

Related Posts