自我“HABTM”或“HasMany Through”概念混淆
赏金:
+500代表一个好的解决方案。 现在我已经严肃地将我的头撞到了这面墙上,现在已经准备好了2周。
表格/模型(简化以显示关联)
大概的概念:
用户可以创建节点类型(例如“电视台”,“电视节目”和“演员”......任何东西)。 如果我事先知道节点类型是什么以及每个节点类型之间的关联,我只是为它们创建模型 - 但我希望这是非常开放的,因此用户可以创建他们想要的任何节点类型。 然后,(特定节点类型的)每个节点可以与任何其他节点类型的任何其他节点相关。
说明和我试过的东西:
每个节点应该能够与任何/每个其他节点相关。
我的假设是,要做到这一点,我必须有一个关联表 - 所以我做了一个名为“node_associations”的node_id
和other_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点,并包含它的演员(S)和它的站...等等
但是 - 通过HABTM或HasMany通过自我(更重要的是,还有未知的数据集),我不知道模型是哪个字段(node_id或other_node_id),总体而言,我无法将自己的头围绕在如何获得内容。
理念
让我们试着用约定来解决这个问题,node_id将是别名首先按字母顺序排列的模型,other_node_id将是第二个。
对于每个包含的模型,我们立即创建一个HABTM关联到Node类,为每个关联创建一个别名(请参阅bindNodes
和bindNode
方法)。
我们查询的每个表我们都会在node_type_id
上添加一个额外的条件,以仅返回该类型节点的结果。 NodeType的id通过getNodeTypeId()
来选择,并且应该被缓存。
要在深度相关的关联中使用条件过滤结果,您需要手动添加额外的联接,为每个联接表创建一个具有唯一别名的联接,然后将每个节点类型本身与别名结合以便能够应用条件(例如,选择所有具有Actor x的TvChannels)。 在Node类中为此创建一个辅助方法。
笔记
我为我的演示使用了foreignKey
作为node_id
和associationForeignKey
作为other_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',
]
],
]);
我认为你的模特之间有不正确的关系。 我想这将足以:
// Node Model
public $hasAdBelongsToMany = array(
'AssociatedNode' => array(
'className' => 'Node',
'foreignKey' => 'node_id'
'associationForeignKey' => 'associated_node_id',
'joinTable' => 'nodes_nodes'
)
);
//表格
节点
nodes_nodes
node_types
然后你可以尝试使用ContainableBehavior获取你的数据。 例如,要查找属于TVStation的所有TVShows:
$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);
不幸的是,我认为问题的一部分是你希望你的解决方案在代码中包含用户数据。 由于所有的节点类型都是用户数据,所以您希望避免尝试将它们用作应用程序中的类方法,因为它们可能是无限的。 相反,我会尝试创建模拟您想要的数据操作的方法。
我在提供的数据模型中看到的一处遗漏是记录类型之间关系的一种方法。 在你的例子中,你提到了TvStation - > TvShows - > Actor等之间的关系,但是这些数据关系在哪里定义/存储? 由于所有节点类型都是用户定义的数据,我认为您需要/需要将这些关系存储在某处。 看起来node_types需要一些关于给定类型的有效或期望子类型的附加元数据。 在创建查询时,将其记录在某处可能会使您的情况更加简单。 这可能有助于思考你要问数据库的所有问题或疑问。 如果您无法用数据库中的数据回答所有这些问题,那么您可能会丢失一些表格。 模型关联只是表中已经存在的数据关系的代理。 如果存在差距,那么数据模型中可能存在差距。
我不认为这是你正在寻找的答案,但希望它能帮助你找到合适的答案。
链接地址: http://www.djcxy.com/p/11131.html