【问题标题】:HABTM with self requires 2x the rows in join table?带有 self 的 HABTM 需要连接表中的 2 倍行数?
【发布时间】:2012-09-01 22:42:08
【问题描述】:

我正在尝试构建一个以Nodes 为主要模型的CMS。每个NodebelongsTo 一个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)
  • 乔治克鲁尼 (id=2)

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

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

现在 - 如果我查询 TV Shows 并包含它的 Actor 节点:

$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 -> George Clooney。

但是 - 如果我想取消所有乔治克鲁尼参加的节目怎么办?

$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”字段中 - 而实际上它们是相反的。

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

我有什么遗漏吗?

【问题讨论】:

  • 对我来说听起来像是一种树行为:D

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


【解决方案1】:

您可能可以使用自定义查询来做到这一点,但为了保持标准的 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;

【讨论】:

    【解决方案2】:

    我不确定您的用例的详细信息是什么,但我为您提供了几个替代选项:

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

    另一方面,如果您将关系存储在一致的方向(即始终是 TV Show->Actor)并知道您的查询运行的方向(查找树以查找演员所在的电视节目与向下查找查找电视节目中的演员),您应该能够在反向时查询 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 的答案是一个很好的通用解决方案。它(必然)笨拙并且可能更慢,但它很好地消除了笨拙。

    【讨论】:

      【解决方案3】:

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

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

      恕我直言:没有什么真正强迫您使用蛋糕惯例。我喜欢蛋糕,但有时它和 ORM 都会使非常简单的事情变得复杂。您可能只想编写自己的查询并自己解析结果。它可能比处理其他行为或模型的开销更快,而且您可能会编写比给定默认值更好的查询。

      哦,最后,当您将 1 个模型用于多种用途时,我会小心的。真的想一想那个模型是否真的应该支持一切。我发现每当我这样做时,我都会在一两年内重写整个内容。您将很快遇到瓶颈,其中一些节点需要以这种方式进行额外行为,而另一些则需要其他行为,并且您的 if 语句(或者可能更聪明的东西)散布在各处。另外,在数据库中进行基于树的疯狂查询确实会减慢速度。

      【讨论】:

        猜你喜欢
        • 2013-08-07
        • 1970-01-01
        • 1970-01-01
        • 2013-02-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多