I-F中通过dar来管理模型和数据表之间的关系,dar模型就是一组类(Class),每个类对应数据库中的一个表,比如我们的一个表tbl_log的DDL为:
CREATE TABLE `tbl_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL,
`content` varchar(3000) NOT NULL,
`category_id` int(11) unsigned NOT NULL,
`submit_date` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
对应的TLog模型如下:
<?php
/**
* TLog
*
* generated by IF-cli at 2008-01-03 19:40:56
*/
class TLog extends IDarDomain {
public static $MODEL = array(
"tblName" => "tbl_log",
"idName" => "id",
"fields" => array("id", "title", "content", "category_id", "submit_date")
);
/**
* get domain object manager
*
* @return IDarManager
*/
public static function manager() {
return parent::manager(__CLASS__);
}
}
?>
可以看到类的注释中有“generated by IF-cli at 2008-01-03 19:40:56”,表示该类是使用IF-cli生成的,这个功能具体可以看I-F的[命令行工具]一章。
从0.2.1开始,无需用I-F cli生成,构造一个只有$MODEL属性的空类即可:
<?php
class TLog extends IDarDomain {
public static $MODEL = array(
"tblName" => "tbl_log"
);
}
?>
I-F会自动根据表名称动态取得表的字段,如果连$MODEL都未定义,则认为表名和模型名一致。比如对于tbl_log表的模型可以写为:
<?php
class TblLog extends IDarDomain {
}
?>
dar背后实现相当复杂而严谨,有驱动工厂、驱动、模型工厂、模型、表、字段、字段校验器、域对象、查询、等概念,但是用户接口(API)相当简单,体现了WEB 2.0时代的技术发展思想。
所有的模型类皆继承自IDarDomain。类中的$MODEL就是当前模型的设置,参数解释如下:
tblName:模型对应的数据库表名称,当前模型对应的表是tbl_log
idName:主键ID
fields:表中的字段,具体请参见下面的模型字段小节
mapping:表之间关系映射
nameRule:字段命名规则,可选java和C任意一个,这在自动生成模型时有效
模型的字段分为两种:
实字段 - 和数据表字段一一对应
虚字段 - 不参与查询,值不来自数据库
字段有以下属性:
name (string) 字段名
type (string) 字段类型,如char、varchar
column (string) 列名
maxLength (integer) 最大长度
notNull (boolean) 是否不为NULL,默认为false
primaryKey (boolean) 是否为主键,默认为false
autoIncrement (boolean) 是否为自增的,默认为false
binary (boolean) 是否为二进制的,默认为false
unsigned (boolean) 是否为无符号的,默认为false
hasDefault (boolean) 是否有默认值,默认为false
defaultValue (mixed) 默认值
virtual (boolean) 是否为虚字段,默认为false
expression (string) 表达式
目前dar中实际用到的有name,column,maxLength,notNull,unsigned,hasDefault,defaultValue,virtual,primaryKey等九个属性,而必须要设置的只有name和column二选一。其中name为dar模型中所用的字段名,而column则是数据表中定义的列名。一旦定义完成,我们就不必再关心column,而是全面面向name编程。
模型的字段完整的定义方式为
array(
"name" => "title",
"column" => "title",
"maxLength" => 100,
"notNull" => false,
"primaryKey" => false,
"virtual" => false
);
因为只有name或column才是必填的,所以上面可以简化为:
array(
"name" => "title",
);
然后dar就可以根据name字段判断column的值,规则是:将name命名方式(Java命名方式,除了第一个外,其余每个单词的开头大写)改为C语言的命名方式(每个单词以小写字母开头,并且用下划线_隔开),所以我们TLog例子中实际定义的字段和列名对应如下:
name column
------------------------
id id
title title
content content
category_id category_id
submit_date submit_date
而我们在TLog模型的例子中,使用了更简洁的方式,直接写入一个名称,dar会自动认出这种简写方式,并能运作的很好。
我们说模型是一个类,那么它就可以实例化,其实它的每个实例对应数据表的一条记录,对该实例的任何操作,都会被隐式应用到数据表。模型访问字段的方法很简单:
$log = TLog::manager()->find(1);
$log->title; //title字段值
$log->categoryId; //category_id字段值
更多实例请下载我们网站上提供的demo。
我们刚才说虚字段的值不是来自数据库,那么它的值来自哪里?使用它有什么意义吗?
像我们TLog的例子中,我们发现字段里定义的是categoryId而不是一个category,假设我们能直接取出category不是更好吗?所以我们在fields中先定义一个category字段,然后再解决取值问题。这时候的$MODEL就变成
public static $MODEL = array(
"tblName" => "tbl_log",
"idName" => "id",
"fields" => array("id", "title", "content", "categoryId", "submitDate", "category" => array("virtual" => true))
);
现在尝试使用$log->category,得到的结果为NULL。目前有两个方式让category有值:
设定和其他模型之间的关系,如何定义参见下面的[模型间关联]一节。
在模型中,定义和其对应的getter方法:
class TLog extends IDarDomain {
public static $MODEL = array(
"tblName" => "tbl_log",
"idName" => "id",
"fields" => array("id", "title", "content", "categoryId", "submitDate", "category" => array("virtual" => true))
);
//category
public function getCategory() {
return TCategory::manager()->find($this->categoryId);
}
......省略了其他代码
}
代码中的TCategory是另外一个对日志的分类进行封装的模型,这时候再调用$log->category时,将激活getCategory方法,并返回执行后的结果。
虚字段使我们的模型更加具有实际意义和使用价值。
在I-F v0.2中域对象字段增加了expression属性,比如:
"random" => array("virtual" => true, "expression" => "RAND()")
这样在random出现的地方,和RAND()效果是一样的:
$this->logs = TLog::manager()->findAll(array(
"order" => array("TLog", "random")
));
就可以将取出来的数组打乱,而普通的方法显然没有如此简洁。
可以在模型中拦截字段值的获取(get)和设定(set),原理是如果模型中无此字段作为属性或者属性为私有(private)的,则dar会调用相应的getter和setter方法。
同样在TLog中,我们加入一个私有(private)属性$title,然后使用自定义的方法获取和设定它的值:
private $title;
public function setTitle($title) {
echo "setTitle: 被调用<br/>\n";
$this->title = $title;
}
public function getTitle() {
echo "getTitle: 被调用<br/>\n";
return $this->title;
}
现在再使用
$log = TLog::manager()->find(5);
echo $log->title;
将会打印出
setTitle: 被调用
getTitle: 被调用
新的日志
这表明在给$title设置值之前调用了setTitle方法,并传入新的值;在获取$title的值时调用了getTitle方法。
可以利用这个特性,对输入的字段值进行校验和过滤:
public function setTitle($title) {
if (strlen($title) < 20) {
throw new IDarException("title too short");
}
$this->title = $title;
}
当$title长度小于20的时候,就会抛出IDarException异常。
dar支持模型间的三个关系:一对一,一对多,多对一。对于多对多的情况,可以采用一对多和多对一的关系的组合进行表达。
关联的设置形式如下:
"category" => array(
"关联方式" => array(
"参数1" => "参数值1",
"参数2" => "参数值2"
//其它参数 ...
)
)
目前有以下几个参数:
type (string) 关联类型,可以有belongsTo,hasOne,hasMany三种,默认为belongsTo
model (string) 关联的模型名
field (string) 关联的模型中的字段
mappedBy (string) 当前模型与之关联的字段
optional (boolean) 关联关系是否是可选的,如果是非可选的,那么每次查询的时候,都会检查两个模型的关联关系
fetchType (string) 数据获取方式,只能为lazy或者eager,当前版本中尚未使用该属性
设置关联字段后,该字段自动为虚字段。
日志对于日志分类来说,一般都是多对一的,多篇日志属于(或者说归类于)某个分类,所以对于TLog中的category可以改成:
"fields" => array("id", "title", "content", "categoryId", "submitDate",
"category" => array("belongsTo" => array(
"mappedBy" => "categoryId",
"model" => "TCategory",
"field" => "id"
)
))
这种情况的一个实例是用户(User)和用户资料(Profile)之间的关系:
"fields" => array(
"profile" => array("belongsTo" => array(
"mappedBy" => "id",
"model" => "Profile",
"field" => "userId"
)
))