HABTM with self需要连接表中行的2倍?

时间:2012-09-01 22:42:09

标签: cakephp has-and-belongs-to-many cakephp-2.1

我正在尝试构建一个以Nodes为主要模型的CMS。每个Node belongsTo一个NodeType,每个Node都可以与任何/其他Node相关。

所以 - 认为这要求HABTM:

//Node model
public $hasAndBelongsToMany = array(
    'AssociatedNode' => array(
        'className' => 'Node',
        'foreignKey' => 'node_id',
        'associationForeignKey' => 'associated_node_id',
        'joinTable' => 'node_associations'
    )
);

问题是,它似乎唯一可行的方法是每个关联都有两行。

只有一个关联行的示例:

节点

  • ER(id = 1)
  • George Clooney(id = 2)

连接表中的一行描述了这两个节点之间的关系:

  • 'node_id'= 1
  • 'associated_node_id'= 2

现在 - 如果我查询电视节目并包含它的演员节点:

$nodes = $this->Node->find('all', array(
        'conditions' => array(
            'Node.node_type_id' => '645' //tv shows
        ),
        'contain' => array(
            'AssociatedNode' => array(
                'conditions' => array(
                    'AssociatedNode.node_type_id' => '239' //actors
                ),
            )
        )
    ));

这有效,我得到ER - >乔治克鲁尼。

但是 - 如果我想拉出乔治克鲁尼所有的节目怎么办?

$nodes = $this->Node->find('all', array(
        'conditions' => array(
            'Node.node_type_id' => '239' //actors
        ),
        'contain' => array(
            'AssociatedNode' => array(
                'conditions' => array(
                    'AssociatedNode.node_type_id' => '645' //tv shows
                ),
            )
        )
    ));

这不起作用,因为它正在寻找George Clooney的ID在'node_id'字段中,并且ER的ID在'associated_node_id'字段中 - 实际上它们是相反的。

我想到的唯一解决方案是为每个关联保留两行。但这似乎有点矫枉过正。但是,我必须提出某种自定义的东西,确保每次保存或删除关联时保持每个副本与其他副本同步 - 等等 - 这似乎是一大堆蠕虫。 / p>

我有什么遗失的吗?

3 个答案:

答案 0 :(得分:3)

你可以用自定义查询来做,但为了保持标准的Cake函数,我能想到的一件事就是在节点之间声明两个关系:

public $hasAndBelongsToMany = array(
  'AssociatedNode1' => array(
      'className' => 'Node',
      'foreignKey' => 'node_id',
      'associationForeignKey' => 'associated_node_id',
      'joinTable' => 'node_associations'
  ),
  'AssociatedNode2' => array(
      'className' => 'Node',
      'foreignKey' => 'associated_node_id',
      'associationForeignKey' => 'node_id',
      'joinTable' => 'node_associations'
  )
);

然后你可以在afterFind回调中合并两个数组。

function afterFind($results)
{
  foreach($results as &$result)
  {
    if(isset($result['AssociatedNode1']) || isset($result['AssociatedNode2']))
    {
      $associated_nodes = array();

      if(isset($result['AssociatedNode1']))
      {
        foreach($result['AssociatedNode1'] as $associated_node)
        {
          $associated_nodes[] = $associated_node;
        }
      }

      if(isset($result['AssociatedNode2']))
      {
        foreach($result['AssociatedNode2'] as $associated_node)
        {
          $associated_nodes[] = $associated_node;
        }
      }

      $result['AssociatedNode'] = $associated_nodes;
    }
  }
  unset($result);

  return $results;
}

但是这会强制你在调用contains()时声明AssociatedNode1和AssociatedNode2;

答案 1 :(得分:1)

我不确定您的用例的详细信息,但我有几个备用选项:

您可能会考虑使用Tree Behavior - 这是为了将内容存储在树中而构建的,这听起来可能就是您正在做的事情。我自己没有使用它,所以我不确定它对你的使用有多适用。

另一方面,如果你以一致的方向存储关系(即总是电视节目 - >演员),并知道你的查询运行的方向(查看电视节目的树,一个演员正在观看在电视节目中找到演员,你应该能够在反向时查询AssociatedNode,例如

$nodes = $this->AssociatedNode->find('all', array(
    'conditions' => array(
        'AssociatedNode.node_type_id' => '239' //actors
    ),
    'contain' => array(
        'Node' => array(
            'conditions' => array(
                'Node.node_type_id' => '645' //tv shows
            ),
        )
    )
));

在这种情况下,为清晰起见,最好使用“ChildNode”而不是“AssociatedNode”。

但同样,这两个答案都取决于您的用例的细节 - nIcO的答案是一个很好的通用解决方案。它(必然)笨拙且可能更慢,但它很好地抽象了尴尬。

答案 2 :(得分:0)

我过去做过的一件事可能有帮助,就是为联接表创建一个模型。我能够在那里存储额外的数据,并根据我的查询做任何我想做的事情。然后在该连接模型的两侧只定义一个hasMany关联(也可以是belongsTo)。然后你可以使用连接模型进行查找并编写类似的东西(来自控制器):

$this->Node->NodesNode->find('all', array('conditions'=>array("or"=>array('node_id'=>$id,'sub_node_id'=>$id))));

恕我直言:没有什么能够迫使你使用蛋糕约定。我喜欢蛋糕,但有时它和ORM都很复杂。您可能只想编写自己的查询并自己解析结果。它可能比处理另一个行为或模型的开销更快,你可能会编写一个比给定的默认值更好的查询。

哦,最后,当你将1个模型用于多种用途时,我会留意。真的想想,如果一个模型真的应该支持一切。我发现,每当我做完这件事,我就会在一两年内重写整件事。你会很快碰到一个瓶颈,一些节点需要这样的额外行为,其他节点需要别的东西,你有if语句(或者更聪明的东西)分散在各处。另外,在db中执行基于疯狂树的查询确实会减慢速度。