cakePHP3でbelongsToMany

[belongsToMany](http://book.cakephp.org/3.0/en/orm/associations.html#belongstomany-associations)、とくにjoinTableにデータを持たせたいときに、どうやってデータを保存して、取り出せば良いのか・・・色々調べたのでメモします。

# テーブル・モデル作成

phinxでテーブルを作っておきます。
今回は、UserとDogの関係をhasMany、DogとDogChildrenの関係をbelongsToManyとしています。

```php
public function change()
    {
        $this->table('users')
            ->addColumn('name', 'string', array('limit' => 20))
            ->create();

        $this->table('dogs')
            ->addColumn('name', 'string', array('limit' => 20))
            ->addColumn('user_id', 'integer')
            ->addForeignKey('user_id', 'users', 'id')
            ->create();

        $this->table('dog_children')
            ->addColumn('name', 'string', array('limit' => 20))
            ->create();

        $this->table('dogs_dog_children')
            ->addColumn('dog_id', 'integer')
            ->addColumn('dog_child_id', 'integer')
            ->addColumn('flg', 'boolean')
            ->create();
    }
```

マイグレーションできたら、モデルも作ります。

```shell-session
./bin/cake migrations migrate
./bin/cake bake model users
./bin/cake bake model dogs
./bin/cake bake model dog_children
./bin/cake bake model dogs_dog_children
```

# データ保存

データの保存も取得も、なんと一気にできます（@AnzNetJpさん、ありがとうございます。）。

```php
$users = TableRegistry::get('users');
$userArray = [
    'name' => 'Tony',
    'dogs' => [[
        'name' => 'pochi',
        'dog_children' => [[
            'name' => 'tama',
            '_joinData' => ['flg' => true]
        ]]
    ]]];
$users->save($users->newEntity($userArray, ['associated' => 'Dogs.DogChildren._joinData']));
```

ポイントは、_joinDataを使ってjoinTableにデータを保存しているところです。
あと、dog_childrenのvalueが二重配列になってるのも間違えやすいところだと思います。
newEntityを作るときのassociatedOptionは必須です。

ただ、dog_childrenのnameがユニークの必要がある場合、下記のようなデータは保存できません。
理由は、tamaという名前を持つDogChildEntityが毎回saveされるためです。

```php
$userArray = [
    'name' => 'Tony',
    'dogs' => [[
        'name' => 'pochi',
        'dog_children' => [[
            'name' => 'tama',
            '_joinData' => ['flg' => true]
        ],
        [
            'name' => 'tama',
            '_joinData' => ['flg' => false]
        ]]
    ]]];
```

こういうときは、あらかじめtamaという名前のDogChildEntityを保存しておき、プロパティに_joinDataを追加すると大丈夫です。

```php
$dogChildren = TableRegistry::get('dog_children');
$dogChild = $dogChildren->newEntity();
$dogChild->name = 'tama';
$dogChildren->save($dogChild);

$dogChild_1 = $dogChildren->find()->where(['name' => 'tama'])->first();
$dogChild_1->_joinData->flg = true;

$dogChild_2 = $dogChildren->find()->where(['name' => 'tama'])->first();
$dogChild_2->_joinData->flg = false;

$userArray = [
    'name' => 'Tony',
    'dogs' => [[
        'name' => 'pochi',
        'dog_children' => [$dogChild_1, $dogChild_2]
    ]]];
```

# データ取得

では、保存されたデータを見てみましょう。

```php
$users = TableRegistry::get('users');
$user = $users->find()->contain(['Dogs', 'Dogs.DogChildren'])->first();
```

これで$userには、Dogs、Dogs.DogChildrenのデータを持ったUserEntityが代入されます。
cakePHP3から[recursiveはなくなってしまった](http://book.cakephp.org/3.0/en/appendices/orm-migration.html#recursive-and-containablebehavior-removed)ので、代わりにcontainを使って関連データを取得できます。

ちなみに、find()のほかにget()もあり、引数にprimary_keyを入れることでデータを取ってこれるのですが、これはget()を動かした時点でEntityが返ってくるので、メソッドチェーンでcontainを使うことができません。

dogsやdog_childrenは配列に入っているので、こんな感じで取ってこれます。

```php
$user = $users->find()->contain(['Dogs', 'Dogs.DogChildren'])->first();
foreach ($user->dogs as $dog) {
    var_dump($dog);
    foreach ($dog->dog_children as $dog_child) {
        var_dump($dog_child);
        var_dump($dog_child->_joinData);
    }
}
/*
class App\Model\Entity\Dog#121 (10) {
  protected $_accessible =>
  array(4) {
    'name' =>
    bool(true)
    'user_id' =>
    bool(true)
    'user' =>
    bool(true)
    'dog_children' =>
    bool(true)
  }
  protected $_properties =>
  array(4) {
    'id' =>
    int(1)
    'name' =>
    string(5) "pochi"
    'user_id' =>
    int(1)
    'dog_children' =>
    array(1) {
      [0] =>
      class App\Model\Entity\DogChild#114 (10) {
        ...
      }
    }
  }
  protected $_original =>
  array(0) {
  }
  protected $_hidden =>
  array(0) {
  }
  protected $_virtual =>
  array(0) {
  }
  protected $_className =>
  string(20) "App\Model\Entity\Dog"
  protected $_dirty =>
  array(0) {
  }
  protected $_new =>
  bool(false)
  protected $_errors =>
  array(0) {
  }
  protected $_registryAlias =>
  string(4) "Dogs"
}
class App\Model\Entity\DogChild#114 (10) {
  protected $_accessible =>
  array(2) {
    'name' =>
    bool(true)
    'dogs' =>
    bool(true)
  }
  protected $_properties =>
  array(3) {
    'id' =>
    int(1)
    'name' =>
    string(4) "tama"
    '_joinData' =>
    class App\Model\Entity\DogsDogChild#111 (10) {
      protected $_accessible =>
      array(5) {
        ...
      }
      protected $_properties =>
      array(4) {
        ...
      }
      protected $_original =>
      array(0) {
        ...
      }
      protected $_hidden =>
      array(0) {
        ...
      }
      protected $_virtual =>
      array(0) {
        ...
      }
      protected $_className =>
      string(29) "App\Model\Entity\DogsDogChild"
      protected $_dirty =>
      array(0) {
        ...
      }
      protected $_new =>
      bool(false)
      protected $_errors =>
      array(0) {
        ...
      }
      protected $_registryAlias =>
      string(15) "DogsDogChildren"
    }
  }
  protected $_original =>
  array(0) {
  }
  protected $_hidden =>
  array(0) {
  }
  protected $_virtual =>
  array(0) {
  }
  protected $_className =>
  string(25) "App\Model\Entity\DogChild"
  protected $_dirty =>
  array(0) {
  }
  protected $_new =>
  bool(false)
  protected $_errors =>
  array(0) {
  }
  protected $_registryAlias =>
  string(11) "DogChildren"
}
class App\Model\Entity\DogsDogChild#111 (10) {
  protected $_accessible =>
  array(5) {
    'dog_id' =>
    bool(true)
    'dog_child_id' =>
    bool(true)
    'flg' =>
    bool(true)
    'dog' =>
    bool(true)
    'dog_child' =>
    bool(true)
  }
  protected $_properties =>
  array(4) {
    'dog_child_id' =>
    int(1)
    'id' =>
    int(1)
    'dog_id' =>
    int(1)
    'flg' =>
    bool(true)
  }
  protected $_original =>
  array(0) {
  }
  protected $_hidden =>
  array(0) {
  }
  protected $_virtual =>
  array(0) {
  }
  protected $_className =>
  string(29) "App\Model\Entity\DogsDogChild"
  protected $_dirty =>
  array(0) {
  }
  protected $_new =>
  bool(false)
  protected $_errors =>
  array(0) {
  }
  protected $_registryAlias =>
  string(15) "DogsDogChildren"
}
*/
```

さて、dogs_dog_childrenに作ったflgはどこにあるかというと・・・dog_childの_joinDataの中にありましたー！
（すみません、Ctrl+Fで検索でもしてみてください。。）
まぁ、保存するときに_joinData使いましたし、当然といえば当然なのですが、私はデータの保存ではなく取得から作り始めたため、どうやって取得すればいいのか結構迷いました・・・。

では、良いcakeLifeを！！＼(^o^)／