YII中的AR与DAO

一、PDO

PDO其实是PHP Database Objects的缩写,中文即PHP数据库对象。它提供了一种统一的PHP与数据库交互的方法。

优势:使得在一个单一的统一的接口可以访问不同的数据库管理系统(DBMS)。

1.1、首先来看下PDO的基本用法:

连接是通过创建 PDO 基类的实例而建立的。不管使用哪种驱动程序,都是用 PDO 类名。构造函数接收用于指定数据库源(所谓的 DSN)以及可能还包括用户名和密码(如果有的话)的参数。

复制代码

<?php
try {
    $dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
    foreach($dbh->query('SELECT * from FOO') as $row) {
        print_r($row);
    }
    $dbh = null;
} catch (PDOException $e) {
    print "Error!: " . $e->getMessage() . "<br/>";
    die();
}
?>

复制代码

如果有任何连接错误,将抛出一个 PDOException 异常对象。如果想处理错误状态,可以捕获异常,或者选择留给通过 set_exception_handler() 设置的应用程序全局异常处理程序。

 连接数据成功后,返回一个 PDO 类的实例给脚本,此连接在 PDO 对象的生存周期中保持活动。要想关闭连接,需要销毁对象以确保所有剩余到它的引用都被删除,可以赋一个 NULL 值给对象变量。如果不明确地这么做,PHP 在脚本结束时会自动关闭连接。

Example #3 关闭一个连接

复制代码

<?php
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
// 在此使用连接


// 现在运行完成,在此关闭连接
$dbh = null;
?>

复制代码

很多 web 应用程序通过使用到数据库服务的持久连接获得好处。持久连接在脚本结束后不会被关闭,且被缓存,当另一个使用相同凭证的脚本连接请求时被重用。持久连接缓存可以避免每次脚本需要与数据库会话时建立一个新连接的开销,从而让 web 应用程序更快。

 Example #4 持久化连接

<?php
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass, array(
    PDO::ATTR_PERSISTENT => true
));
?>

Note:

如果想使用持久连接,必须在传递给 PDO 构造函数的驱动选项数组中设置 PDO::ATTR_PERSISTENT 。如果是在对象初始化之后用 PDO::setAttribute() 设置此属性,则驱动程序将不会使用持久连接。

Note:

如果使用 PDO ODBC 驱动且 ODBC 库支持 ODBC 连接池(有unixODBC 和 Windows 两种做法;可能会有更多),建议不要使用持久的 PDO 连接,而是把连接缓存留给 ODBC 连接池层处理。 ODBC 连接池在进程中与其他模块共享;如果要求 PDO 缓存连接,则此连接绝不会被返回到 ODBC 连接池,导致创建额外的连接来服务其他模块。

1.2、连接各个数据库的示例

下面看一下连接MySQL的代码:

$dsn='mysql:host='.$dbhost.';dbname='.$dbdatabase.';'
$dbh=new PDO($dsn,$username,$userpass);

如果是SQLite3,直接使用下面的代码:

$dsn='sqlite3:"C:\sqlite\user.db"';
$dbh=new PDO($dsn);

如果是PostgreSQL,下面的代码能够对付:

$dsn='pgsql:host='.$dbhost.' port=5432 dbname='.$dbdatabase.' user='.$username.' password='.$userpass;
$dbh=new PDO($dsn);

跟数据库成功建立连接之后,下面就只需要从数据库获取数据或插入更新数据,实例代码如下:

$stmt=$dbh->query('SELECT id,name FROM user');
$row=$stmt->fetch();

 二、数据访问对象 (DAO)

    Yii提供了强大的数据库编程支持。Yii数据访问对象(DAO)建立在PHP的数据对象(PDO)extension上,使得在一个单一的统一的接口可以访问不同的数据库管理系统(DBMS)。使得Yii的DAO开发的应用程序可以很容易地切换使用不同的数据库管理系统,而不需要修改数据访问代码,这在数据库移植时非常有用。它是一个为众多流行的DBMS 提供统一数据访问的扩展,这些DBMS 包括MySQL, PostgreSQL 等等。因此,要使用Yii DAO,PDO 扩展和特定的PDO 数据库驱动(例如PDO_MYSQL)必须安装。
