Laravelでリレーションされているデータをpaginateする

色々躓いたのでまとめておきます。

# 前提

PHP 7.0
Laravel 5.5

## データ構造

### Work

|id|category_id|title|
|:--|:--|:--|
|1|1|なんかのしごと|

### WorkCategory

|id|name|
|:--|:--|
|1|未分類|

# やりたいこと

上記データ構造のデータを下記のような形で返すAPIを作りたい

```json
{
  "id": "1",
  "category_name": "未分類",
  "title": "なんかのしごと" 
}
```

かつ、Laravelさんのページネーション情報つきで！

# 1対1リレーションするようにモデルを変更する

[Eloquent：リレーション 5.5 Laravel](https://readouble.com/laravel/5.5/ja/eloquent-relationships.html#one-to-one)

```php:Work.php
class Work extends Model {
    public function workCategory() {
        return $this->hasOne('App\models\WorkCategory', 'id', 'category_id');
    }
}
```

## 躓いたこと

`workCategory` ではなく `categoryName` にして名前だけ直接取ろうとして、なんか色々だめでした。
素直に関連するモデル名にしたらすんなり動きました。

下記だめだったコード

```php:だめだったコード
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);` を使う。

```php:WorkRepository.php
class WorkRepository {

    public function getPaginateWorks() {
        return Work::with('workCategory')->paginate(9);
    }
}
```

# Resources を用意してJSONに変換する

[Laravel 5.5 Eloquent: APIリソース](https://readouble.com/laravel/5.5/ja/eloquent-resources.html) のようにモデルをJSONに変換する層が用意されているみたいです。

```console
$ php artisan make:resource PaginatedWorks --collection
```

`artisan` にリソースを作ってもらって、先程の変換ロジックを入れます。

```php:PaginatedWorks.php
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` になっています**

```php:WorkRepository.php
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つ目に実行するメソッド名の文字列。

下記だめだったコード

```php:だめだったコード
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 で渡せる方法がある模様です。](https://www.php.net/manual/ja/closure.fromcallable.php)

# 結果

```php:WorkController.php
class WorksController extends Controller {
    public function __construct() {
    }

    public function index(Request $request) {
        $paginatedWorks = (new WorkRepository())->getPaginatedWorks();
        return new PaginatedWorks($paginatedWorks);
    }
}
```

```php:Work.php
class Work extends Model {
    public function workCategory() {
        return $this->hasOne('App\models\WorkCategory', 'id', 'category_id');
    }
}
```

```php:WorkRepository.php
class WorkRepository {
    public function getPaginateWorks() {
        return Work::with('workCategory')->paginate(9);
    }
}
```

```php:PaginatedWorks.php
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
        ];
    }
}
```

```json:Response
{
  "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` は少し変えてます。