Yii提供了強大的數(shù)據(jù)庫編程支持。Yii數(shù)據(jù)訪問對象(DAO)建立在PHP的數(shù)據(jù)對象(PDO)extension上,使得在一個單一的統(tǒng)一的接口可以訪問不同的數(shù)據(jù)庫管理系統(tǒng)(DBMS)。使用Yii的DAO開發(fā)的應用程序可以很容易地切換使用不同的數(shù)據(jù)庫管理系統(tǒng),而不需要修改數(shù)據(jù)訪問代碼。Yii 的Active Record( AR ),實現(xiàn)了被廣泛采用的對象關(guān)系映射(ORM)辦法,進一步簡化數(shù)據(jù)庫編程。按照約定,一個類代表一個表,一個實例代表一行數(shù)據(jù)。Yii AR消除了大部分用于處理CRUD(創(chuàng)建,讀取,更新和刪除)數(shù)據(jù)操作的sql語句的重復任務。
盡管Yii的DAO和AR能夠處理幾乎所有數(shù)據(jù)庫相關(guān)的任務,您仍然可以在Yii application中使用自己的數(shù)據(jù)庫。事實上,Yii框架精心設計使得可以與其他第三方庫同時使用。
一、數(shù)據(jù)訪問對象 (DAO)
Yii DAO 基于 PHP Data Objects (PDO) 構(gòu)建。它是一個為眾多流行的DBMS提供統(tǒng)一數(shù)據(jù)訪問的擴展,這些 DBMS 包括 MySQL, PostgreSQL 等等。因此,要使用 Yii DAO,PDO 擴展和特定的 PDO 數(shù)據(jù)庫驅(qū)動(例如 PDO_MYSQL) 必須安裝。
Yii DAO 主要包含如下四個類:
CDbConnection: 代表一個數(shù)據(jù)庫連接。
CDbCommand: 代表一條通過數(shù)據(jù)庫執(zhí)行的 SQL 語句。
CDbDataReader: 代表一個只向前移動的,來自一個查詢結(jié)果集中的行的流。
CDbTransaction: 代表一個數(shù)據(jù)庫事務。
1、建立數(shù)據(jù)庫連接
要建立一個數(shù)據(jù)庫連接,創(chuàng)建一個 CDbConnection 實例并將其激活。連接到數(shù)據(jù)庫需要一個數(shù)據(jù)源的名字(DSN)以指定連接信息。用戶名和密碼也可能會用到。當連接到數(shù)據(jù)庫的過程中發(fā)生錯誤時 (例如,錯誤的 DSN 或無效的用戶名/密碼),將會拋出一個異常。
$connection=new CDbConnection($dsn,$username,$password);
// 建立連接。你可以使用 try...catch 捕獲可能拋出的異常
$connection->active=true;
......
$connection->active=false; // 關(guān)閉連接
DSN 的格式取決于所使用的 PDO 數(shù)據(jù)庫驅(qū)動??傮w來說, DSN 要含有 PDO 驅(qū)動的名字,跟上一個冒號,再跟上驅(qū)動特定的連接語法。可查閱 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 (或其他名字)應用組件如下:
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 訪問數(shù)據(jù)庫連接了。它已經(jīng)被自動激活了,除非我們特意配置了 CDbConnection::autoConnect 為 false。通過這種方式,這個單獨的DB連接就可以在我們代碼中的很多地方共享。
2、執(zhí)行SQL語句
數(shù)據(jù)庫連接建立后,SQL 語句就可以通過使用 CDbCommand 執(zhí)行了。你可以通過使用指定的SQL語句作為參數(shù)調(diào)用 CDbConnection::createCommand()創(chuàng)建一個 CDbCommand 實例。
$connection=Yii::app()->db; // 假設你已經(jīng)建立了一個 "db" 連接
// 如果沒有,你可能需要顯式建立一個連接:
// $connection=new CDbConnection($dsn,$username,$password);
$command=$connection->createCommand($sql);
// 如果需要,此 SQL 語句可通過如下方式修改:
// $command->text=$newSQL;
一條 SQL 語句會通過 CDbCommand 以如下兩種方式被執(zhí)行:
execute(): 執(zhí)行一個無查詢 (non-query)SQL語句,例如 INSERT, UPDATE 和 DELETE 。如果成功,它將返回此執(zhí)行所影響的行數(shù)。
query(): 執(zhí)行一條會返回若干行數(shù)據(jù)的 SQL 語句,例如 SELECT。如果成功,它將返回一個 CDbDataReader 實例,通過此實例可以遍歷數(shù)據(jù)的結(jié)果行。為簡便起見,(Yii)還實現(xiàn)了一系列 queryXXX() 方法以直接返回查詢結(jié)果。
執(zhí)行 SQL 語句時如果發(fā)生錯誤,將會拋出一個異常。
$rowCount=$command->execute(); // 執(zhí)行無查詢SQL
$dataReader=$command->query(); // 執(zhí)行一個SQL查詢
$rows=$command->queryAll(); // 查詢并返回結(jié)果中的所有行
$row=$command->queryRow(); // 查詢并返回結(jié)果中的第一行
$column=$command->queryColumn(); // 查詢并返回結(jié)果中的第一列
$value=$command->queryScalar(); // 查詢并返回結(jié)果中第一行的第一個字段
3、獲取查詢結(jié)果
在CDbCommand::query() 生成 CDbDataReader 實例之后,你可以通過重復調(diào)用CDbDataReader::read() 獲取結(jié)果中的行。你也可以在 PHP 的 foreach 語言結(jié)構(gòu)中使用 CDbDataReader 一行行檢索數(shù)據(jù)。
$dataReader=$command->query();
// 重復調(diào)用 read() 直到它返回 false
while(($row=$dataReader->read())!==false) { ... }
// 使用 foreach 遍歷數(shù)據(jù)中的每一行
foreach($dataReader as $row) { ... }
// 一次性提取所有行到一個數(shù)組
$rows=$dataReader->readAll();
注意: 不同于query(), 所有的queryXXX()方法會直接返回數(shù)據(jù)。例如,queryRow()會返回代表查詢結(jié)果第一行的一個數(shù)組。
4、使用事務
事務,在 Yii 中表現(xiàn)為 CDbTransaction 實例,可能會在下面的情況中啟動:
* 開始事務.
* 一個個執(zhí)行查詢。任何對數(shù)據(jù)庫的更新對外界不可見。
* 提交事務。如果事務成功,更新變?yōu)榭梢姟?br>
* 如果查詢中的一個失敗,整個事務回滾。
上述工作流可以通過如下代碼實現(xiàn):
$transaction=$connection->beginTransaction();
try
{
$connection->createCommand($sql1)->execute();
$connection->createCommand($sql2)->execute();
//.... other SQL executions
$transaction->commit();
}
catch(Exception $e) // 如果有一條查詢失敗,則會拋出異常
{
$transaction->rollBack();
}
5、綁定參數(shù)
要避免 SQL 注入攻擊 并提高重復執(zhí)行的 SQL 語句的效率,你可以 "準備(prepare)"一條含有可選參數(shù)占位符的 SQL 語句,在參數(shù)綁定時,這些占位符將被替換為實際的參數(shù)。
參數(shù)占位符可以是命名的 (表現(xiàn)為一個唯一的標記) 或未命名的 (表現(xiàn)為一個問號)。調(diào)用 CDbCommand::bindParam() 或 CDbCommand::bindValue() 以使用實際參數(shù)替換這些占位符。這些參數(shù)不需要使用引號引起來:底層的數(shù)據(jù)庫驅(qū)動會為你搞定這個。參數(shù)綁定必須在 SQL 語句執(zhí)行之前完成。
// 一條帶有兩個占位符 ":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();
// 使用新的參數(shù)集插入另一行
$command->bindParam(":username",$username2,PDO::PARAM_STR);
$command->bindParam(":email",$email2,PDO::PARAM_STR);
$command->execute();
方法 bindParam() 和 bindValue() 非常相似。唯一的區(qū)別就是前者使用一個PHP變量綁定參數(shù),而后者使用一個值。對于那些內(nèi)存中的大數(shù)據(jù)塊參數(shù),處于性能的考慮,應優(yōu)先使用前者。
6、綁定列
當獲取查詢結(jié)果時,你也可以使用PHP變量綁定列。這樣在每次獲取查詢結(jié)果中的一行時就會自動使用最新的值填充。
$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 是指不帶前綴的表名。例如,如果數(shù)據(jù)庫含有一個名為 tbl_user 的表,而 tbl_ 被配置為表前綴,那我們就可以使用如下代碼執(zhí)行用戶相關(guān)的查詢:
$sql='SELECT * FROM {{user}}';
$users=$connection->createCommand($sql)->queryAll();
二、Active Record
雖然Yii DAO可以處理幾乎任何數(shù)據(jù)庫相關(guān)的任務,但很可能我們會花費 90% 的時間以編寫一些執(zhí)行普通 CRUD(create, read, update 和 delete)操作的SQL語句。而且我們的代碼中混雜了SQL語句時也會變得難以維護。要解決這些問題,我們可以使用Active Record。
Active Record(AR)是一個流行的對象-關(guān)系映射(ORM)技術(shù)。每個 AR 類代表一個數(shù)據(jù)表(或視圖),數(shù)據(jù)表(或視圖)的列在 AR 類中體現(xiàn)為類的屬性,一個AR實例則表示表中的一行。常見的 CRUD 操作作為 AR 的方法實現(xiàn)。因此,我們可以以一種更加面向?qū)ο蟮姆绞皆L問數(shù)據(jù)。例如,我們可以使用以下代碼向tbl_post表中插入一個新行。
$post=new Post;
$post->title='sample post';
$post->content='post body content';
$post->save();
注意: AR并非要解決所有數(shù)據(jù)庫相關(guān)的任務。它的最佳應用是模型化數(shù)據(jù)表為PHP結(jié)構(gòu)和執(zhí)行不包含復雜SQL語句的查詢。 對于復雜查詢的場景,應使用Yii DAO。
1、建立數(shù)據(jù)庫連接
AR依靠一個數(shù)據(jù)庫連接以執(zhí)行數(shù)據(jù)庫相關(guān)的操作。默認情況下,它假定db應用組件提供了所需的CDbConnection數(shù)據(jù)庫連接實例。如下應用配置提供了一個例子:
return array(
'components'=>array(
'db'=>array(
'class'=>'system.db.CDbConnection',
'connectionString'=>'sqlite:path/to/dbfile',
// 開啟表結(jié)構(gòu)緩存(schema caching)提高性能
// 'schemaCachingDuration'=>3600,
),
),
);
提示: 由于Active Record依靠表的元數(shù)據(jù)(metadata)測定列的信息,讀取元數(shù)據(jù)并解析需要時間。 如果你數(shù)據(jù)庫的表結(jié)構(gòu)很少改動,你應該通過配置CDbConnection::schemaCachingDuration屬性的值為一個大于零的值開啟表結(jié)構(gòu)緩存。
如果你想使用一個不是db的應用組件,或者如果你想使用AR處理多個數(shù)據(jù)庫,你應該覆蓋CActiveRecord::getDbConnection() 。CActiveRecord類是所有AR類的基類。
提示: 通過AR使用多個數(shù)據(jù)庫有兩種方式。如果數(shù)據(jù)庫的結(jié)構(gòu)不同,你可以創(chuàng)建不同的AR基類實現(xiàn)不同的getDbConnection()。否則,動態(tài)改變靜態(tài)變量CActiveRecord::db是一個好主意。
2、定義AR類
要訪問一個數(shù)據(jù)表,我們首先需要通過集成CActiveRecord定義一個AR類。每個AR類代表一個單獨的數(shù)據(jù)表,一個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 類經(jīng)常在多處被引用,我們可以導入包含 AR 類的整個目錄,而不是一個個導入。 例如,如果我們所有的 AR 類文件都在 protected/models 目錄中,我們可以配置應用如下:
return array(
'import'=>array(
'application.models.*',
),
);
默認情況下,AR類的名字和數(shù)據(jù)表的名字相同。如果不同,請覆蓋tableName()方法。
要使用表前綴功能,AR類的 tableName() 方法可以通過如下方式覆蓋
public function tableName()
{
return '{{post}}';
}
這就是說,我們將沒有前綴的表名用雙大括號括起來,這樣Yii就能自動添加前綴,從而返回完整的表名。
數(shù)據(jù)表行中列的值可以作為相應AR實例的屬性訪問。例如,如下代碼設置了 title 列 (屬性):
$post=new Post;
$post->title='a sample post';
雖然我們從未在Post類中顯式定義屬性title,我們還是可以通過上述代碼訪問。這是因為title是tbl_post表中的一個列,CActiveRecord通過PHP的__get()魔術(shù)方法使其成為一個可訪問的屬性。如果我們嘗試以同樣的方式訪問一個不存在的列,將會拋出一個異常。
如果一個表沒有主鍵,則必須在相應的AR類中通過如下方式覆蓋 primaryKey() 方法指定哪一列或哪幾列作為主鍵。
public function primaryKey()
{
return 'id';
// 對于復合主鍵,要返回一個類似如下的數(shù)組
// return array('pk1', 'pk2');
}
3、創(chuàng)建記錄
要向數(shù)據(jù)表中插入新行,我們要創(chuàng)建一個相應 AR 類的實例,設置其與表的列相關(guān)的屬性,然后調(diào)用 save() 方法完成插入:
$post=new Post;
$post->title='sample post';
$post->content='content for the sample post';
$post->create_time=time();
$post->save();
如果表的主鍵是自增的,在插入完成后,AR實例將包含一個更新的主鍵。在上面的例子中,id屬性將反映出新插入帖子的主鍵值,即使我們從未顯式地改變它。
如果一個列在表結(jié)構(gòu)中使用了靜態(tài)默認值(例如一個字符串,一個數(shù)字)定義。則AR實例中相應的屬性將在此實例創(chuàng)建時自動含有此默認值。改變此默認值的一個方式就是在AR類中顯示定義此屬性:
class Post extends CActiveRecord
{
public $title='please enter a title';
......
}
$post=new Post;
echo $post->title; // 這兒將顯示: please enter a title
記錄在保存(插入或更新)到數(shù)據(jù)庫之前,其屬性可以賦值為 CDbExpression 類型。例如,為保存一個由MySQL的 NOW() 函數(shù)返回的時間戳,我們可以使用如下代碼:
$post=new Post;
$post->create_time=new CDbexpression_r('NOW()'); //CDbExpression類就是計算數(shù)據(jù)庫表達式的值
// $post->create_time='NOW()'; 不會起作用,因為
// 'NOW()' 將會被作為一個字符串處理。
$post->save();
提示: 由于AR允許我們無需寫一大堆SQL語句就能執(zhí)行數(shù)據(jù)庫操作, 我們經(jīng)常會想知道AR在背后到底執(zhí)行了什么SQL語句。這可以通過開啟Yii的日志功能實現(xiàn)。例如,我們在應用配置中開啟了CWebLogRoute,我們將會在每個網(wǎng)頁的最后看到執(zhí)行過的SQL語句。 我們也可以在應用配置中設置CDbConnection::enableParamLogging為true,這樣綁定在SQL語句中的參數(shù)值也會被記錄。
4、讀取記錄
要讀取數(shù)據(jù)表中的數(shù)據(jù),我們可以通過如下方式調(diào)用 find 系列方法中的一種:
// 查找滿足指定條件的結(jié)果中的第一行
$post=Post::model()->find($condition,$params);
// 查找具有指定主鍵值的那一行
$post=Post::model()->findByPk($postID,$condition,$params);
// 查找具有指定屬性值的行
$post=Post::model()->findByAttributes($attributes,$condition,$params);
// 通過指定的SQL語句查找結(jié)果中的第一行
$post=Post::model()->findBySql($sql,$params);
如上所示,我們通過 Post::model() 調(diào)用find 方法。請記住,靜態(tài)方法 model() 是每個AR類所必須的。此方法返回在對象上下文中的一個用于訪問類級別方法(類似于靜態(tài)類方法的東西)的AR實例。
如果find方法找到了一個滿足查詢條件的行,它將返回一個Post實例,實例的屬性含有數(shù)據(jù)表行中相應列的值。然后我們就可以像讀取普通對象的屬性那樣讀取載入的值,例如 echo $post->title;。
如果使用給定的查詢條件在數(shù)據(jù)庫中沒有找到任何東西, find 方法將返回null。
調(diào)用find時,我們使用 $condition 和 $params 指定查詢條件。此處 $condition 可以是 SQL 語句中的 WHERE 字符串,$params 則是一個參數(shù)數(shù)組,其中的值應綁定到 $condition 中的占位符。例如:
// 查找 postID=10 的那一行
$post=Post::model()->find('postID=:postID', array(':postID'=>10));
注意: 在上面的例子中,我們可能需要在特定的 DBMS 中將 postID 列的引用進行轉(zhuǎn)義。
例如,如果我們使用 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 參數(shù)不再需要了,因為它可以在 CDbCriteria 中指定,就像上面那樣。
一種替代 CDbCriteria 的方法是給 find 方法傳遞一個數(shù)組。數(shù)組的鍵和值各自對應標準(criterion)的屬性名和值,上面的例子可以重寫為如下:
$post=Post::model()->find(array(
'select'=>'title',
'condition'=>'postID=:postID',
'params'=>array(':postID'=>10),
));
當一個查詢條件是關(guān)于按指定的值匹配幾個列時,我們可以使用 findByAttributes()。我們使 $attributes 參數(shù)是一個以列名做索引的值的數(shù)組。在一些框架中,此任務可以通過調(diào)用類似 findByNameAndTitle 的方法實現(xiàn)。雖然此方法看起來很誘人, 但它常常引起混淆,沖突和比如列名大小寫敏感的問題。
當有多行數(shù)據(jù)匹配指定的查詢條件時,我們可以通過下面的 findAll 方法將他們?nèi)繋Щ?。每個都有其各自的 find 方法,就像我們已經(jīng)講過的那樣。
// 查找滿足指定條件的所有行
$posts=Post::model()->findAll($condition,$params);
// 查找?guī)в兄付ㄖ麈I的所有行
$posts=Post::model()->findAllByPk($postIDs,$condition,$params);
// 查找?guī)в兄付▽傩灾档乃行?$posts=Post::model()->findAllByAttributes($attributes,$condition,$params);
// 通過指定的SQL語句查找所有行
$posts=Post::model()->findAllBySql($sql,$params);
如果沒有任何東西符合查詢條件,findAll 將返回一個空數(shù)組。
這跟 find 不同,find 會在沒有找到什么東西時返回 null。
除了上面講述的 find 和 findAll 方法,為了方便,(Yii)還提供了如下方法:
// 獲取滿足指定條件的行數(shù)
$n=Post::model()->count($condition,$params);
// 通過指定的 SQL 獲取結(jié)果行數(shù)
$n=Post::model()->countBySql($sql,$params);
// 檢查是否至少有一行復合指定的條件
$exists=Post::model()->exists($condition,$params);
5、更新記錄
在 AR 實例填充了列的值之后,我們可以改變它們并把它們存回數(shù)據(jù)表。
$post=Post::model()->findByPk(10);
$post->title='new post title';
$post->save(); // 將更改保存到數(shù)據(jù)庫
正如我們可以看到的,我們使用同樣的 save() 方法執(zhí)行插入和更新操作。如果一個 AR 實例是使用 new 操作符創(chuàng)建的,調(diào)用 save() 將會向數(shù)據(jù)表中插入一行新數(shù)據(jù);如果 AR 實例是某個find 或 findAll 方法的結(jié)果,調(diào)用 save() 將更新表中現(xiàn)有的行。實際上,我們是使用 CActiveRecord::isNewRecord 說明一個 AR 實例是不是新的。
直接更新數(shù)據(jù)表中的一行或多行而不首先載入也是可行的。 AR 提供了如下方便的類級別方法實現(xiàn)此目的:
// 更新符合指定條件的行
Post::model()->updateAll($attributes,$condition,$params);
// 更新符合指定條件和主鍵的行
Post::model()->updateByPk($pk,$attributes,$condition,$params);
// 更新滿足指定條件的行的計數(shù)列
Post::model()->updateCounters($counters,$condition,$params);
在上面的代碼中, $attributes 是一個含有以 列名作索引的列值的數(shù)組; $counters是一個由列名索引的可增加的值的數(shù)組;$condition 和 $params在前面的段落中已有描述。
6、刪除記錄
如果一個 AR 實例被一行數(shù)據(jù)填充,我們也可以刪除此行數(shù)據(jù)。
$post=Post::model()->findByPk(10); // 假設有一個帖子,其 ID 為 10
$post->delete(); // 從數(shù)據(jù)表中刪除此行
注意,刪除之后, AR 實例仍然不變,但數(shù)據(jù)表中相應的行已經(jīng)沒了。
使用下面的類級別代碼,可以無需首先加載行就可以刪除它。
// 刪除符合指定條件的行
Post::model()->deleteAll($condition,$params);
// 刪除符合指定條件和主鍵的行
Post::model()->deleteByPk($pk,$condition,$params);
7、數(shù)據(jù)驗證
當插入或更新一行時,我們常常需要檢查列的值是否符合相應的規(guī)則。如果列的值是由最終用戶提供的,這一點就更加重要??傮w來說,我們永遠不能相信任何來自客戶端的數(shù)據(jù)。
當調(diào)用 save() 時, AR 會自動執(zhí)行數(shù)據(jù)驗證。驗證是基于在 AR 類的 rules() 方法中指定的規(guī)則進行的。關(guān)于驗證規(guī)則的更多詳情,請參考 聲明驗證規(guī)則 一節(jié)。下面是保存記錄時所需的典型的工作流。
if($post->save())
{
// 數(shù)據(jù)有效且成功插入/更新
}
else
{
// 數(shù)據(jù)無效,調(diào)用 getErrors() 提取錯誤信息
}
當要插入或更新的數(shù)據(jù)由最終用戶在一個 HTML 表單中提交時,我們需要將其賦給相應的 AR 屬性。我們可以通過類似如下的方式實現(xiàn):
$post->title=$_POST['title'];
$post->content=$_POST['content'];
$post->save();
如果有很多列,我們可以看到一個用于這種復制的很長的列表。這可以通過使用如下所示的 attributes 屬性簡化操作。更多信息可以在 安全的特性賦值 一節(jié)和 創(chuàng)建動作 一節(jié)找到。
// 假設 $_POST['Post'] 是一個以列名索引列值為值的數(shù)組
$post->attributes=$_POST['Post'];
$post->save();
8、對比記錄
類似于表記錄,AR實例由其主鍵值來識別。因此,要對比兩個AR實例,假設它們屬于相同的AR類, 我們只需要對比它們的主鍵值。然而,一個更簡單的方式是調(diào)用 CActiveRecord::equals()。
不同于AR在其他框架的執(zhí)行, Yii在其 AR 中支持多個主鍵. 一個復合主鍵由兩個或更多字段構(gòu)成。相應地,主鍵值在Yii中表現(xiàn)為一個數(shù)組。primaryKey屬性給出了一個 AR 實例的主鍵值。
9、自定義
CActiveRecord 提供了幾個占位符方法,它們可以在子類中被覆蓋以自定義其工作流。
beforeValidate 和 afterValidate:這兩個將在驗證數(shù)據(jù)有效性之前和之后被調(diào)用。
beforeSave 和 afterSave: 這兩個將在保存 AR 實例之前和之后被調(diào)用。
beforeDelete 和 afterDelete: 這兩個將在一個 AR 實例被刪除之前和之后被調(diào)用。
afterConstruct: 這個將在每個使用 new 操作符創(chuàng)建 AR 實例后被調(diào)用。
beforeFind: 這個將在一個 AR 查找器被用于執(zhí)行查詢(例如 find(), findAll())之前被調(diào)用。
afterFind: 這個將在每個 AR 實例作為一個查詢結(jié)果創(chuàng)建時被調(diào)用。
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)查詢規(guī)則,它可以和其他命名范圍聯(lián)合使用并應用于Active Record查詢。
命名范圍主要是在 CActiveRecord::scopes() 方法中以名字-規(guī)則對的方式聲明。如下代碼在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 實例的數(shù)組。例如,recently 命名范圍指定 order 屬性為 create_time DESC , limit 屬性為 5。他們翻譯為查詢規(guī)則后就會返回最近的5篇帖子。
命名范圍多用作 find 方法調(diào)用的修改器。幾個命名范圍可以鏈到一起形成一個更有約束性的查詢結(jié)果集。例如,要找到最近發(fā)布的帖子,我們可以使用如下代碼:
$posts=Post::model()->published()->recently()->findAll();
總體來說,命名范圍必須出現(xiàn)在一個 find 方法調(diào)用的左邊。它們中的每一個都提供一個查詢規(guī)則,并聯(lián)合到其他規(guī)則,包括傳遞給 find 方法調(diào)用的那一個。最終結(jié)果就像給一個查詢添加了一系列過濾器。
命名范圍也可用于 update 和 delete 方法。例如,如下代碼將刪除所有最近發(fā)布的帖子:
Post::model()->published()->recently()->delete();
注意: 命名范圍只能用于類級別方法。也就是說,此方法必須使用 ClassName::model() 調(diào)用。
12、參數(shù)化的命名范圍
命名范圍可以參數(shù)化。例如,我們想自定義 recently 命名范圍中指定的帖子數(shù)量,要實現(xiàn)此目的,不是在CActiveRecord::scopes 方法中聲明命名范圍,而是需要定義一個名字和此命名范圍的名字相同的方法:
public function recently($limit=5)
{
$this->getDbCriteria()->mergeWith(array(
'order'=>'create_time DESC',
'limit'=>$limit,
));
return $this;
}
然后,我們就可以使用如下語句獲取3條最近發(fā)布的帖子。
$posts=Post::model()->published()->recently(3)->findAll();
上面的代碼中,如果我們沒有提供參數(shù) 3,我們將默認獲取 5 條最近發(fā)布的帖子。
13、默認的命名范圍
模型類可以有一個默認命名范圍,它將應用于所有 (包括相關(guān)的那些) 關(guān)于此模型的查詢。例如,一個支持多種語言的網(wǎng)站可能只想顯示當前用戶所指定的語言的內(nèi)容。因為可能會有很多關(guān)于此網(wǎng)站內(nèi)容的查詢,我們可以定義一個默認的命名范圍以解決此問題。為實現(xiàn)此目的,我們覆蓋 CActiveRecord::defaultScope 方法如下:
class Content extends CActiveRecord
{
public function defaultScope()
{
return array(
'condition'=>"language='".Yii::app()->language."'",
);
}
}
現(xiàn)在,如果下面的方法被調(diào)用,將會自動使用上面定義的查詢規(guī)則:
$contents=Content::model()->findAll();
注意,默認的命名范圍只會應用于 SELECT 查詢。INSERT, UPDATE 和 DELETE 查詢將被忽略。
三、Relational Active Record(關(guān)聯(lián)查詢)
我們已經(jīng)知道如何通過Active Record(AR)從單個數(shù)據(jù)表中取得數(shù)據(jù)了,在這一節(jié)中,我們將要介紹如何使用AR來連接關(guān)聯(lián)的數(shù)據(jù)表獲取數(shù)據(jù)。
在使用關(guān)聯(lián)AR之前,首先要在數(shù)據(jù)庫中建立關(guān)聯(lián)的數(shù)據(jù)表之間的主鍵-外鍵關(guān)聯(lián),AR需要通過分析數(shù)據(jù)庫中的定義數(shù)據(jù)表關(guān)聯(lián)的元信息,來決定如何連接數(shù)據(jù)。
1、如何聲明關(guān)聯(lián)
在使用AR進行關(guān)聯(lián)查詢之前,我們需要告訴AR各個AR類之間有怎樣的關(guān)聯(lián)。
AR類之間的關(guān)聯(lián)直接反映著數(shù)據(jù)庫中這個類所代表的數(shù)據(jù)表之間的關(guān)聯(lián)。從關(guān)系數(shù)據(jù)庫的角度來說,兩個數(shù)據(jù)表A,B之間可能的關(guān)聯(lián)有三種:一對多,一對一,多對多。而在AR中,關(guān)聯(lián)有以下四種:
BELONGS_TO: 如果數(shù)據(jù)表A和B的關(guān)系是一對多,那我們就說B屬于A(B belongs to A)。
HAS_MANY: 如果數(shù)據(jù)表A和B的關(guān)系是多對一,那我們就說B有多個A(B has many A)。
HAS_ONE: 這是‘HAS_MANY’關(guān)系中的一個特例,當A最多有一個的時候,我們說B有一個A (B has one A)。
MANY_MANY: 這個相當于關(guān)系數(shù)據(jù)庫中的多對多關(guān)系。因為絕大多數(shù)關(guān)系數(shù)據(jù)庫并不直接支持多對多的關(guān)系,這時通常都需要一個單獨的關(guān)聯(lián)表,把多對多的關(guān)系分解為兩個一對多的關(guān)系。用AR的方式去理解的話,我們可以認為 MANY_MANY關(guān)系是由BELONGS_TO和HAS_MANY組成的。
在AR中聲明關(guān)聯(lián),是通過覆蓋(Override)父類CActiveRecord中的relations()方法來實現(xiàn)的。這個方法返回一個包含了關(guān)系定義的數(shù)組,數(shù)組中的每一組鍵值代表一個關(guān)聯(lián):
'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', ...additional options)
這里的VarName是這個關(guān)聯(lián)的名稱;RelationType指定了這個關(guān)聯(lián)的類型,有四個常量代表了四種關(guān)聯(lián)的類型:
self::BELONGS_TO,self::HAS_ONE,self::HAS_MANY和self::MANY_MANY; ClassName是這個關(guān)系關(guān)聯(lián)到的AR類的類名;ForeignKey指定了這個關(guān)聯(lián)是通過哪個外鍵聯(lián)系起來的。后面的additional options可以加入一些額外的設置,后面會做介紹。
下面的代碼演示了如何定義User和Post之間的關(guān)聯(lián)。
class Post extends CActiveRecord {
public function relations() {
return array(
'author'=>array(
self::BELONGS_TO,
'User',
'authorID'
),
'categories'=>array(
self::MANY_MANY,
'Category',
'PostCategory(postID, categoryID)'
),
);
}
}
class User extends CActiveRecord {
public function relations() {
return array(
'posts'=>array(
self::HAS_MANY,
'Post',
'authorID'
),
'profile'=>array(
self::HAS_ONE,
'Profile',
'ownerID'
),
);
}
}
說明: 有時外鍵可能由兩個或更多字段組成,在這里可以將多個字段名由逗號或空格分隔, 一并寫在這里。對于多對多的關(guān)系,關(guān)聯(lián)表必須在外鍵中注明,例如在Post類的categories 關(guān)聯(lián)中,外鍵就需要寫成PostCategory(postID, categoryID)。
在AR類中聲明關(guān)聯(lián)時,每個關(guān)聯(lián)會作為一個屬性添加到AR類中,屬性名就是關(guān)聯(lián)的名稱。在進行關(guān)聯(lián)查詢時,這些屬性就會被設置為關(guān)聯(lián)到的AR類的實例,例如在查詢?nèi)〉靡粋€Post實例時,它的$author屬性就是代表Post作者的一個User類的實例。
2、關(guān)聯(lián)查詢
進行關(guān)聯(lián)查詢最簡單的方式就是訪問一個關(guān)聯(lián)AR對象的某個關(guān)聯(lián)屬性。如果這個屬性之前沒有被訪問過,這時就會啟動一個關(guān)聯(lián)查詢,通過當前AR對象的主鍵連接相關(guān)的表,來取得關(guān)聯(lián)對象的值,然后將這些數(shù)據(jù)保存在對象的屬性中。這種方式叫做“延遲加載”,也就是只有等到訪問到某個屬性時,才會真正到數(shù)據(jù)庫中把這些關(guān)聯(lián)的數(shù)據(jù)取出來。下面的例子描述了延遲加載的過程:
// retrieve the post whose ID is 10
$post=Post::model()->findByPk(10);
// retrieve the post's author: a relational query will be performed here
$author=$post->author;
在不同的關(guān)聯(lián)情況下,如果沒有查詢到結(jié)果,其返回的值也不同:BELONGS_TO 和 HAS_ONE 關(guān)聯(lián),無結(jié)果時返回null; HAS_MANY 和 MANY_MANY, 無結(jié)果時返回空數(shù)組。
延遲加載方法使用非常方便,但在某些情況下并不高效。例如,若我們要取得N個post的作者信息,使用延遲方法將執(zhí)行N次連接查詢。此時我們應當使用所謂的急切加載方法。
急切加載方法檢索主要的 AR 實例及其相關(guān)的 AR 實例. 這通過使用 with() 方法加上 find 或 findAll 方法完成。例如,
$posts=Post::model()->with('author')->findAll();
上面的代碼將返回一個由 Post 實例組成的數(shù)組. 不同于延遲加載方法,每個Post 實例中的author 屬性在我們訪問此屬性之前已經(jīng)被關(guān)聯(lián)的 User 實例填充。不是為每個post 執(zhí)行一個連接查詢, 急切加載方法在一個單獨的連接查詢中取出所有的 post 以及它們的author!
我們可以在with()方法中指定多個關(guān)聯(lián)名字。例如, 下面的代碼將取回 posts 以及它們的作者和分類:
$posts=Post::model()->with('author','categories')->findAll();
我們也可以使用嵌套的急切加載。不使用一個關(guān)聯(lián)名字列表, 我們將關(guān)聯(lián)名字以分層的方式傳遞到 with() 方法, 如下,
$posts=Post::model()->with(
'author.profile',
'author.posts',
'categories')->findAll();
上面的代碼將取回所有的 posts 以及它們的作者和分類。它也將取出每個作者的profile和 posts.
急切加載也可以通過指定 CDbCriteria::with 屬性被執(zhí)行, 如下:
$criteria=new CDbCriteria;
$criteria->with=array(
'author.profile',
'author.posts',
'categories',
);
$posts=Post::model()->findAll($criteria);
或
$posts=Post::model()->findAll(array(
'with'=>array(
'author.profile',
'author.posts',
'categories',
)
);
3、關(guān)聯(lián)查詢選項
之前我們提到額外的參數(shù)可以被指定在關(guān)聯(lián)聲明中。這些選項,指定為 name-value 對,被用來定制關(guān)聯(lián)查詢。它們被概述如下:
select: 為關(guān)聯(lián) AR 類查詢的字段列表。默認是 '*', 意味著所有字段。
查詢的字段名字可用別名表達式來消除歧義(例如:COUNT(??.name) AS nameCount)。
condition: WHERE 子語句。默認為空。注意, 列要使用別名引用(例如:??.id=10)。
params: 被綁定到 SQL 語句的參數(shù). 應當為一個由 name-value 對組成的數(shù)組()。
on: ON 子語句. 這里指定的條件將使用 and 操作符被追加到連接條件中。
此選項中的字段名應被消除歧義。此選項不適用于 MANY_MANY 關(guān)聯(lián)。
order: ORDER BY 子語句。默認為空。注意, 列要使用別名引用(例如:??.age DESC)。
with: 應當和此對象一同載入的子關(guān)聯(lián)對象列表. 注意, 不恰當?shù)氖褂每赡軙纬梢粋€無窮的關(guān)聯(lián)循環(huán)。
joinType: 此關(guān)聯(lián)的連接類型。默認是 LEFT OUTER JOIN。
aliasToken:列前綴占位符。默認是“??.”。
alias: 關(guān)聯(lián)的數(shù)據(jù)表的別名。默認是 null, 意味著表的別名和關(guān)聯(lián)的名字相同。
together: 是否關(guān)聯(lián)的數(shù)據(jù)表被強制與主表和其他表連接。此選項只對于HAS_MANY 和 MANY_MANY 關(guān)聯(lián)有意義。
若此選項被設置為 false, ......(此處原文出錯!).默認為空。此選項中的字段名以被消除歧義。
having: HAVING 子語句。默認是空。注意, 列要使用別名引用。
index: 返回的數(shù)組索引類型。確定返回的數(shù)組是關(guān)鍵字索引數(shù)組還是數(shù)字索引數(shù)組。
不設置此選項, 將使用數(shù)字索引數(shù)組。此選項只對于HAS_MANY 和 MANY_MANY 有意義
此外, 下面的選項在延遲加載中對特定關(guān)聯(lián)是可用的:
group: GROUP BY子句。默認為空。注意, 列要使用別名引用(例如:??.age)。
本選項僅應用于HAS_MANY 和 MANY_MANY 關(guān)聯(lián)。
having: HAVING子句。默認為空。注意, 列要使用別名引用(例如:??.age)。
本選項僅應用于HAS_MANY 和 MANY_MANY 關(guān)聯(lián)。
limit: 限制查詢的行數(shù)。本選項不能用于BELONGS_TO關(guān)聯(lián)。
offset: 偏移。本選項不能用于BELONGS_TO關(guān)聯(lián)。
下面我們改變在 User 中的 posts 關(guān)聯(lián)聲明,通過使用上面的一些選項:
class User extends CActiveRecord
{
public function relations()
{
return array(
'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
'order'=>'posts.create_time DESC',
'with'=>'categories'),
'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),
);
}
}
現(xiàn)在若我們訪問 $author->posts, 我們將得到用戶的根據(jù)發(fā)表時間降序排列的 posts. 每個 post 實例也載入了它的分類。
4、為字段名消除歧義
當一個字段的名字出現(xiàn)在被連接在一起的兩個或更多表中,需要消除歧義(disambiguated)??梢酝ㄟ^使用表的別名作為字段名的前綴實現(xiàn)。
在關(guān)聯(lián)AR查詢中,主表的別名確定為 t,而一個關(guān)聯(lián)表的別名和相應的關(guān)聯(lián)的名字相同(默認情況下)。 例如,在下面的語句中,Post 的別名是 t ,而 Comment 的別名是 comments:
$posts=Post::model()->with('comments')->findAll();
現(xiàn)在假設 Post 和 Comment 都有一個字段 create_time , 我們希望取出 posts 及它們的 comments ,排序方式是先根據(jù) posts 的創(chuàng)建時間,然后根據(jù) comment 的創(chuàng)建時間。 我們需要消除create_time 字段的歧義,如下:
$posts=Post::model()->with('comments')->findAll(array(
'order'=>'t.create_time, comments.create_time'
));
默認情況下,Yii 自動為每個關(guān)聯(lián)表產(chǎn)生一個表別名,我們必須使用此前綴 ??. 來指向這個自動產(chǎn)生的別名。 主表的別名是表自身的名字。
5、動態(tài)關(guān)聯(lián)查詢選項
我們使用 with()和 with 均可使用動態(tài)關(guān)聯(lián)查詢選項。 動態(tài)選項將覆蓋在 relations() 方法中指定的
已存在的選項。例如,使用上面的 User 模型, 若我們想要使用急切加載方法以升序來取出屬于一個作者的 posts(關(guān)聯(lián)中的order 選項指定為降序), 我們可以這樣做:
User::model()->with(array(
'posts'=>array('order'=>'posts.create_time ASC'),
'profile',
))->findAll();
動態(tài)查詢選項也可以在使用延遲加載方法時使用以執(zhí)行關(guān)聯(lián)查詢。 要這樣做,我們應當調(diào)用一個方法,它的名字和關(guān)聯(lián)的名字相同,并傳遞動態(tài)查詢選項 作為此方法的參數(shù)。例如,下面的代碼返回一個用戶的 status 為 1 的posts :
$user=User::model()->findByPk(1);
$posts=$user->posts(array('condition'=>'status=1'));
6、關(guān)聯(lián)查詢的性能
如上所述,急切加載方法主要用于當我們需要訪問許多關(guān)聯(lián)對象時。 通過連接所有所需的表它產(chǎn)生一個大而復雜的 SQL 語句。一個大的 SQL 語句在許多情況下是首選的。然而在一些情況下它并不高效。
考慮一個例子,若我們需要找出最新的文章以及它們的評論。 假設每個文章有 10 條評論,使用一個大的 SQL 語句,我們將取回很多多余的 post 數(shù)據(jù), 因為每個post 將被它的每條評論反復使用?,F(xiàn)在讓我們嘗試另外的方法:我們首先查詢最新的文章, 然后查詢它們的評論。用新的方法,我們需要執(zhí)行執(zhí)行兩條 SQL 語句。有點是在查詢結(jié)果中沒有多余的數(shù)據(jù)。
因此哪種方法更加高效?沒有絕對的答案。執(zhí)行一條大的 SQL 語句也許更加高效,因為它需要更少的花銷來解析和執(zhí)行 SQL 語句。另一方面,使用單條 SQL 語句,我們得到更多冗余的數(shù)據(jù),因此需要更多時間來閱讀和處理它們。 因為這個原因,Yii 提供了 together 查詢選項一邊我們在需要時選擇兩種方法之一。默認下, Yii 使用第一種方式,即產(chǎn)生一個單獨的 SQL 語句來執(zhí)行急切加載。我們可以在關(guān)聯(lián)聲明中設置 together 選項為 false 以便一些表被連接在單獨的 SQL 語句中。例如,為了使用第二種方法來查詢最新的文章及它們的評論,我們可以在 Post 類中聲明 comments 關(guān)聯(lián)如下,
public function relations()
{
return array(
'comments' => array(self::HAS_MANY, 'Comment', 'post_id', 'together'=>false),
);
}
當我們執(zhí)行急切加載時,我們也可以動態(tài)地設置此選項:
$posts = Post::model()->with(array('comments'=>array('together'=>false)))->findAll();
7、統(tǒng)計查詢
除了上面描述的關(guān)聯(lián)查詢,Yii 也支持所謂的統(tǒng)計查詢(或聚合查詢)。 它指的是檢索關(guān)聯(lián)對象的聚合信息,例如每個 post 的評論的數(shù)量,每個產(chǎn)品的平均等級等。 統(tǒng)計查詢只被 HAS_MANY(例如,一個 post 有很多評論) 或 MANY_MANY (例如,一個post 屬于很多分類和一個 category 有很多 post) 關(guān)聯(lián)對象執(zhí)行。
執(zhí)行統(tǒng)計查詢非常類似于之前描述的關(guān)聯(lián)查詢。我們首先需要在 CActiveRecord 的 relations() 方法中聲明統(tǒng)計查詢。
class Post extends CActiveRecord
{
public function relations()
{
return array(
'commentCount'=>array(self::STAT, 'Comment', 'post_id'),
'categoryCount'=>array(self::STAT, 'Category', 'post_category(post_id,
category_id)'),
);
}
}
在上面,我們聲明了兩個統(tǒng)計查詢:commentCount 計算屬于一個 post 的評論的數(shù)量,categoryCount 計算一個 post 所屬分類的數(shù)量。注意 Post 和 Comment 之間的關(guān)聯(lián)類型是 HAS_MANY, 而 Post 和 Category 之間的關(guān)聯(lián)類型是 MANY_MANY (使用連接表 PostCategory)。 如我們所看到的,聲明非常類似于之間小節(jié)中的關(guān)聯(lián)。唯一的不同是這里的關(guān)聯(lián)類型是 STAT。
有了上面的聲明,我們可以檢索使用表達式 $post->commentCount 檢索一個 post 的評論的數(shù)量。 當我們首次訪問此屬性,一個 SQL 語句將被隱含地執(zhí)行并檢索 對應的結(jié)果。我們已經(jīng)知道,這是所謂的 lazy loading 方法。若我們需要得到多個post 的評論數(shù)目,我們也可以使用 eager loading 方法:
$posts=Post::model()->with('commentCount', 'categoryCount')->findAll();
上面的語句將執(zhí)行三個 SQL 語句以取回所有的 post 及它們的評論數(shù)目和分類數(shù)目。使用延遲加載方法, 若有 N 個 post ,我們使用 2*N+1 條 SQL 查詢完成。
默認情況下,一個統(tǒng)計查詢將計算 COUNT 表達式(and thus the comment count and category count in the above example). 當我們在 relations()中聲明它時,通過 指定額外的選項,可以定制它??捎玫倪x項簡介如下。
select: 統(tǒng)計表達式。默認是 COUNT(*),意味著子對象的個數(shù)。
defaultValue: 沒有接收一個統(tǒng)計查詢結(jié)果時被賦予的值。例如,若一個 post 沒有任何評論,
它的 commentCount 將接收此值。此選項的默認值是 0。
condition: WHERE 子語句。默認是空。
params: 被綁定到產(chǎn)生的SQL 語句中的參數(shù)。它應當是一個 name-value 對組成的數(shù)組。
order: ORDER BY 子語句。默認是空。
group: GROUP BY 子語句。默認是空。
having: HAVING 子語句。默認是空。
8、關(guān)聯(lián)查詢命名空間
關(guān)聯(lián)查詢也可以和 命名空間一起執(zhí)行。有兩種形式。第一種形式,命名空間被應用到主模型。第二種形式,命名空間被應用到關(guān)聯(lián)模型。
下面的代碼展示了如何應用命名空間到主模型。
$posts=Post::model()->published()->recently()->with('comments')->findAll();
這非常類似于非關(guān)聯(lián)的查詢。唯一的不同是我們在命名空間后使用了 with() 調(diào)用。 此查詢應當返回最近發(fā)布的 post和它們的評論。
下面的代碼展示了如何應用命名空間到關(guān)聯(lián)模型。
$posts=Post::model()->with('comments:recently:approved')->findAll();
上面的查詢將返回所有的 post 及它們審核后的評論。注意 comments 指的是關(guān)聯(lián)名字,而recently 和 approved 指的是 在 Comment 模型類中聲明的命名空間。關(guān)聯(lián)名字和命名空間應當由冒號分隔。
命名空間也可以在 CActiveRecord::relations() 中聲明的關(guān)聯(lián)規(guī)則的 with 選項中指定。在下面的例子中, 若我們訪問 $user->posts,它將返回此post 的所有審核后的評論。
class User extends CActiveRecord
{
public function relations()
{
return array(
'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
'with'=>'comments:approved'),
);
}
}
注意: 應用到關(guān)聯(lián)模型的命名空間必須在 CActiveRecord::scopes 中指定。結(jié)果,它們不能被參數(shù)化。