Yii DAO 主要包含如下四个类:

  • CDbConnection: 代表一个数据库连接。
  • CDbCommand: 代表一条通过数据库执行的SQL 语句。
  • CDbDataReader: 代表一个只向前移动的,来自一个查询结果集中的行的流。
  • CDbTransaction: 代表一个数据库事务。
1、建立数据库连接

    要建立一个数据库连接,创建一个CDbConnection 实例并将其激活。连接到数据库需要一个数据源的名字(DSN)以指定连接信息。用户名和密码也可能会用到。当连接到数据库的过程中发生错误时(例如,错误的DSN 或无效的用户名/密码),将会抛出一个异常。

$connection=new CDbConnection($dsn,$username,$password);
// 建立连接。你可以使用try...catch 捕获可能抛出的异常
$connection->active=true;
......
$connection->active=false; // 关闭连接

DSN 的格式取决于所使用的PDO 数据库驱动。总体来说, DSN 要含有PDO 驱动的名字,跟上一个冒号,再跟上驱动特定的连接语法。可查阅PDO 文档获取更多信息。下面是一个常用DSN 格式的列表。

  • 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,我们也可以将其作为一个应用组件使用。我们可以在应用配置中如下配置 db (或其他名字) 应用组件,来实现此目的在config/main.php配置如下:

 

复制代码

array(
   ......
   'components'=>array(
      ......
      'db'=>array(
         'class'=>'CDbConnection',
         'connectionString'=>'mysql:host=localhost;dbname=testdb',
         'username'=>'root',
         'password'=>'password',
         'emulatePrepare'=>true, // needed by some MySQL installations
      ),
   ),
)

复制代码

然后我们就可以通过Yii::app()->db 访问数据库连接了。它已经被自动激活了, 除非我们特意配置了CDbConnection::autoConnect 为false。通过这种方式,这个单独的DB 连接就可以在我们代码中的很多地方共享。

2、执行 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;

一条SQL 语句会通过CDbCommand 以如下两种方式被执行:
 A、execute(): 执行一个无查询(non-query)SQL 语句,例如INSERT, UPDATE 和DELETE 。如果成功,它将返回此执行所影响的行数。
 B、query(): 执行一条会返回若干行数据的SQL 语句,例如SELECT。如果成功,它将返回一个CDbDataReader 实例,通过此实例可以遍历数据的结果行。为简便起见,(Yii)还实现了一系列queryXXX() 方法以直接返回查询结果。
执行SQL 语句时如果发生错误,将会抛出一个异常。

$rowCount=$command->execute(); // 执行无查询SQL
$dataReader=$command->query(); // 执行一个SQL 查询
$rows=$command->queryAll(); // 查询并返回结果中的所有行
$row=$command->queryRow(); // 查询并返回结果中的第一行
$column=$command->queryColumn(); // 查询并返回结果中的第一列
$value=$command->queryScalar(); // 查询并返回结果中第一行的第一个字段
3、获取查询结果

在CDbCommand::query() 生成CDbDataReader 实例之后,你可以通过重复调用CDbDataReader::read() 获取结果
中的行。你也可以在PHP 的foreach 语言结构中使用CDbDataReader 一行行检索数据。

复制代码

$dataReader=$command->query();
//print_r($dataReader);
// 重复调用read() 直到它返回false
while(($row=$dataReader->read())!==false) { ... }
// 使用foreach 遍历数据中的每一行
foreach($dataReader as $row) { ... }
// 一次性提取所有行到一个数组
$rows=$dataReader->readAll();
注意: 不同于query(), 所有的queryXXX()方法会直接返回数据。例如,queryRow()会返回代表查询结果第一行的一个数组。

复制代码

 

4、使用事务

事务,在Yii 中表现为CDbTransaction 实例,可能会在下面的情况中启动:

  •  开始事务.
  •  一个个执行查询。任何对数据库的更新对外界不可见。
  •  提交事务。如果事务成功,更新变为可见。
  •  如果查询中的一个失败,整个事务回滚。

