Yii数据访问对象(DAO)建立在PHP的数据对象(PDO)extension上,使得在一个单一的统一的接口可以访问不同的数据库管理系统(DBMS)。使用Yii的DAO开发的应用程序可以很容易地切换使用不同的数据库管理系统,而不需要修改数据访问代码。
Yii 的Active Record( AR ),实现了被广泛采用的对象关系映射(ORM)办法,进一步简化数据库编程。按照约定,一个类代表一个表,一个实例代表一行数据。Yii AR消除了大部分用于处理CRUD(创建,读取,更新和删除)数据操作的sql语句的重复任务。
建立数据库连接
$connection=new CDbConnection($dsn,$username,$password);
$connection->active=true;
......
$connection->active=false; // 关闭连接
常用的几个DNS格式:
SQLite: sqlite:/path/to/dbfile
MySQL: mysql:host=localhost;dbname=testdb
PostgreSQL: pgsql:host=localhost;port=5432;dbname=testdb
SQL Server: mssql:host=localhost;dbname=testdb
Oracle: oci:dbname=//localhost:1521/testdb
由于 CDbConnection 继承自 CApplicationComponent,我们也可以将其作为一个 应用组件 使用。所以我们可以直接在main.php里面修改db的配置。
然后我们就可以通过
Yii::app()->db
访问数据库连接了。它已经被自动激活了,除非我们特意配置了CDbConnection::autoConnect 为 false。通过这种方式,这个单独的DB连接就可以在我们代码中的很多地方共享。
执行SQL语句
数据库连接建立后,SQL 语句就可以通过使用 CDbCommand 执行了。你可以通过使用指定的SQL语句作为参数调用 CDbConnection::createCommand() 创建一个 CDbCommand 实例。
$connection=Yii::app()->db; // 假设你已经建立了一个 "db" 连接// 如果没有,你可能需要显式建立一个连接:
// $connection=new CDbConnection($dsn,$username,$password);
$command=$connection->createCommand($sql);
// 如果需要,此 SQL 语句可通过如下方式修改:// $command->text=$newSQL;
-
execute(): 执行一个无查询 (non-query)SQL语句, 例如
INSERT
,UPDATE
和DELETE
。如果成功,它将返回此执行所影响的行数。 -
query(): 执行一条会返回若干行数据的 SQL 语句,例如
SELECT
。 如果成功,它将返回一个 CDbDataReader 实例,通过此实例可以遍历数据的结果行。为简便起见, (Yii)还实现了一系列queryXXX()
方法以直接返回查询结果。
$dataReader=$command->query(); // 执行一个 SQL 查询
$rowCount=$command->execute(); // 执行无查询 SQL
$rows=$command->queryAll(); // 查询并返回结果中的所有行
$row=$command->queryRow(); // 查询并返回结果中的第一行
$column=$command->queryColumn(); // 查询并返回结果中的第一列
$value=$command->queryScalar(); // 查询并返回结果中第一行的第一个字段
获取查询结果
在
CDbCommand::query() 生成
CDbDataReader 实例之后,你可以通过重复调用
CDbDataReader::read() 获取结果中的行。你也可以在 PHP 的
foreach
语言结构中使用
CDbDataReader 一行行检索数据。
$dataReader=$command->query();
foreach($dataReader as $row) {
print_r($row)
}
//也 一次性提取所有行到一个数组
$rows=$dataReader->readAll();
使用事务
当一个应用要执行几条查询,每条查询要从数据库中读取并/或向数据库中写入信息时, 保证数据库没有留下几条查询而只执行了另外几条查询是非常重要的。 事务,在 Yii 中表现为
CDbTransaction
实例,可能会在下面的情况中启动:
$transaction=$connection->beginTransaction();
try{
$connection->createCommand($sql1)->execute();
$connection->createCommand($sql2)->execute();
//.... other SQL executions
$transaction->commit();
}catch(Exception $e) // 如果有一条查询失败,则会抛出异常{
$transaction->rollBack();
}
绑定参数避免SQL注入攻击
参数占位符可以是命名的 (表现为一个唯一的标记) 或未命名的 (表现为一个问号)。调用
CDbCommand::bindParam() 或
CDbCommand::bindValue() 以使用实际参数替换这些占位符。 这些参数不需要使用引号引起来:底层的数据库驱动会为你搞定这个。 参数绑定必须在 SQL 语句执行之前完成。
// 一条带有两个占位符 ":username" 和 ":email"的 SQL
$sql="INSERT INTO tbl_user (username, email) VALUES(:username,:email)";
$command=$connection->createCommand($sql);
// 用实际的用户名替换占位符 ":username"
$command->bindParam(":username",$username,PDO::PARAM_STR);
// 用实际的 Email 替换占位符 ":email"
$command->bindParam(":email",$email,PDO::PARAM_STR);
$command->execute();
// 使用新的参数集插入另一行
$command->bindParam(":username",$username2,PDO::PARAM_STR);
$command->bindParam(":email",$email2,PDO::PARAM_STR);
$command->execute();
注:
方法 bindParam() 和 bindValue() 非常相似。唯一的区别就是前者使用一个 PHP 变量绑定参数, 而后者使用一个值。对于那些内存中的大数据块参数,处于性能的考虑,应优先使用前者。
使用表前缀
要使用表前缀,配置
CDbConnection::tablePrefix 属性为所希望的表前缀。 然后,在 SQL 语句中使用
{{TableName}}
代表表的名字,其中的
TableName
是指不带前缀的表名。 例如,如果数据库含有一个名为
tbl_user
的表,而
tbl_
被配置为表前缀,那我们就可以使用如下代码执行用户相关的查询。
$sql='SELECT * FROM {{user}}';
$users=$connection->createCommand($sql)->queryAll();
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();
注:我们在配置数据库(main.php)的时候,
由于 Active Record 依靠表的元数据(metadata)测定列的信息,读取元数据并解析需要时间。 如果你数据库的表结构很少改动,你应该通过配置 CDbConnection::schemaCachingDuration 属性的值为一个大于零的值开启表结构缓存。
由于 AR 类经常在多处被引用,我们可以导入包含 AR 类的整个目录,而不是一个个导入。 例如,如果我们所有的 AR 类文件都在 protected/models 目录中,我们可以配置应用如下:
return array(
'import'=>array(
'application.models.*',
),
);
AR模型对应的类名与表名
默认情况下,AR 类的名字和数据表的名字相同。如果不同,请覆盖 tableName() 方法。
要使用 1.1.0 版本中引入的 表前缀功能 AR 类的 tableName() 方法可以通过如下方式覆盖:
public function tableName(){
return '{{post}}';
}
创建记录
$post=new Post;
$post->title='sample post';
$post->content='content for the sample post';
$post->create_time=time();
$post->save();
如果我们需要给某一个数据设置一个默认值,可以定义成此类的一个属性,例如:
class Post extends CActiveRecord{
public $title='please enter a title';
......
}
$post=new Post;
echo $post->title; // 这儿将显示: please enter a title
查找记录
// 查找满足指定条件的结果中的第一行 $post=Post::model()->find($condition,$params);
// 查找具有指定主键值的那一行 $post=Post::model()->findByPk($postID,$condition,$params);
// 查找具有指定属性值的行 $post=Post::model()->findByAttributes($attributes,$condition,$params);
// 通过指定的 SQL 语句查找结果中的第一行 $post=Post::model()->findBySql($sql,$params);
请记住,静态方法 model() 是每个 AR 类所必须的。 此方法返回在对象上下文中的一个用于访问类级别方法(类似于静态类方法的东西)的 AR 实例。
调用 find 时,我们使用 $condition 和 $params 指定查询条件。此处 $condition 可以是 SQL 语句中的 WHERE 字符串,$params 则是一个参数数组,其中的值应绑定到 $condation 中的占位符。例如:
$post=Post::model()->find('postID=:postID', array(':postID'=>10));
$post=Post::model()->find(array(
'select'=>'title',
'condition'=>'postID=:postID',
'params'=>array(':postID'=>10),
));
当有多行数据匹配指定的查询条件时,我们可以通过下面的 findAll 方法将他们全部带回。 每个都有其各自的 find 方法,就像我们已经讲过的那样。
// 查找满足指定条件的所有行 $posts=Post::model()->findAll($condition,$params);
// 查找带有指定主键的所有行 $posts=Post::model()->findAllByPk($postIDs,$condition,$params);
// 查找带有指定属性值的所有行 $posts=Post::model()->findAllByAttributes($attributes,$condition,$params);
// 通过指定的SQL语句查找所有行 $posts=Post::model()->findAllBySql($sql,$params);
除了上面讲述的 find 和 findAll 方法,为了方便,(Yii)还提供了如下方法,当然,他们可以使用数组的形式代替所需要的参数。
// 获取满足指定条件的行数 $n=Post::model()->count($condition,$params);
// 通过指定的 SQL 获取结果行数 $n=Post::model()->countBySql($sql,$params);
// 检查是否至少有一行复合指定的条件 $exists=Post::model()->exists($condition,$params);
更新记录
在 AR 实例填充了列的值之后,我们可以改变它们并把它们存回数据表。
$post=Post::model()->findByPk(10);
$post->title='new post title';
$post->save(); // 将更改保存到数据库
正如我们可以看到的,我们使用同样的
save()
方法执行插入和更新操作。 如果一个 AR 实例是使用
new
操作符创建的,调用
save()
将会向数据表中插入一行新数据; 如果 AR 实例是某个
find
或
findAll
方法的结果,调用
save()
将更新表中现有的行。 实际上,我们是使用
CActiveRecord::isNewRecord
说明一个 AR 实例是不是新的。
直接更新数据表中的一行或多行而不首先载入也是可行的。 AR 提供了如下方便的类级别方法实现此目的:
// 更新符合指定条件的行 Post::model()->updateAll($attributes,$condition,$params);
// 更新符合指定条件和主键的行 Post::model()->updateByPk($pk,$attributes,$condition,$params);
// 更新满足指定条件的行的计数列 Post::model()->updateCounters($counters,$condition,$params);
在上面的代码中, $attributes 是一个含有以 列名作索引的列值的数组; $counters 是一个由列名索引的可增加的值的数组;$condition 和 $params 在前面的段落中已有描述。如果以数组的形式,可以这样写:
Post::model()->updateAll(
array('title' => 'update_name'),
array(
'condition' => 'title=:title',
'params' => array(':title'=>'post1')
)
);
array('title' => 'update_name'),
array(
'condition' => 'title=:title',
'params' => array(':title'=>'post1')
)
);
删除记录
如果一个 AR 实例被一行数据填充,我们也可以删除此行数据。
$post=Post::model()->findByPk(10); // 假设有一个帖子,其 ID 为 10
$post->delete(); // 从数据表中删除此行
使用下面的类级别代码,可以无需首先加载行就可以删除它
// 删除符合指定条件的行 Post::model()->deleteAll($condition,$params);
// 删除符合指定条件和主键的行 Post::model()->deleteByPk($pk,$condition,$params);
数据验证
如果有很多列,我们可以看到一个用于这种复制的很长的列表。 这可以通过使用如下所示的 attributes 属性简化操作。
$post->attributes=$_POST['Post'];// 假设 $_POST['Post'] 是一个以列名索引列值为值的数组
$post->save();
自定义方法
CActiveRecord 提供了几个占位符方法,它们可以在子类中被覆盖以自定义其工作流。
- beforeValidate 和
-
beforeSave 和 afterSave: 这两个将在保存 AR 实例之前和之后被调用。
-
beforeDelete 和 afterDelete: 这两个将在一个 AR 实例被删除之前和之后被调用。
-
afterConstruct: 这个将在每个使用
new
操作符创建 AR 实例后被调用。 -
beforeFind: 这个将在一个 AR 查找器被用于执行查询(例如
find()
,findAll()
)之前被调用。 1.0.9 版本开始可用。 -
afterFind: 这个将在每个 AR 实例作为一个查询结果创建时被调用。
使用 AR 处理事务
$model=Post::model();
$transaction=$model->dbConnection->beginTransaction();
try{
// 查找和保存是可能由另一个请求干预的两个步骤
// 这样我们使用一个事务以确保其一致性和完整性
$post=$model->findByPk(10);
$post->title='new post title';
$post->save();
$transaction->commit();
}catch(Exception $e){
$transaction->rollBack();
}