转自:http://www.cnblogs.com/siliconvalley/p/3259608.htmlhtml
虽 然Yii DAO能够处理事实上任何数据库相关的任务,但极可能咱们会花费90%的时间用来编写一些通用的SQL语句来执行CRUD操做(建立,读取,更新和删除)。 同时咱们也很难维护这些PHP和SQL语句混合的代码,要解决这些问题,咱们可使用Active Record。
Active Record(AR)是一种流行的对象关系映射(ORM)技术。每一个AR类表明一个数据表(或视图),其字段做为AR类的属性,一个AR实例表明在表中的 一行。常见的CRUD操做被做为AR类的方法执行。 因而,咱们可使用更面向对象的方法处理咱们的数据。例如,咱们可使用下面的代码在
tbl_post
表中插入一个新行:
$post=new Post;
$post->title='sample post';
$post->content='post body content';
$post->save();在下面咱们将介绍如何设置AR和用它来执行CRUD操做。在下一小节咱们将展现如何使用AR 处理数据库中的关系。为了简单起见,咱们使用本节下面的数据库表做为例子。 请注意,若是你使用MySQL数据库,在下面的SQL中您应该替换
AUTOINCREMENT
为
AUTO_INCREMENT
。
CREATE TABLE tbl_post (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
title VARCHAR(128) NOT NULL,
content TEXT NOT NULL,
create_time INTEGER NOT NULL
);注意: AR不是要解决全部与数据库相关的任务。它最好用于在PHP结构中模型化数据表和执行不复杂的 SQL 语句。 而 Yii DAO 应该用于复杂的状况下。
创建数据库链接
AR须要一个数据库链接以执行数据库相关的操做。默认状况下,应用中的db组件提供了 CDbConnection实例做为咱们须要的数据库链接。mysql
下面的应用程序配置提供了一个例子:
return array(
'components'=>array(
'db'=>array(
'class'=>'system.db.CDbConnection',
'connectionString'=>'sqlite:path/to/dbfile',
// turn on schema caching to improve performance
// 'schemaCachingDuration'=>3600,
),
),
);sql
提示: 因为Active Record须要表的元数据来肯定数据表的字段信息, 这须要时间来读取和分析元数据。若是您的数据库结构是比较固定的,你应该打开缓存。 打开方法是配置CDbConnection::schemaCachingDuration属 性为一个大于0的值。数据库
附mysql链接方式数组
'db'=>array(//---------------------------------------------------------------DB CONNECT 'connectionString'=>'mysql:host='.DB_HOST.';dbname='.DB_NAME.";port=".DB_PORT.";unix_socket=".DB_SOCK, 'username'=>DB_USERNAME, 'password'=>DB_PASSWORD, 'charset' =>'utf8', ),
AR的支持受限于数据库管理系统。目前,只有如下数据库管理系统支持:
MySQL 4.1 或之后版本
PostgreSQL 7.3 或之后版本
SQLite 2 和 3
Microsoft SQL Server 2000 或之后版本
Oracle
注意: Microsoft SQL Server自1.0.4版本提供支持;而对 Oracle 自1.0.5版本即提供支持。
若是你想使用其余组件而不是
db,或者你使用 AR 访问多个数据库,你应该重写CActiveRecord::getDbConnection()。 CActiveRecord类 是全部AR类的基类。
提示: 有两种方法能够在AR模式下使用多种数据库系统。若是数据库的模式不一样, 您能够以不一样的getDbConnection()来 建立不一样的 AR 类。不然,动态改变静态变量CActiveRecord::db 是一个更好的主意。
定义 AR 类
为了使用一个数据表,咱们首先须要扩展CActiveRecord来 定义一个AR类。 每一个AR类表明一个数据库表,每一个AR实例表明数据表中的一行。下面的代码介绍了 要建立一个对应
tbl_post
表的AR类所须要的最少的代码。
class Post extends CActiveRecord
{
public static function model($className=__CLASS__)
{
return parent::model($className);
}
public function tableName()
{
return ’tbl_post’;
}
}缓存
提示: 由于AR类在不少地方被引用,咱们能够导入包含AR类的整个目录,而不是逐个引入它们。例如,若咱们全部的AR类文件位于 protected/models,咱们能够以下配置:
return array(
’import’=>array(
’application.models.*’,
),
);
默认的, AR 类的名字和数据表的名字相同。 若它们不一样须要重写 tableName()方法。 方法 model() is declared as such for every AR class (to be explained shortly)。
信息: 要使用版本 1.1.0 引入的表前缀特征, AR 的方法 tableName() 能够被以下重写,
public function tableName()
{
return ’{{post}}’;
}这样,不是返回一个完整的表名, 咱们返回去掉了前缀的表名,并把它环绕在双弯曲括号中。
一条数据的字段能够做为相应 AR 实例的属性被访问。 例如, 下面的代码设置了 title 字段(属性):
$post=new Post;
$post->title=’a sample post’;虽然咱们没有在 Post 类中明确声明 title 属性, 咱们仍然能够在上面的代码中访问它。 这是由于 title 是表 tbl_post 中的字段, 在 PHP__get() 魔术方法的帮助下,CActiveRecord 能够将其做为一个属性来访问。 若以一样方式尝试访问不存在的字段,一个异常将被抛出。
信息:在此指南中,咱们为全部的数据表和字段采起小写格式。这是由于在不一样的DBMS中,对于大小写的敏感是不 同的。例如,PostgreSQL默认对字段名字是大小写不敏感的, and we must quote a column in a query condition if the column contains mixed-case letters.使用小写格式能够避免此问题。
AR 依赖于数据表良好定义的主键。 若一个表没有一个主键, 须要相应的 AR 类指定哪些字段应当为主键,经过重写
primaryKey()
方法,
public function primaryKey()
{
return ’id’;
// For composite primary key, return an array like the following
// return array(’pk1’, ’pk2’);
}建立记录
要插入新的一行记录到数据表中, 咱们建立一个新的对应的 AR 类实例, 设置和字段对应的属性的值, 并调用 save() 方法来完成插入。
$post=new Post;
$post->title=’sample post’;
$post->content=’content for the sample post’;
$post->create time=time();
$post->save();若表的主键是自增的, 在插入后 AR 实例将包含一个更新后的主键。 在上面的例子中, 属性id 将映射为新插入的主键值, 即便咱们没有明确更改它。
若在表模式中,一个字段被定义为一些静态默认值(static default value) (例如一个字符串, 一个数字), 在这个AR实例被建立后, 实例中相应的属性将自动有相应的默认值。 改变此默认值的一个方式是在 AR 类中明确声明此属性:
class Post extends CActiveRecord
{
public $title=’please enter a title’;
……
}
$post=new Post;
echo $post->title; // this would display: please enter a title从版本 1.0.2 开始, 在记录被保存前(插入或更新)一个属性能够赋值为 CDbExpression 类型的值。 例如, 为了保存由 MySQL NOW() 函数返回的时间戳, 咱们可使用下面的代码:
$post=new Post;
$post->createtime=new CDbExpression(’NOW()’);
// $post->create time=’NOW()’; will not work because
// ’NOW()’ will be treated as a string
$post->save();app
提示:AR 容许咱们执行数据库操做而无需编写麻烦的 SQL 语句, 咱们经常想要知道什么 SQL 语句被 AR 在下面执行了。 这能够经过打开 Yii 的记录(logging)特征来实现。 例如, 咱们能够在应用配置中打开 CWebLogRoute , 咱们将看到被执行的 SQL 语句被显示在每一个页面的底部。 自版本 1.0.5 开始, 咱们能够在应用配置设置 CDbConnection::enableParamLogging 为 true 以便 绑定到 SQL 语句的参数值也被记录。
读取记录
要读取数据表中的数据,咱们能够调用下面其中一个
find
方法:
// find the first row satisfying the specified condition
$post=Post::model()->find($condition,$params);
// find the row with the specified primary key
$post=Post::model()->findByPk($postID,$condition,$params);
// find the row with the specified attribute values
$post=Post::model()->findByAttributes($attributes,$condition,$params);
// find the first row using the specified SQL statement
$post=Post::model()->findBySql($sql,$params);在上面, 咱们使用
Post::model()
调用
find
方法。 记得静态方法
model()
是每一个 AR 类所必需的。 此方法返回一个 AR 实例,此实例 被用来访问类水平的方法(相似于静态的类方法)。
若
find
方法找到一行记录知足查询条件, 它将返回一个 Post 实例,此实例的属性包含表记录对应的字段值。 而后咱们能够读取被载入的值如同咱们访问普通对象的属性, 例如,
echo $post->title;
.
find
方法将返回 null 若在数据库中没有找到知足条件的记录。
当调用 find,咱们使用$condition 和 $params 来指定查询条件。 这里 $condition 能够是字符串表明一个 SQL 语句中的 WHERE 子语句,$params 是一个参数数组,其中的值应被绑定到$condition的占位符。例如,
// find the row with postID=10
$post=Post::model()->find(’postID=:postID’, array(’:postID’=>10));注意: 在上面的例子中, 对于某些 DBMS 咱们可能须要转义对 postID 字段的引用。 例如, 若咱们使用 PostgreSQL, 咱们须要写condition 为 “postID”=:postID ,由于 PostgreSQL 默认状况下对待字段名字为大小写不敏感的。
咱们也可使用 $condition 来指定更复杂的查询条件。 不使用字符串,咱们让$condition框架
为一个 CDbCriteria 实例,可让咱们指定条件而不限于 WHERE 子语句。例如,socket
$criteria=new CDbCriteria;
$criteria->select=’title’; // only select the ’title’ column
$criteria->condition=’postID=:postID’;
$criteria->params=array(’:postID’=>10);
$post=Post::model()->find($criteria); // $params is not needed注意, 当使用 CDbCriteria 做为查询条件, 再也不须要参数
$params
由于它能够在 CDbCriteria 中被指定, 如上所示。
一个可选的 CDbCriteria 方式是传递一个数组到
find
方法。 数组的键和值分别对应于 criteria 的属性名字和值。上面的例子能够被以下重写,
$post=Post::model()->find(array(
’select’=>’title’,
’condition’=>’postID=:postID’,
’params’=>array(’:postID’=>10),
));信息: 当一个查询条件是关于匹配一些字段用指定的值, 咱们可使用 findByAttributes()。 咱们让参数$attributes为一个数组,数组的值由字段名字索引。 在一些框架中,此任务能够经过调用相似于
findByNameAndTitle 的方法来实现。 虽然这个方法看起来颇有吸引力, 但它经常引发混淆和冲突,例如字段名字的大小写敏感性问题。
当多行记录知足指定的查询条件, 咱们可使用下面的
findAll
方法将它们聚合在一块儿, 每一个都有它们本身的副本
find
方法。
// find all rows satisfying the specified condition
$posts=Post::model()->findAll($condition,$params);
// find all rows with the specified primary keys
$posts=Post::model()->findAllByPk($postIDs,$condition,$params);
// find all rows with the specified attribute values
$posts=Post::model()->findAllByAttributes($attributes,$condition,$params);
// find all rows using the specified SQL statement
$posts=Post::model()->findAllBySql($sql,$params);若没有符合条件的记录,
findAll
返回一个空数组。 不一样于
find
方法,find方法会返回 null.
除了上面所说的 find和findAll 方法, 为了方便, 下面的方法也可使用:
// get the number of rows satisfying the specified condition
$n=Post::model()->count($condition,$params);
// get the number of rows using the specified SQL statement
$n=Post::model()->countBySql($sql,$params);
// check if there is at least a row satisfying the specified condition
$exists=Post::model()->exists($condition,$params);更新记录
一个 AR 实例被字段值填充后, 咱们能够改变它们并保存回它们到数据表中。
$post=Post::model()->findByPk(10);
$post->title=’new post title’;
$post->save(); // save the change to database若咱们所见, 咱们使用相同的 save() 方法来执行插入和更新操做。 若一个 AR 实例被使用 new 操做符建立,调用 save() 将插入一行新记录到数据表中; 若此 AR 实例是一些find或findAll方法调用的结果, 调用 save() 将更新表中已存在的记录。 事实上, 咱们可使用 CActiveRecord::isNewRecord 来检查一个 AR 实例是不是新建的。
更新一行或多行表中的记录而不预先载入它们也是可能的。 AR 提供以下方便的类水平的(class-level)方法来实现它:
// update the rows matching the specified condition
Post::model()->updateAll($attributes,$condition,$params);
// update the rows matching the specified condition and primary key(s)
Post::model()->updateByPk($pk,$attributes,$condition,$params);
// update counter columns in the rows satisfying the specified conditions
Post::model()->updateCounters($counters,$condition,$params);在上面,
$attributes
是一个值由字段名索引的数组;
$counters
是一个增长值由字段名索引的数组;
$condition
和
$params
已在以前被描述。
删除记录
咱们也能够删除一行记录若一个 AR 实例已被此行记录填充。
$post=Post::model()->findByPk(10); // assuming there is a post whose ID is 10
$post->delete(); // delete the row from the database table注意, 在删除后, 此 AR 实例仍然未改变, 但相应的表记录已经不存在了。
下面类水平的(class-level)方法被用来删除记录,而无需预先载入它们:
// delete the rows matching the specified condition
Post::model()->deleteAll($condition,$params);
// delete the rows matching the specified condition and primary key(s)
Post::model()->deleteByPk($pk,$condition,$params);数据验证
当插入或更新一行记录, 咱们经常须要检查字段的值是否符合指定的规则。 若字段值来自用户时这一点特别重要。一般咱们永远不要信任用户提交的数据。
AR 自动执行数据验证在 save() 被调用时。验证基于在 AR 类中的 rules()方 法中指定的规则。如何指定验证规则的更多信息, 参考 声明验证规则 部分。 下面是保存一条记录典型的工做流程:
if($post->save())
{
// data is valid and is successfully inserted/updated
}
else
{
// data is invalid. call getErrors() to retrieve error messages
}当插入或更新的数据被用户在 HTML 表单中提交, 咱们须要赋值它们到对象的 AR 属性。 咱们能够这样作:
$post->title=$_POST[’title’];
$post->content=$_POST[’content’];
$post->save();如有不少字段, 咱们能够看到一个很长的赋值列表。 可使用下面的 attributes 属性来缓解。 更多细节能够在 Securing Attribute Assignments 部分和 Creating Action 部分找到。
// assume $_POST[’Post’] is an array of column values indexed by column names
$post->attributes=$_POST[’Post’];
$post->save();对比记录
相似于表记录, AR 实例由它们的主键值来被识别。 所以,要对比两个 AR 实例, 咱们只须要对比它们的主键值, 假设它们属于相同的 AR 类。 然而,一个更简单的方式是调用 CActiveRecord::equals()。
信息: 不一样于 AR 在其余框架的执行, Yii 在其 AR 中支持多个主键。 一个复合主键由两个或更多字段构成。 对应的, 主键值在 Yii 中表示为一个数组。 The
primaryKey
属性给出一个 AR 实例的主键值。
Customization
CActiveRecord 提供了一些占位符(placeholder)方法可被用来在子类中重写以自定义它的工做流程。
beforeValidate 和 afterValidate: 它们在验证执行 以前/后 被调用。
beforeSave 和 afterSave: 它们在保存一个 AR 实例以前/后 被调用。
beforeDelete 和 afterDelete: 它们在一个 AR 实例被删除 以前/后 被调用。
afterConstruct: 这将在每一个 AR 实例被使用 new 操做符建立以后被调用。
beforeFind: 它在一个 AR finder 被用来执行一个查询以前被调用 (例如 find(), findAll())。 从版本 1.0.9 可用。
afterFind: 它在每一个 AR 实例被建立做为一个查询结果后被调用。
在 AR 中使用事务处理
每一个 AR 实例包含一个名为 dbConnection 的属性,它是一个 CDbConnection 实例。 这样咱们在使用 AR 时就可使用 Yii DAO 提供的事务处理特征:
$model=Post::model();
$transaction=$model->dbConnection->beginTransaction();
try
{
// find and save are two steps which may be intervened by another request
// we therefore use a transaction to ensure consistency and integrity
$post=$model->findByPk(10);
$post->title=’new post title’;
$post->save();
$transaction->commit();
}
catch(Exception $e)
{
$transaction->rollBack();
}命名空间(new!)
注意: 对于命名空间的支持从版本 1.0.5 开始。 思路来源于 Ruby on Rails.
a named scope represents a named query criteria that can be combined with other named scopes and applied to an active record query.
命名空间被主要在 CActiveRecord::scopes() 方法中声明,格式是 name-criteria 对。 下面的代码在 Post 模型类中声明了两个命名空间,
published
和
recently
:
class Post extends CActiveRecord
{
……
public function scopes()
{
return array(
’published’=>array(
’condition’=>’status=1’,
),
’recently’=>array(
’order’=>’create time DESC’,
’limit’=>5,
),
);
}
}每一个命名空间被声明为一个被用来初始化一个 CDbCriteria 实例的数组。例如,命名空间
recently
指定 了
order
属性为
create_time DESC
,
limit
属性为 5, 翻译为一个查询条件就是应当返回最近发表的5篇帖子。
命名空间大多数做为find方法的 modi?er 来使用。几个命名空间能够链接在一块儿,则样能够获得一个更加有限制性的查询结果集。 例如,要找到最近发表的帖子,咱们可使用下面的代码:
$posts=Post::model()->published()->recently()->findAll();一般命名空间必须出如今一个 find 方法的左边。 Each of them provides a query criteria, which is combined with other criterias, including the one passed to the find method call. The net e?ect is like adding a list of ?lters to a query.
从版本 1.0.6 开始, 命名空间也可使用 update 和 delete 方法。例如,下面的代码将删除 全部最近发表的帖子:
Post::model()->published()->recently()->delete();注意: 命名空间只能够被做为类水平的方法使用。也就是说,此方法必须使用 ClassName::model() 来调用它。
参数化命名空间(Parameterized Named Scopes)
命名空间能够被参数化。例如,咱们想要定制命名空间
recently
指定的帖子数目。 要这样作,不是在 CActiveRecord::scopes 方法中声明命名空间,咱们须要定义一个新的方法,它的名字和空间的名字相同:
public function recently($limit=5)
{
$this->getDbCriteria()->mergeWith(array(
’order’=>’create time DESC’,
’limit’=>$limit,
));
return $this;
}而后,咱们可使用下面的语句来检索 3 个最近发表的帖子:
$posts=Post::model()->published()->recently(3)->findAll();若咱们不使用上面的参数 3 ,默认状况下咱们将检索 5 个最近发表的内容。
默认命名空间
一个模型类能够有一个默认命名空间,它被应用于此模型全部的查询 (包括 relational ones)。例如,一个支持多种语言的网站只是以当前用户指定的语言来显示内容。 由于有不少关于站点内容的查询,咱们能够定义一个默认命名空间来解决这个问题。要这样作,咱们重写 CActiveRecord::defaultScope 方法以下,
class Content extends CActiveRecord
{
public function defaultScope()
{
return array(
’condition’=>"language=’".Yii::app()->language."’",
);
}
}如今,若调用下面的方法将自动使用上面定义的查询条件:
$contents=Content::model()->findAll();注意默认命名空间只应用于 SELECT 查询。它忽视 INSERT,UPDATE 和 DELETE 查询。
Popularity: unranked [?]ide