上述工作流可以通过如下代码实现:

复制代码

$transaction=$connection->beginTransaction();
try
{
   $connection->createCommand($sql1)->execute();
   $connection->createCommand($sql2)->execute();
   //.... other SQL executions
   $transaction->commit();
}
catch(Exception $e) // 如果有一条查询失败,则会抛出异常
{
   $transaction->rollBack();
}

复制代码

5、绑定参数

要避免SQL 注入攻击并提高重复执行的SQL 语句的效率,你可以"准备(prepare)"一条含有可选参数占位符的
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 变量绑定参数,而后者使用一个值。
对于那些内存中的大数据块参数,处于性能的考虑,应优先使用前者。

6、绑定列

当获取查询结果时,你也可以使用PHP 变量绑定列。这样在每次获取查询结果中的一行时就会自动使用最新的值填充。
$sql="SELECT username, email FROM tbl_user";
$dataReader=$connection->createCommand($sql)->query();
// 使用$username 变量绑定第一列(username)
$dataReader->bindColumn(1,$username);
// 使用$email 变量绑定第二列(email)
$dataReader->bindColumn(2,$email);
while($dataReader->read()!==false)
{
// $username 和$email 含有当前行中的username 和email
}

7、使用表前缀

要使用表前缀,配置CDbConnection::tablePrefix 属性为所希望的表前缀。然后,在SQL 语句中使用{{TableName}}
代表表的名字,其中的TableName 是指不带前缀的表名。例如,如果数据库含有一个名为tbl_user 的表,而tbl_ 被
配置为表前缀,那我们就可以使用如下代码执行用户相关的查询:
$sql='SELECT * FROM {{user}}';
$users=$connection->createCommand($sql)->queryAll();

三、Active Record

虽然Yii DAO 可以处理几乎任何数据库相关的任务,但很可能我们会花费90% 的时间以编写一些执行普通CRUD(create, read, update 和delete)操作的SQL 语句。而且我们的代码中混杂了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 并非要解决所有数据库相关的任务。它的最佳应用是模型化数据表为PHP 结构和执行不包含复杂SQL 语句的查询。对于复杂查询的场景,应使用Yii DAO。

1、建立数据库连接

AR 依靠一个数据库连接以执行数据库相关的操作。默认情况下,它假定db 应用组件提供了所需的CDbConnection 数据库连接实例。如下应用配置提供了一个例子:

复制代码

return array(
   'components'=>array(
      'db'=>array(
         'class'=>'system.db.CDbConnection',
         'connectionString'=>'sqlite:path/to/dbfile',
         // 开启表结构缓存(schema caching)提高性能
         // 'schemaCachingDuration'=>3600,
      ),
   ),
);

复制代码

提示: 由于Active Record 依靠表的元数据(metadata)测定列的信息,读取元数据并解析需要时间。如果你数据库的表结构很少改动,你应该通过配置CDbConnection::schemaCachingDuration 属性的值为一个大于零的值开启表结构缓存。

如果你想使用一个不是db 的应用组件, 或者如果你想使用AR 处理多个数据库, 你应该覆盖CActiveRecord::getDbConnection()。CActiveRecord 类是所有AR 类的基类。
提示: 通过AR 使用多个数据库有两种方式。如果数据库的结构不同,你可以创建不同的AR 基类实现不同的getDbConnection()。否则,动态改变静态变量CActiveRecord::db 是一个好主意。

2、定义 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.*',
   'application.components.*',
   ),
);

默认情况下,AR 类的名字和数据表的名字相同。如果不同,请覆盖tableName()方法。
要使用表前缀功能,AR 类的tableName() 方法可以通过如下方式覆盖

public function tableName()
{
   return '{{post}}';
}

这就是说,我们将没有前缀的表名用双大括号括起来,这样Yii 就能自动添加前缀,从而返回完整的表名。
数据表行中列的值可以作为相应AR 实例的属性访问。例如,如下代码设置了title 列(属性):

