hasAndBelongsToManyのバグ

Akelosでn対mのモデルを扱うときに新しくjoin_tableにレコードを追加したい場合多分以下のようにするのだと思う。

<?php
Ak::import('User,Group');

$user_model = new User();
$group_model = new Group();
$user = $userModel->find(1);
$user->group->add($group_model->find(array(1,2,3)));
$user->save();
?>

結果join_tableには以下のように追加されるはず。

SELECT * FROM groups_users;
+----+---------+----------+
| id | user_id | group_id |
+----+---------+----------+
|  1 |       1 |        1 |
|  2 |       1 |        2 |
|  3 |       1 |        3 |
+----+---------+----------+


実際試してみると期待通りにうまくいく。ちなみにUserモデルとGroupモデルはそれぞれ以下のような感じで定義されている。

models/user.php

<?php
class User extends AkActiveRecord
{
  var $habtm = 'groups';
}
?>

models/group.php

<?php
class Group extends AkActiveRecord
{
  var $habtm = 'users';
}
?>

ここで、Userモデルにもう一つn:mのroleモデルを定義すると

models/user.php

<?php
class User extends AkActiveRecord
{
  var $habtm = array('groups', 'role');
}
?>

models/tag.php

<?php
class Role extends AkActiveRecord
{
  var $habtm = array('users');
}
?>

となる。ここまではいい。

ここで、Userモデルからgroupとroleをそれぞれjoin_tableに追加してみる。

<?php
Ak::import('User,Group,Role');

$user_model = new User();
$group_model = new Group();
$role_model = new Role();

$user = $userModel->find(1);
$user->group->add($group_model->find(array(1,2,3)));
$user->role->add($role_model->find(array(1,2,3)));
$user->save();
?>

期待された結果は以下のような感じになるはず

SELECT * FROM groups_users;   SELECT * FROM roles_users;
+----+---------+----------+   +----+---------+---------+
| id | user_id | group_id |   | id | user_id | role_id |
+----+---------+----------+   +----+---------+---------+
|  1 |       1 |        1 |   |  1 |       1 |       1 |
|  2 |       1 |        2 |   |  2 |       1 |       2 |
|  3 |       1 |        3 |   |  3 |       1 |       3 |
+----+---------+----------+   +----+---------+---------+

ところが実際にやってみると、groupにはちゃんと追加されたがroleには何も追加されない。ソース追いかけて調べた結果akelosのバグっぽい。問題の箇所は以下のとおり

lib/AkActiveRecord/AkHasAndBelongsToMany.php

<?php
// 849行目付近

    function _afterCallback(&$object)
    {
        static $joined_items = array();
        $success = true;

        $object_id = $object->getId();
        foreach (array_keys($object->hasAndBelongsToMany->models) as $association_id){
            $CollectionHandler =& $object->hasAndBelongsToMany->models[$association_id];
            $options = $CollectionHandler->getOptions($association_id);

            $class_name = strtolower($CollectionHandler->getOption($association_id, 'class_name'));
            if(!empty($object->$association_id) && is_array($object->$association_id)){
                $this->_removeDuplicates($object, $association_id);
                foreach (array_keys($object->$association_id) as $k){
                    if(!empty($object->{$association_id}[$k]) && strtolower(get_class($object->{$association_id}[$k])) == $class_name){
                        $AssociatedItem =& $object->{$association_id}[$k];
                        // This helps avoiding double realation on first time savings
                        if(!in_array($AssociatedItem->__hasAndBelongsToManyMemberId, $joined_items)){
                            $joined_items[] = $AssociatedItem->__hasAndBelongsToManyMemberId;
                            if(empty($AssociatedItem->hasAndBelongsToMany->__joined) && $AssociatedItem->isNewRecord()? $AssociatedItem->save() : true){
                                $AssociatedItem->hasAndBelongsToMany->__joined = true;
                                $CollectionHandler->JoinObject =& $CollectionHandler->JoinObject->create(array($options['foreign_key'] => $object_id ,$options['association_foreign_key'] => $AssociatedItem->getId()));


                                $success = !$CollectionHandler->JoinObject->isNewRecord() ? $success : false;

                            }else{
                                $success = false;
                            }
                        }
                    }
                }
            }
            return $success; // ←多分こいつが原因。
        }
        return $success; // ←正しいのはここ。
    }

上記はsaveやupdate時に自動実行されるメソッドですが、最後のreturnの位置がおかしい。foreach内でreturnしちゃってるから、2個目以降のhabtmの実行がキャンセルされてしまっている。問題の箇所を削除して正しい位置に書き換えたらすんなりと動いた。

trac見たけどチケット上がってないなー。ユーザが少ないのかやっぱ。