色々躓いたのでまとめておきます。
前提
PHP 7.0
Laravel 5.5
データ構造
Work
id | category_id | title |
---|---|---|
1 | 1 | なんかのしごと |
WorkCategory
id | name |
---|---|
1 | 未分類 |
やりたいこと
上記データ構造のデータを下記のような形で返すAPIを作りたい
{
"id": "1",
"category_name": "未分類",
"title": "なんかのしごと"
}
かつ、Laravelさんのページネーション情報つきで!
1対1リレーションするようにモデルを変更する
class Work extends Model {
public function workCategory() {
return $this->hasOne('App\models\WorkCategory', 'id', 'category_id');
}
}
躓いたこと
workCategory
ではなく categoryName
にして名前だけ直接取ろうとして、なんか色々だめでした。
素直に関連するモデル名にしたらすんなり動きました。
下記だめだったコード
class Work extends Model {
public function categoryName() {
return $this->hasOne('App\models\WorkCategory', 'id', 'category_id')->name;
}
}
主に name
が存在しないという感じで怒られていました。
また、名前だけを取る方法をやめ WorkCategory
自体を持つように変えています。
EagerLoad を使う
Work::paginate(9);
ではなく Work::with('workCategory')->paginate(9);
を使う。
class WorkRepository {
public function getPaginateWorks() {
return Work::with('workCategory')->paginate(9);
}
}
Resources を用意してJSONに変換する
Laravel 5.5 Eloquent: APIリソース のようにモデルをJSONに変換する層が用意されているみたいです。
$ php artisan make:resource PaginatedWorks --collection
artisan
にリソースを作ってもらって、先程の変換ロジックを入れます。
class PaginatedWorks extends ResourceCollection {
public function toArray($request) {
return ['data' => $this->collection->transform([$this, 'map'])];
}
public function map($work) {
return [
'id' => $work->id,
'category_name' => $work->workCategory->name,
'title' => $work->title
];
}
}
paginate 内のデータ変換には transform
メソッドを使うと良いみたいです。
callable は配列で渡してもOK
Resouces
を知る前に書いていたので Repository
になっています
class WorkRepository {
public function getPaginateWorks() {
$works = Work::with('workCategory')->paginate(9);
$works->getCollection()->transform([$this, 'map']);
return $works;
}
public function map(Work $work) {
return [
'id' => $work->id,
'category_name' => $work->workCategory->name,
'title' => $work->title
];
}
}
はじめのうち transform
に $this->map
を渡していて
Undefined property: App\\Repositories\\WorkRepository::$map
と怒られていました。
調べたところ [$this, 'map']
という風に配列で渡してやれば実行してくれるとのこと。
渡す配列は1つ目にインスタンス、2つ目に実行するメソッド名の文字列。
下記だめだったコード
class WorkRepository {
public function getPaginateWorks() {
$works = Work::with('workCategory')->paginate(9);
$works->getCollection()->transform($this->map);
return $works;
}
private function map(Work $work) {
return [
'id' => $work->id,
'category_name' => $work->workCategory->name,
'title' => $work->title
];
}
}
渡したいメソッドを private
で定義していたところも躓いていました。
なお PHP7.1 以上であれば private で渡せる方法がある模様です。
結果
class WorksController extends Controller {
public function __construct() {
}
public function index(Request $request) {
$paginatedWorks = (new WorkRepository())->getPaginatedWorks();
return new PaginatedWorks($paginatedWorks);
}
}
class Work extends Model {
public function workCategory() {
return $this->hasOne('App\models\WorkCategory', 'id', 'category_id');
}
}
class WorkRepository {
public function getPaginateWorks() {
return Work::with('workCategory')->paginate(9);
}
}
class PaginatedWorks extends ResourceCollection {
public function toArray($request) {
return ['data' => $this->collection->transform([$this, 'map'])];
}
public function map($work) {
return [
'id' => $work->id,
'category_name' => $work->workCategory->name,
'title' => $work->title
];
}
}
{
"data": [
{
"id": 1,
"category_name": "\u672a\u5206\u985e",
"title": "\u30af\u30ec\u30b9\u30c8\u30db\u30fc\u30eb\u30c7\u30a3\u30f3\u30b0\u30b9 \u30b3\u30fc\u30dd\u30ec\u30fc\u30c8\u30b5\u30a4\u30c8\u5236\u4f5c"
}
],
"links": {
"first": "\/api\/works?page=1",
"last": "\/api\/works?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "\/backoffice\/api\/works",
"per_page": 9,
"to": 1,
"total": 1
}
}
Response
の path
は少し変えてます。