$post=new Post;
$post->title='a sample post';

虽然我们从未在Post 类中显式定义属性title,我们还是可以通过上述代码访问。这是因为title 是tbl_post 表中的一个列,CActiveRecord 通过PHP 的__get()魔术方法使其成为一个可访问的属性。如果我们尝试以同样的方式访问一个不存在的列,将会抛出一个异常。

如果一个表没有主键,则必须在相应的AR 类中通过如下方式覆盖primaryKey() 方法指定哪一列或哪几列作为主键。

public function primaryKey()
{
   return 'id';
   // 对于复合主键,要返回一个类似如下的数组
   // return array('pk1', 'pk2');
}
3、创建记录

要向数据表中插入新行,我们要创建一个相应AR 类的实例,设置其与表的列相关的属性,然后调用save() 方法完
成插入:

$post=new Post;
$post->title='sample post';
$post->content='content for the sample post';
$post->create_time=time();
$post->save();

如果表的主键是自增的,在插入完成后,AR 实例将包含一个更新的主键。在上面的例子中,id 属性将反映出新插入帖子的主键值,即使我们从未显式地改变它。

如果一个列在表结构中使用了静态默认值(例如一个字符串,一个数字)定义。则AR 实例中相应的属性将在此实例创建时自动含有此默认值。改变此默认值的一个方式就是在AR 类中显示定义此属性:

复制代码

class Post extends CActiveRecord
{
   public $title='please enter a title';
   ......
}
$post=new Post;
echo $post->title; // 这儿将显示: please enter a title

复制代码

记录在保存(插入或更新)到数据库之前,其属性可以赋值为CDbExpression 类型。例如,为保存一个由MySQL 的NOW() 函数返回的时间戳,我们可以使用如下代码:

$post=new Post;
$post->create_time=new CDbExpression('NOW()'); //CDbExpression 类就是计算数据库表达式的值
// $post->create_time='NOW()'; 不会起作用,因为
// 'NOW()' 将会被作为一个字符串处理。
$post->save();

提示: 由于AR 允许我们无需写一大堆SQL 语句就能执行数据库操作, 我们经常会想知道AR 在背后到底执行了什么SQL 语句。这可以通过开启Yii 的日志功能实现。例如,我们在应用配置中开启了CWebLogRoute,我们将会在每个网页的最后看到执行过的SQL 语句。我们也可以在应用配置中设置CDbConnection::enableParamLogging 为true,这样

绑定在SQL 语句中的参数值也会被记录。

4、读取记录

要读取数据表中的数据,我们可以通过如下方式调用find 系列方法中的一种:

复制代码

// 查找满足指定条件的结果中的第一行
$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);

复制代码

如上所示,我们通过Post::model() 调用find 方法。请记住,静态方法model() 是每个AR 类所必须的。此方法返回在对象上下文中的一个用于访问类级别方法(类似于静态类方法的东西)的AR 实例。

如果find 方法找到了一个满足查询条件的行,它将返回一个Post 实例,实例的属性含有数据表行中相应列的值。然后我们就可以像读取普通对象的属性那样读取载入的值,例如echo $post->title;。
如果使用给定的查询条件在数据库中没有找到任何东西, find 方法将返回null。
调用find 时,我们使用$condition 和$params 指定查询条件。此处$condition 可以是SQL 语句中的WHERE 字符串,$params 则是一个参数数组,其中的值应绑定到$condation 中的占位符。例如:

// 查找postID=10 的那一行
$post=Post::model()->find('postID=:postID', array(':postID'=>10));

注意: 在上面的例子中,我们可能需要在特定的DBMS 中将postID 列的引用进行转义。例如,如果我们使用PostgreSQL,我们必须将此表达式写为"postID"=:postID,因为PostgreSQL 在默认情况下对列名大小写不敏感。

我们也可以使用$condition 指定更复杂的查询条件。不使用字符串,我们可以让$condition 成为一个CDbCriteria 的实例,它允许我们指定不限于WHERE 的条件。例如:

