belongsToMany、とくにjoinTableにデータを持たせたいときに、どうやってデータを保存して、取り出せば良いのか・・・色々調べたのでメモします。
テーブル・モデル作成
phinxでテーブルを作っておきます。
今回は、UserとDogの関係をhasMany、DogとDogChildrenの関係をbelongsToManyとしています。
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();
}
マイグレーションできたら、モデルも作ります。
./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さん、ありがとうございます。)。
$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されるためです。
$userArray = [
'name' => 'Tony',
'dogs' => [[
'name' => 'pochi',
'dog_children' => [[
'name' => 'tama',
'_joinData' => ['flg' => true]
],
[
'name' => 'tama',
'_joinData' => ['flg' => false]
]]
]]];
こういうときは、あらかじめtamaという名前のDogChildEntityを保存しておき、プロパティに_joinDataを追加すると大丈夫です。
$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]
]]];
データ取得
では、保存されたデータを見てみましょう。
$users = TableRegistry::get('users');
$user = $users->find()->contain(['Dogs', 'Dogs.DogChildren'])->first();
これで$userには、Dogs、Dogs.DogChildrenのデータを持ったUserEntityが代入されます。
cakePHP3からrecursiveはなくなってしまったので、代わりにcontainを使って関連データを取得できます。
ちなみに、find()のほかにget()もあり、引数にprimary_keyを入れることでデータを取ってこれるのですが、これはget()を動かした時点でEntityが返ってくるので、メソッドチェーンでcontainを使うことができません。
dogsやdog_childrenは配列に入っているので、こんな感じで取ってこれます。
$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^)/