今天要來講第三種 Collection:Resource Collection
如果畫成流程圖的話,三種 Collection 分別在這三個位置(簡略的畫一下我初步的理解)
前面有講過 API 與 RESTful API 的概念,後端工程師的工作就是建立這些 API (按鈕),讓前端工程師或其他使用者在需要時,按下特定按鈕可以得到某些特定資料。
所以 API 回傳的資料,就需要經過後端工程師的設計、處理,有些資料可能不適合傳遞給前端、有些資料為了符合前端需求需要進一步處理等等,就會在 API Resource 裡面撰寫。
備註:如果是純後端專案(前後端分離下),只需要回傳資料內容上圖,透過 API Resource 功能將這些資料加以整理。前後端不分離的狀況下,直接回傳變數帶入畫面渲染。
這裡故意比較兩種結果,通常專案不會既前後端不分離、又前後分離。
這裡我撰寫了一個 api route,當請求 (http://localhost:8000/api/group/34) 傳入專案時,會進到 GroupController,Controller 直接回傳 id=34 的團購資料。
Route::apiResource('/group', GroupController::class)
->only('show');
public function show(Group $group)
{
return $group;
}
比對前端需求,在這支 API 需要收到:團購名稱、團購圖片、結單金額、結單日期、是否允許使用者新增購買品項、訂單狀態等資訊,顯然剛剛打 API 回傳的結果需要進一步處理,包含:
這些需求後端工程師要跟前端進一步溝通,哪些欄位是不是真的不用顯示、如果是 null 要回傳什麼、前端處理還是後端處理(前端也可處理)。
這裡的範例可能沒有太大的感受,但如果回傳資料一多,十幾二十個欄位時就會很有感覺!
使用 php artisan 指令建立
class GroupResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'group_name' => $this->group_name,
"organizer_id" => $this->organizer_id,
"close_price" => isset($this->close_price) ? $this->close_price : "---",
"close_date" => isset($this->close_date) ? $this->close_date : "---",
"allow_insert_product" => $this->allow_insert_product,
"status" => $this->status,
"image_path" => Storage::url($this->image_path),
'products' => $this->whenLoaded('products'),
];
}
}
public function show(Group $group)
{
return GroupResource::make($group);
// 引入 GroupResource
}
搭啦~按照希望的方式回傳囉!
上面處理的是單筆資料,像 index() 功能要一次回傳多筆資料,就需要 Collection 協助
public function index() {
$groups = Group::where('status', Group::STATUS_OPEN)
->orderBy('close_date', 'ASC')
->orderBy('updated_at', 'DESC')
->orderBy('id', 'ASC')
->paginate(
$perPage = 4, $columns = ['*'], $pageName = 'groups'
);
return $groups;
}
public function index()
{
$groups = Group::where('status', Group::STATUS_OPEN)
->orderBy('close_date', 'ASC')
->orderBy('updated_at', 'DESC')
->orderBy('id', 'ASC')
->paginate(
$perPage = 4, $columns = ['*'], $pageName = 'groups'
);
return GroupResource::collection($groups); // 使用 collection() 方法
}
{
"data": [
{
"id": 36,
"group_name": "丸龜製麵",
"organizer_id": 1,
"close_price": "9000.00",
"close_date": "---",
"allow_insert_product": null,
"status": 1,
"image_path": "http://localhost:8000/storage/public/groups/FEnBqBkhfeXUPFuwh6MOBpUAgkS2ZAvGkMQduqTN.png"
},
{
"id": 34,
"group_name": "丸龜製麵",
"organizer_id": 1,
"close_price": "2500.00",
"close_date": "---",
"allow_insert_product": null,
"status": 1,
"image_path": "http://localhost:8000/storage/public/groups/lK9cFTKAI7nYFyWnsF9nkxlMxCpHmek4LkXChN4e.png"
},
{
"id": 2,
"group_name": "肯德基",
"organizer_id": 2,
"close_price": "1000.00",
"close_date": "---",
"allow_insert_product": 1,
"status": 1,
"image_path": "http://localhost:8000/storage/https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTPhDv3ncnk3W1CSDwU498WBrMmosbIChVBVA&usqp=CAU"
},
{
"id": 3,
"group_name": "睛水股份有限公司",
"organizer_id": 3,
"close_price": "1000.00",
"close_date": "---",
"allow_insert_product": 1,
"status": 1,
"image_path": "http://localhost:8000/storage/揪團囉.png"
}
],
"links": {
"first": "http://localhost:8000/api/group?groups=1",
"last": "http://localhost:8000/api/group?groups=4",
"prev": null,
"next": "http://localhost:8000/api/group?groups=2"
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 4,
"links": [
{
"url": null,
"label": "« Previous",
"active": false
},
{
"url": "http://localhost:8000/api/group?groups=1",
"label": "1",
"active": true
},
{
"url": "http://localhost:8000/api/group?groups=2",
"label": "2",
"active": false
},
{
"url": "http://localhost:8000/api/group?groups=3",
"label": "3",
"active": false
},
{
"url": "http://localhost:8000/api/group?groups=4",
"label": "4",
"active": false
},
{
"url": "http://localhost:8000/api/group?groups=2",
"label": "Next »",
"active": false
}
],
"path": "http://localhost:8000/api/group",
"per_page": 4,
"to": 4,
"total": 16
}
}
public function index()
{
$groups = Group::where('status', Group::STATUS_OPEN)
->orderBy('close_date', 'ASC')
->orderBy('updated_at', 'DESC')
->orderBy('id', 'ASC')
->paginate(
$perPage = 4, $columns = ['*'], $pageName = 'groups'
);
return GroupCollection::make($groups);
}
暫時不用任何操作 (對!你沒看錯)
回傳結果會跟上面 GroupResource::collection() 方法相同,不過兩個方法底層是不太相同的,有興趣可以自己追一下!
上面使用 GroupCollection 並沒有進一步撰寫不同邏輯,所以回傳結果相同,這次來試試看撰寫不同邏輯
class GroupCollection extends ResourceCollection
{
public function toArray(Request $request): array
{
return [
'groups' => $this->collection,
'message' => "Wonderful!"
];
}
}
{
"data": {
"groups": [ // 我希望 團購資料被塞入的陣列名稱
{
"id": 36,
"group_name": "丸龜製麵",
"organizer_id": 1,
"close_price": "9000.00",
"close_date": "---",
"allow_insert_product": null,
"status": 1,
"image_path": "http://localhost:8000/storage/public/groups/FEnBqBkhfeXUPFuwh6MOBpUAgkS2ZAvGkMQduqTN.png"
},
{
"id": 34,
"group_name": "丸龜製麵",
"organizer_id": 1,
"close_price": "2500.00",
"close_date": "---",
"allow_insert_product": null,
"status": 1,
"image_path": "http://localhost:8000/storage/public/groups/lK9cFTKAI7nYFyWnsF9nkxlMxCpHmek4LkXChN4e.png"
},
{
"id": 2,
"group_name": "肯德基",
"organizer_id": 2,
"close_price": "1000.00",
"close_date": "---",
"allow_insert_product": 1,
"status": 1,
"image_path": "http://localhost:8000/storage/https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTPhDv3ncnk3W1CSDwU498WBrMmosbIChVBVA&usqp=CAU"
},
{
"id": 3,
"group_name": "睛水股份有限公司",
"organizer_id": 3,
"close_price": "1000.00",
"close_date": "---",
"allow_insert_product": 1,
"status": 1,
"image_path": "http://localhost:8000/storage/揪團囉.png"
}
],
"message": "Wonderful!" // 我塞入的訊息
},
"links": {
"first": "http://localhost:8000/api/group?groups=1",
"last": "http://localhost:8000/api/group?groups=4",
"prev": null,
"next": "http://localhost:8000/api/group?groups=2"
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 4,
"links": [
{
"url": null,
"label": "« Previous",
"active": false
},
// 略 ...
],
"path": "http://localhost:8000/api/group",
"per_page": 4,
"to": 4,
"total": 16
}
}
今天的第三種 collection 跟前面兩種比較不一樣,但都是對多筆資料的處理。
看完這三篇,下次不會再被 Laravel Collection 這個字混淆啦~