$criteria=new CDbCriteria;
$criteria->select='title'; // 只选择'title' 列
$criteria->condition='postID=:postID';
$criteria->params=array(':postID'=>10);
$post=Post::model()->find($criteria); // $params 不需要了

注意,当使用CDbCriteria 作为查询条件时,$params 参数不再需要了,因为它可以在CDbCriteria 中指定,就像上面那样。

一种替代CDbCriteria 的方法是给find 方法传递一个数组。数组的键和值各自对应标准( criterion)的属性名和值,上面的例子可以重写为如下:

$post=Post::model()->find(array(
'select'=>'title',
'condition'=>'postID=:postID',
'params'=>array(':postID'=>10),
));

当一个查询条件是关于按指定的值匹配几个列时,我们可以使用findByAttributes()。我们使$attributes 参数是一个以列名做索引的值的数组。在一些框架中,此任务可以通过调用类似findByNameAndTitle 的方法实现。虽然此方法看起来很诱人, 但它常常引起混淆,冲突和比如列名大小写敏感的问题。

当有多行数据匹配指定的查询条件时,我们可以通过下面的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);
如果没有任何东西符合查询条件,findAll 将返回一个空数组。这跟find 不同,find 会在没有找到什么东西时返回null。
除了上面讲述的find 和findAll 方法,为了方便,(Yii)还提供了如下方法:
// 获取满足指定条件的行数
$n=Post::model()->count($condition,$params);
// 通过指定的SQL 获取结果行数
$n=Post::model()->countBySql($sql,$params);
// 检查是否至少有一行复合指定的条件
$exists=Post::model()->exists($condition,$params);

复制代码

5、更新记录

在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 在前面的段落中已有描述。

复制代码

6、删除记录

如果一个AR 实例被一行数据填充,我们也可以删除此行数据。

复制代码

$post=Post::model()->findByPk(10); // 假设有一个帖子,其ID 为10
$post->delete(); // 从数据表中删除此行
注意,删除之后, AR 实例仍然不变,但数据表中相应的行已经没了。
使用下面的类级别代码,可以无需首先加载行就可以删除它。
// 删除符合指定条件的行
Post::model()->deleteAll($condition,$params);
// 删除符合指定条件和主键的行
Post::model()->deleteByPk($pk,$condition,$params);

复制代码

7、数据验证

当插入或更新一行时,我们常常需要检查列的值是否符合相应的规则。如果列的值是由最终用户提供的,这一点就更加重要。总体来说,我们永远不能相信任何来自客户端的数据。
当调用save() 时, AR 会自动执行数据验证。验证是基于在AR 类的rules() 方法中指定的规则进行的。关于验证规则的更多详情,请参考声明验证规则一节。下面是保存记录时所需的典型的工作流。
if($post->save())
{
// 数据有效且成功插入/更新
}
else
{
// 数据无效,调用getErrors() 提取错误信息
}
当要插入或更新的数据由最终用户在一个HTML 表单中提交时,我们需要将其赋给相应的AR 属性。我们可以通过
类似如下的方式实现:
$post->title=$_POST['title'];
$post->content=$_POST['content'];
$post->save();
如果有很多列,我们可以看到一个用于这种复制的很长的列表。这可以通过使用如下所示的attributes 属性简化操作。
更多信息可以在安全的特性赋值一节和创建动作一节找到。
// 假设$_POST['Post'] 是一个以列名索引列值为值的数组
$post->attributes=$_POST['Post'];
$post->save();

8、对比记录

类似于表记录,AR 实例由其主键值来识别。因此,要对比两个AR 实例,假设它们属于相同的AR 类, 我们只需要
对比它们的主键值。然而,一个更简单的方式是调用CActiveRecord::equals()。
不同于AR 在其他框架的执行, Yii 在其AR 中支持多个主键. 一个复合主键由两个或更多字段构成。相应地,主键值
在Yii 中表现为一个数组。primaryKey 属性给出了一个AR 实例的主键值。

9、自定义

