7

赏金:

+500 代表赏金到一个好的解决方案。我已经把头撞在墙上两周了,我已经准备好寻求帮助了。

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

  • 节点
    • ID
    • 姓名
    • node_type_id
  • 节点关联
    • ID
    • node_id
    • other_node_id
  • 节点类型
    • 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'
    )
);

起初,我以为我做到了——这是有道理的。但后来我开始尝试检索数据,并且在过去的两周里一直在用头撞墙。

示例问题:

可以说我有以下节点:

  • 美国全国广播公司
  • 急诊室
  • 乔治·克鲁尼
  • 安东尼·爱德华兹
  • 今晚秀:雷诺
  • 杰·雷诺
  • 狐狸
  • 居家男人

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

$this->TvStation->find('all', array(
    'contain' => array(
        'TvShow' => array(
            'Actor'
        )
    )
));

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

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

4

4 回答 4

2

理念

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

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

我们查询的每个表都添加了一个额外的条件,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);
    }

}

例子

$results = $this->Node->findNodes('all', [
    'node' => 'TvStation', // the top-level node to fetch
    'contain' => [         // all child associated nodes to fetch
        'TvShow' => [
            'Actor',
        ]
    ],
]);
于 2012-09-01T06:19:34.273 回答
1

我认为不幸的部分问题是您希望您的解决方案在代码中包含用户数据。由于您的所有节点类型都是用户数据,因此您要避免尝试将它们用作应用程序中的类方法,因为它们可能是无限的。相反,我会尝试创建对您想要的数据操作进行建模的方法。

我在提供的数据模型中看到的一个遗漏是一种记录类型之间关系的方法。在您的示例中,您提到了 TvStation -> TvShows -> Actor 等之间的关系。但是这些数据关系在哪里定义/存储?由于您的所有节点类型都是用户定义的数据,我认为您希望/需要在某处记录存储这些关系。似乎 node_types 需要一些关于给定类型的有效或所需子类型是什么的额外元数据。将此记录在某处可能会使您在创建查询时的情况更简单一些。考虑您要询问数据库的所有问题或查询可能会有所帮助。如果您无法使用数据库中的数据回答所有这些问题,那么您可能缺少一些表。模型关联只是表中已经存在的数据关系的代理。如果存在差距,则您的数据模型中可能存在差距。

我认为这不是您正在寻找的答案,但希望它可以帮助您找到正确的答案。

于 2012-09-01T23:17:02.470 回答
1

我认为您的模型之间的关系不正确。我想这已经足够了:

// Node Model
public $hasAdBelongsToMany = array(
    'AssociatedNode' => array(
        'className' => 'Node',
        'foreignKey' => 'node_id'
        'associationForeignKey' => 'associated_node_id',
        'joinTable' => 'nodes_nodes'
    )
);

// 表格

节点

  • ID
  • 姓名
  • node_type_id

节点节点

  • ID
  • node_id
  • 关联节点id

节点类型

  • ID
  • 姓名

然后您可以尝试使用ContainableBehavior来获取您的数据。例如,要查找属于 TVStation 的所有 TVShow:

$options = array(
    'contain' => array(
        'AssociatedNode' => array(
            'conditions' => array(
                'AssociatedNode.node_type_id' => $id_of_tvshows_type
            )
        )
    ),
    conditions => array(
        'node_type_id' => $id_of_tvstations_type
    )
);
$nodes = $this->Node->find('all', $options);

编辑 :

您甚至可以有二级条件(参见本节最后一个示例,查看“标记”模型条件)。尝试这个:

$options = array(
    'contain' => array(
        'AssociatedNode' => array(
            'conditions' => array(
                'AssociatedNode.node_type_id' => $id_of_tvshows_type
            ),
            'AssociatedNode' => array(
                'conditions' => array( 'AssociatedNode.type_id' => $id_of_actors_type)
            )
        )
    ),
    conditions => array(
        'node_type_id' => $id_of_tvstations_type
    )
);
$nodes = $this->Node->find('all', $options);
于 2012-09-01T18:57:17.000 回答
-1

为什么不在节点模型中创建一个方法?

就像是:

    <?php 
        // first argument is a nested array filled with  integers 
(corresponding to node_type_id)
        //second one id of a node
    //third one corresponds to the data you want(empty at beginning in most case)
    public function custom_find($conditions,$id,&$array){

        //there may several type of nodes wanted: for instances actors and director of a serie, so we loop
        foreach($conditions as $key_condition=>$condition){

            //test to know if we have reached the 'bottom' of the nested array: if yes it will be an integer '2', if no it will be an array like '2'=>array(...)
            if(is_array($condition))){
                   //this is the case where there is deeper levels remaining

                        //a find request: we ask for the node defined by its id,
 //and the child nodes constrained by their type: ex: all actors of "Breaking Bad"
                        $this->id=$id;
                $result=$this->find('all',array(
                        'contain' => array(
                                'OtherNode' => array(
                                        'conditions'=>array('node_type_id'=>$key_condition)
                                )
                        )
                )
             );

                //we add to $array the nodes found. Ex: we add all the actors of the serie, with type_id as key
                        $array[$key_condition]=$result['OtherNode'];

                         //Then  on each node we just defined we call the function recursively. Note it's $condition not $conditions
                foreach($array[$key_condition] as &$value){
                    $this->custom_find($condition,$value['Node']['id'],$value);
                }

            }else{
                //if we simply add data
                        $this->id=$id;
                $result=$this->find('all',array(
                        'contain' => array(
                                'OtherNode' => array(
                                        'conditions'=>array('node_type_id'=>$value)
                                )
                        )
                )
             );

             $array[$condition]=$result['OtherNode'];
            }

        }



    }

该代码几乎肯定是错误的,只是为了让您了解我的意思。

编辑:

它能做什么:

它是一个递归函数,它接受嵌套的条件数组和节点的 id,并返回嵌套的节点数组。

例如: $conditions=array('2','4'=>array('5','6'=>array('4')))

这个怎么运作:

对于单个节点,它返回与数组中的条件相对应的所有子节点:然后它对条件更深一层的子节点执行相同的操作,直到没有更多的级别。

于 2012-09-01T18:02:49.677 回答