[CakePHP]助けて！hasManyで結合したテーブルが取得できないの！[MySQL]

#テーブル#
ゲームから見てメーカーは多対1の関係(belongsTo)レビューは1対多(hasMany)の関係。

```GamesTable.php
class GamesTable extends Table
{
    public function initialize(array $config)
    {
        $this->hasMany('Reviews');

        $this->belongsTo('Makers');
    }
}
```
#selectしようとしたらなんかエラー出たんだけど！#
SQLを発行するときに、データをselectするのはいい考えですね。
クエリビルダーはselectメソッドを使わない場合、全てのカラムがselectされますが、
パフォーマンスを考えると必要なカラムだけ取得したいものです。

```ReviewController.php
<?php

namespace App\Controller;

use App\Controller\AppController;
use Cake\ORM\TableRegistry;

class GameController extends AppController
{
    public function index()
    {
        $reviews_table = TableRegistry::get('Games');
        $review = $reviews_table->find()
            ->contain('Makers', 'Reviews')
            ->select([
                'title' => 'Games.title',
                'price' => 'Games.price',
                'name' => 'Makers.name',
                'comment' => 'Reviews.comment'
            ]);
    }        
}
```

しかし、Reviews.commentなんてカラムはないよって怒られます。
でも同じアソシエーションしてるMakersテーブルからは持ってこれてるじゃん！なんで！？

これは、belongsToとhasManyとで発行しているSQLの違いにあります。
クエリオーオブジェクトをdebug()すると、データベースで発行してるSQLを見ることができます。

```sql
SELECT Games.title AS title, Games.price AS price, Makers.name AS name
FROM Games
LEFT JOIN Makers 
ON Games.id = Makers.Game_id;

SELECT Reviews.comment
FROM Revies
WHERE Game_id IN (1, 2, 3); /* このIN句の数字は上記のSQLで発行されたGamesテーブル主キー*/
```

だいたいこんな感じのSQLが発行されます。
belongsToで関連付けられたテーブルは、**LEFT JOIN**が使用されていることがわかりますね。

hasManyで関連付けれたテーブルは、**複数のSQLに分割して発行されます。**

ちなみに4つのアソシエーションがどのようなSQLを発行するかはこんな感じ。

| アソシエーション種別 | 発行されるSQL |
|:-:|:-:|
|  hasOne | LEFT JOIN  |
|  belongsTo |  LEFT JOIN |
| hasMany  |  分割 |
| belongsToMany  |  分割 |

##つまりどういうことだってばよ!?##
クエリビルダーで使ったselectメソッドは、１つ目のSQL文に対して適応されます。
一目瞭然ですが、１つ目に発行されたSQL分には```Reviews.comment```なんてカラムはかけらも見当たらないですね。

だからCakePHPちゃんは「お前の指定したカラム見つかれねえんだけど!!」とお怒りだったわけです。

##それじゃあhasManyやbelongsToManyでSELECTしたいときは？##

containにクロージャーを渡してあげます。

```php
$review = $reviews_table->find()
   ->contain(['Makers', 'Reviews' => function ($q) {
       return $q->select([
            'Game_id',
            'comment'
       ]);
   }])
   ->select([
       'title' => 'Games.title',
       'price' => 'Games.price',
       'name' => 'Makers.name',
    ]);
]);
```
ここで注意したいのは、関連テーブルを**selectする際には、外部キーを確実にselectする必要がある。**ということです。
外部キーは規約にしたがってると、ここではGame_idになります。

[公式](https://book.cakephp.org/3/ja/orm/retrieving-data-and-resultsets.html#id12)にもそう書いてありますね。

>関連によってフェッチされるフィールドを限定する場合、外部キーの列が確実に select されなければなりません 。外部キーのカラムが select されない場合、関連データが 最終的な結果の中に無いということがおこります。

って書いてあるけど外部キーがselectされてなかったエラー吐いてくれます。

##主キー側も(binding key)もselectされてなければならない！！##
それよりもやっかいなのはこっち。
主キーつまり、Gamesテーブルのidをselectしていないと、エラーを吐かないでただただhasMany関連が空で取得されます。
結構気づきくい。

まあこれは、hasManyで発行されるSQLを見ればなんでそうなるかなんとなく理解できると思います。

最終的にクエリビルダーはこうなる。

```php
$review = $reviews_table->find()
   ->contain(['Makers', 'Reviews' => function ($q) {
       return $q->select([
            'Game_id'
            'comment' => 'Reviews.comment'
       ]);
   }])
   ->select([ 
       'id',
       'title' => 'Games.title',
       'price' => 'Games.price',
       'name' => 'Makers.name',
    ]);
]);
```

##もう一個ハマリポイント##
主キーをselectしなきゃいけないけど、Makersテーブルとは```LEFT JOIN```してるから、idはアンビシャスになるんだろうなーって思いつつこんな書き方したら、

```php
->select([ 
   'id' => 'Games.id'
   'title' => 'Games.title',
   'price' => 'Games.price',
   'name' => 'Makers.name',
]);
```
だめだった。これもhasManyを取得できないです。
selectしなかった時にSQLログを見ると、```Games.id AS Games__id```みたいな書き方されてたから、

```php
->select([ 
   'Games__id' => 'Games.id'
   'title' => 'Games.title',
   'price' => 'Games.price',
   'name' => 'Makers.name',
]);
```
って感じにしたらうまくいきました。

というか、そもそもさっきみたいにidをselectしてもアンビシャスにならないっぽいですね。