CActiveRecord 提供了几个占位符方法,它们可以在子类中被覆盖以自定义其工作流。
beforeValidate 和afterValidate:这两个将在验证数据有效性之前和之后被调用。
beforeSave 和afterSave: 这两个将在保存AR 实例之前和之后被调用。
beforeDelete 和afterDelete: 这两个将在一个AR 实例被删除之前和之后被调用。
afterConstruct: 这个将在每个使用new 操作符创建AR 实例后被调用。
beforeFind: 这个将在一个AR 查找器被用于执行查询(例如find(), findAll())之前被调用。
afterFind: 这个将在每个AR 实例作为一个查询结果创建时被调用。

10、使用 AR处理事务

每个AR 实例都含有一个属性名叫dbConnection ,是一个CDbConnection 的实例,这样我们可以在需要时配合AR
使用由Yii DAO 提供的事务功能:
$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();
}

11、命名范围

命名范围(named scope)表示一个命名的(named)查询规则,它可以和其他命名范围联合使用并应用于Active Record
查询。
命名范围主要是在CActiveRecord::scopes() 方法中以名字-规则对的方式声明。如下代码在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 方法调用的修改器。几个命名范围可以链到一起形成一个更有约束性的查询结果集。例如,要找
到最近发布的帖子,我们可以使用如下代码:
$posts=Post::model()->published()->recently()->findAll();
总体来说,命名范围必须出现在一个find 方法调用的左边。它们中的每一个都提供一个查询规则,并联合到其他规则,
包括传递给find 方法调用的那一个。最终结果就像给一个查询添加了一系列过滤器。
命名范围也可用于update 和delete 方法。例如,如下代码将删除所有最近发布的帖子:
Post::model()->published()->recently()->delete();
注意: 命名范围只能用于类级别方法。也就是说,此方法必须使用ClassName::model() 调用。

12、参数化的命名范围

命名范围可以参数化。例如, 我们想自定义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 条最近发布的帖子。

13、默认的命名范围

模型类可以有一个默认命名范围,它将应用于所有(包括相关的那些) 关于此模型的查询。例如,一个支持多种语言的
网站可能只想显示当前用户所指定的语言的内容。因为可能会有很多关于此网站内容的查询,我们可以定义一个默认
的命名范围以解决此问题。为实现此目的,我们覆盖CActiveRecord::defaultScope 方法如下:
class Content extends CActiveRecord
{
public function defaultScope()
{
return array(
'condition'=>"language='".Yii::app()->language."'",
);
}
}
现在,如果下面的方法被调用,将会自动使用上面定义的查询规则:
$contents=Content::model()->findAll();
注意,默认的命名范围只会应用于SELECT 查询。INSERT, UPDATE 和DELETE 查询将被忽略。
 

 

Active Record(AR)是一个流行的对象-关系映射(ORM)技术。每个AR 类代表一个数据表(或视图),数据表(或视图)的列在AR 类中体现为类的属性,一个AR 实例则表示表中的一行。常见的CRUD 操作作为AR 的方法实现。因此,我们可以以一种更加面向对象的方式访问数据。$c = new CDbCriteria();是ActiveRecord的一种写法,使ActiveRecord更加灵活,而拼装sql则是最常见不过,下面看两个例子。

注意: AR 并非要解决所有数据库相关的任务。它的最佳应用是模型化数据表为PHP 结构和执行不包含复杂SQL 语句的查询。对于复杂查询的场景,应使用Yii DAO。

复制代码

$criteria = new CDbCriteria;       
//函数方式 
$criteria->addCondition("id=1"); //查询条件,即where id = 1   
$criteria->addInCondition('id', array(1,2,3,4,5)); //代表where id IN (1,23,,4,5,);   
$criteria->addNotInCondition('id', array(1,2,3,4,5));//与上面正好相法,是NOT IN   
$criteria->addCondition('id=1','OR');//这是OR条件,多个条件的时候,该条件是OR而非AND   
$criteria->addSearchCondition('name', '分类');//搜索条件,其实代表了。。where name like '%分类%'   
$criteria->addBetweenCondition('id', 1, 4);//between 1 and 4    
     
