自我"HABTM"或"HasMany Through"概念混乱

Dav*_*ave 7 cakephp associations has-and-belongs-to-many has-many-through cakephp-2.1

赏金:

+500代表赏金的解决方案.我已经严重撞击这堵墙了两个星期了,我已准备好帮忙了.

表/模型(简化为显示关联)

  • 节点
    • ID
    • 名称
    • node_type_id
  • node_associations
    • ID
    • NODE_ID
    • other_node_id
  • node_types
    • ID
    • 名称

大概的概念:

用户可以创建节点类型(例如"电视台","电视节目"和"演员"......任何东西).如果我提前知道节点类型是什么以及每个节点之间的关联,我只是为它们制作模型 - 但我希望这是非常开放的,以便用户可以创建他们想要的任何节点类型.然后,(特定节点类型的)每个节点可以与任何其他节点类型的任何其他节点相关.

描述和我尝试过的内容:

每个节点都应该能够与任何/每个其他节点相关联.

我的假设是,要做到这一点,我必须有一个关联表 - 所以我做了一个名为"node_associations",其中包含node_idother_node_id.

然后我建立了我的关联(通过我相信使用hasMany):(以下是我对我的设置的最好记忆......它可能稍微关闭)

//Node model
public $hasMany = array(
    'Node' => array(
        'className' => 'NodeAssociation',
        'foreignKey' => 'node_id'

    ),
    'OtherNode' => array(
        'className' => 'NodeAssociation',
        'foreignKey' => 'other_node_id'
    )
);

//NodeAssociation model
public $belongsTo = array(
    'Node' => array(
        'className' => 'Node',
        'foreignKey' => 'node_id'

    ),
    'OtherNode' => array(
        'className' => 'Node',
        'foreignKey' => 'other_node_id'
    )
);
Run Code Online (Sandbox Code Playgroud)

起初,我以为我有它 - 这是有道理的.但后来我开始尝试检索数据,过去两周一直在撞墙.

示例问题:

可以说我有以下节点:

  • NBC
  • ER
  • 乔治·克鲁尼
  • 安东尼爱德华兹
  • 今晚秀:Leno
  • 杰莱诺
  • 狐狸
  • 家庭的家伙

如何设置我的数据结构以便能够拉出所有电视台,并包含他们的电视节目,其中包含他们的演员(例如)?对于正常的模型设置,这将是简单的:

$this->TvStation->find('all', array(
    'contain' => array(
        'TvShow' => array(
            'Actor'
        )
    )
));
Run Code Online (Sandbox Code Playgroud)

然后,也许我想要检索所有男性演员并包含包含电视台的电视节目.或电视节目从晚上9点开始,包含它的演员和它的电视台等等.

但是 - 使用HABTM或HasMany通过self(更重要的是,未知的数据集),我不知道该模型是哪个字段(node_id或other_node_id),总体上无法理解我是如何得到的内容.

tig*_*ang 2

想法

让我们尝试用约定来解决这个问题,node_id 将是别名按字母顺序排在第一位的模型,而 other_node_id 将是第二位的别名。

对于每个包含的模型,我们动态创建一个与 Node 类的 HABTM 关联,​​为每个关联创建一个别名(请参阅bindNodesbindNode方法)。

我们查询的每个表都会添加一个额外的条件,node_type_id以便仅返回该类型节点的结果。NodeType的id是通过选择的getNodeTypeId()并且应该被缓存。

要在深度相关的关联中使用条件过滤结果,您需要手动添加额外的联接,为每个具有唯一别名的可连接创建联接,然后使用别名联接每个节点类型本身,以便能够应用条件(例如,选择所有具有演员 x) 的电视频道。在 Node 类中为此创建一个辅助方法。

笔记

我用了foreignKeyfornode_idassociationForeignKeyforother_node_id我的演示。

节点(不完整)

<?php
/**
 * @property Model NodeType
 */
class Node extends AppModel {

    public $useTable = 'nodes';

    public $belongsTo = [
        'NodeType',
    ];

    public function findNodes($type = 'first', $query = []) {
        $node = ClassRegistry::init(['class' => 'Node', 'alias' => $query['node']]);
        return $node->find($type, $query);
    }

    // TODO: cache this
    public function nodeTypeId($name = null) {
        if ($name === null) {
            $name = $this->alias;
        }
        return $this->NodeType->field('id', ['name' => $name]);
    }

    public function find($type = 'first', $query = []) {
        $query = array_merge_recursive($query, ['conditions' => ["{$this->alias}.node_type_id" => $this->nodeTypeId()]]);
        if (!empty($query['contain'])) {
            $query['contain'] = $this->bindNodes($query['contain']);
        }
        return parent::find($type, $query);
    }

    // could be done better    
    public function bindNodes($contain) {
        $parsed = [];
        foreach($contain as $assoc => $deeperAssoc) {
            if (is_numeric($assoc)) {
                $assoc = $deeperAssoc;
                $deeperAssoc = [];
            }
            if (in_array($assoc, ['conditions', 'order', 'offset', 'limit', 'fields'])) {
                continue;
            }
            $parsed[$assoc] = array_merge_recursive($deeperAssoc, [
                'conditions' => [
                    "{$assoc}.node_type_id" => $this->nodeTypeId($assoc),
                ],
            ]);
            $this->bindNode($assoc);
            if (!empty($deeperAssoc)) {
                $parsed[$assoc] = array_merge($parsed[$assoc], $this->{$assoc}->bindNodes($deeperAssoc));
                foreach($parsed[$assoc] as $k => $v) {
                    if (is_numeric($k)) {
                        unset($parsed[$assoc][$k]);
                    }
                }
            }
        }
        return $parsed;
    }

    public function bindNode($alias) {
        $models = [$this->alias, $alias];
        sort($models);
        $this->bindModel(array(
            'hasAndBelongsToMany' => array(
                $alias => array(
                    'className' => 'Node',
                    'foreignKey' => ($models[0] === $this->alias) ? 'foreignKey' : 'associationForeignKey',
                    'associationForeignKey' => ($models[0] === $alias) ? 'foreignKey' : 'associationForeignKey',
                    'joinTable' => 'node_associations',
                )
            )
        ), false);
    }

}
Run Code Online (Sandbox Code Playgroud)

例子

$results = $this->Node->findNodes('all', [
    'node' => 'TvStation', // the top-level node to fetch
    'contain' => [         // all child associated nodes to fetch
        'TvShow' => [
            'Actor',
        ]
    ],
]);
Run Code Online (Sandbox Code Playgroud)