最近开发二手房项目,里面用到模型,然后数据库是沿用之前的,框架转为thinkphp,之前一直自己设计数据库,所以模型可以做的和官方一样,但是这次因为数据库不便于改动,所以用HAS_ONE的时候,发现问题了。
我用的是3.1.3版本,
官方手册给出的HAS_ONE模型支持的关联属性是这样的:
mapping_type | 关联类型,这个在HAS_ONE 关联里面必须使用HAS_ONE 常量定义。 |
class_name | 要关联的模型类名 例如,class_name 定义为Profile的话则表示和另外的Profile模型类关联,这个Profile模型类是无需定义的,系统会自动定位到相关的数据表进行关联。 |
mapping_name | 关联的映射名称,用于获取数据用 该名称不要和当前模型的字段有重复,否则会导致关联数据获取的冲突。如果mapping_name没有定义的话,会取class_name的定义作为mapping_name。如果class_name也没有定义,则以数组的索引作为mapping_name。 |
foreign_key | 关联的外键名称 外键的默认规则是当前数据对象名称_id,例如: UserModel对应的可能是表think_user (注意:think只是一个表前缀,可以随意配置) 那么think_user表的外键默认为 user_id,如果不是,就必须在定义关联的时候显式定义 foreign_key 。 |
condition | 关联条件 关联查询的时候会自动带上外键的值,如果有额外的查询条件,可以通过定义关联的condition属性。 |
mapping_fields | 关联要查询的字段 默认情况下,关联查询的关联数据是关联表的全部字段,如果只是需要查询个别字段,可以定义关联的mapping_fields属性。 |
as_fields | 直接把关联的字段值映射成数据对象中的某个字段 这个特性是ONE_TO_ONE 关联特有的,可以直接把关联数据映射到数据对象中,而不是作为一个关联数据。当关联数据的字段名和当前数据对象的字段名称有冲突时,还可以使用映射定义。 |
Community(小区表)和Region(地区表)
小区表里的字段region和地区表里的rid是关联的
小区表
地区表
按照官方文档,写出来的模型是这样的
<?php
class CommunityModel extends RelationModel
{
protected $_link = array(
'region'=>array(
'mapping_type'=>HAS_ONE,
'class_name'=>'region',
'mapping_name'=>'region',
'foreign_key' =>'rid',
),
);
}
然后action那边是
$id = $_GET['cid'];
$Community = D('Community');
$where['cid'] = $id;
$list = $Community->relation('region')->where($where)->select();
打印出来的结果是
array(1) { [0] => array(27) { ["cid"] => string(18) "201201121788888888" ["username"] => string(14) "abc" ["name"] => string(18) "城市花园" ["region"] => NULL很明显关联没成功,我又回去查看官方手册,官方是这么说道
foreign_key:关联的外键名称
外键的默认规则是当前数据对象名称_id,例如:
UserModel对应的可能是表think_user (注意:think只是一个表前缀,可以随意配置)
那么think_user表的外键默认为 user_id,如果不是,就必须在定义关联的时候显式定义 foreign_key 。
我foreign_key没定义错,是rid,但是我需要关联的是小区表里的region=地区表里的rid呀,
怎么办,这个时候开源的优势就出来了,直接查看tp的源代码,
打开thinkphp/Extend/Model/RelationModel.php
找到第130行getRelation方法
今天是研究HAS_ONE 关键是看HAS_ONE
第149-156行代码
case HAS_ONE:
$pk = $result[$mappingKey];
$mappingCondition .= " AND {$mappingFk}='{$pk}'";
$relationData = $model->where($mappingCondition)->field($mappingFields)->find();
if (!empty($val['relation_deep'])){
$model->getRelation($relationData,$val['relation_deep']);
}
break;
关键是151行
$mappingCondition .= " AND {$mappingFk}='{$pk}'";
这里就是关联的条件了,平时我们left join table on A=B
就是这里啦,然后再往上找$mappingFk和$pk变量
第135行-145行代码中有
$mappingType = !empty($val['mapping_type'])?$val['mapping_type']:$val; // 关联类型
$mappingClass = !empty($val['class_name'])?$val['class_name']:$key; // 关联类名
$mappingFields = !empty($val['mapping_fields'])?$val['mapping_fields']:'*'; // 映射字段
$mappingCondition = !empty($val['condition'])?$val['condition']:'1=1'; // 关联条件
$mappingKey =!empty($val['mapping_key'])? $val['mapping_key'] : $this->getPk(); // 关联键名
if(strtoupper($mappingClass)==strtoupper($this->name)) {
// 自引用关联 获取父键名
$mappingFk = !empty($val['parent_key'])? $val['parent_key'] : 'parent_id';
}else{
$mappingFk = !empty($val['foreign_key'])?$val['foreign_key']:strtolower($this->name).'_id'; // 关联外键
}
下面是查找$mappingFk经过
151行$mappingFk
144行
$mappingFk = !empty($val['foreign_key'])?$val['foreign_key']:strtolower($this->name).'_id'; // 关联外键
下面是查找$pk经过
151行$pk
150行$pk = $result[$mappingKey];
139行 $mappingKey =!empty($val['mapping_key'])? $val['mapping_key'] : $this->getPk(); // 关联键名
重点139行,mapping_key是否不是为空,不是空则 $val['mapping_key'],是空就$this->getPk()
$this->getPk();是获取主键的方法,我们定义模型的时候,官方文档没告诉我们定义mapping_key,所以tp默认找主键,所以我们关联的时候,如果两个表不是主键关联,那么将无法关联
下面是解决方案
<?php
class CommunityModel extends RelationModel
{
protected $_link = array(
'region'=>array(//关联的表名
'mapping_type'=>HAS_ONE, //关联类型
'class_name'=>'region', //需要关联的模型类名
'mapping_key' =>'region', //Community中关联的字段名
'mapping_name'=>'region', //关联的映射名称,用于获取数据用
'foreign_key' =>'rid', //region中关联的字段名
),
);
}
其实就是加了
'mapping_key' =>'region', //Community中关联的字段名
这样就可以了,tp手册中没告诉我们,可能是因为怕我们使用的时候没加索引,影响效率吧,
还有,一般我是HAS_MANY的时候才用tp的关联模型,因为HAS_ONE还不如直接left join效率高,下回再分析,如有不对,希望大家指正。