$criteria->compare('id', 1);    //这个方法比较特殊,他会根据你的参数自动处理成addCondition或者addInCondition,    
                                //即如果第二个参数是数组就会调用addInCondition   
$criteria->addCondition("id = :id");   
$criteria->params[':id']=1;   
      
//属性方式   
$criteria->select = 'id,parentid,name'; //代表了要查询的字段,默认select='*';   
$criteria->join = 'xxx'; //连接表   
$criteria->with = 'xxx'; //调用relations    
$criteria->limit = 10;    //取1条数据,如果小于0,则不作处理   
$criteria->offset = 1;   //两条合并起来,则表示 limit 10 offset 1,或者代表了。limit 1,10   
$criteria->order = 'xxx DESC,XXX ASC' ;//排序条件   
$criteria->group = 'group 条件';   
$criteria->having = 'having 条件 ';   
$criteria->distinct = FALSE; //是否唯一查询

复制代码

 

复制代码

$criteria = new CDbCriteria(); 
$criteria->select = 'table_name,model_id,sum(amount) total'; 
$criteria->group = 'table_name,model_id'; 
$criteria->addCondition("$nIdcId=4");//也可以$criteria->condition = "$nIdcId=4"; 
$aResult = accessory_info::model()->findAll($criteria); 
  
$c = new CDbCriteria(); 
$c->select = 't.id, t.created_at, t.outsource_id, t.user_id, t.operate, t.content';  
$c->join = 'LEFT JOIN outsource ON outsource.id=t.outsource_id'; 
$c->condition = 'outsource.idc_id IN(' . implode(',', $idc_ids)  . ')'; 
   
if($last_log_id) { 
    $c->condition .= " AND t.id > $last_log_id"; 
} 
   
$c->limit = 20; 
$c->order = 't.id DESC';  
$logs = OutsourceProcessLog::model()->findAll($c);

复制代码

 

Yii数据访问对象(DAO)建立在PHP的数据对象(PDO)扩展上,使得在一个单一的统一的接口可以访问不同的数据库管理系统(DBMS)。使用Yii的DAO开发的应用程序可以很容易地切换使用不同的数据库管理系统,而不需要修改数据访问代码。下面是DAO的例子:

 

复制代码

/**
     * 关于DAO的例子
     * 获取所有用户名
     */
    public function getUsernames ()
    {
        $sqlStatement = ' SELECT `username` FROM `testdrive`.`tbl_user` ';
        $this->command = $this->connection->createCommand($sqlStatement);
        return $this->command->queryAll();
    }
    /**
     *
     * 添加一个用户
     */
    public function addUser ()
    {
        $sqlStatement = " INSERT INTO `tbl_user` (`username`, `password`, `email`) VALUES ('test', 'test', 'test@test.com') ";
        $this->command = $this->connection->createCommand($sqlStatement);
        return $this->command->execute();
    }

复制代码

有几点注意的地方:

1,与DAO方式结果区别是:DAO方式数组中的每一个元素仍是数组。而通过CDbCriteria方式,数组中的元素是对象。
2,即使该功能很强大,仍有一些需求不能满足,此时仍需sql语句。比如select avg(num) amount from ****。 
3,打印运行后的结果,可更深入yii这么做是如何实现的

复制代码

//可见
$c = new CDbCriteria();
$c->join = "JOIN idc_user on t.id=idc_user.user_id";
$c->condition = "idc_user.idc_id=$idc_id";
 
//运行后
object(CDbCriteria)#98 (12) { ["select"]=> string(1) "*" ["distinct"]=> bool(false) ["condition"]=> string(17) "idc_user.idc_id=6" ["params"]=> array(0) { } ["limit"]=> int(-1) ["offset"]=> int(-1) ["order"]=> string(0) "" ["group"]=> string(0) "" ["join"]=> string(38) "JOIN idc_user on t.id=idc_user.user_id" ["having"]=> string(0) "" ["with"]=> NULL ["alias"]=> NULL }
 
//User::model()->with('Idcs')->findAll($c)

转载于:https://my.oschina.net/botkenni/blog/922578

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值