Yii快速入門(mén)(三)數(shù)據(jù)庫(kù)操作

Yii提供了強(qiáng)大的數(shù)據(jù)庫(kù)編程支持吼蚁。Yii數(shù)據(jù)訪問(wèn)對(duì)象(DAO)建立在PHP的數(shù)據(jù)對(duì)象(PDO)extension上游昼,使得在一個(gè)單一的統(tǒng)一的接口可以訪問(wèn)不同的數(shù)據(jù)庫(kù)管理系統(tǒng)(DBMS)姑尺。使用Yii的DAO開(kāi)發(fā)的應(yīng)用程序可以很容易地切換使用不同的數(shù)據(jù)庫(kù)管理系統(tǒng)场仲,而不需要修改數(shù)據(jù)訪問(wèn)代碼矛紫。Yii 的Active Record( AR )赞哗,實(shí)現(xiàn)了被廣泛采用的對(duì)象關(guān)系映射(ORM)辦法,進(jìn)一步簡(jiǎn)化數(shù)據(jù)庫(kù)編程郎楼。按照約定万伤,一個(gè)類代表一個(gè)表,一個(gè)實(shí)例代表一行數(shù)據(jù)呜袁。Yii AR消除了大部分用于處理CRUD(創(chuàng)建敌买,讀取,更新和刪除)數(shù)據(jù)操作的sql語(yǔ)句的重復(fù)任務(wù)阶界。
盡管Yii的DAO和AR能夠處理幾乎所有數(shù)據(jù)庫(kù)相關(guān)的任務(wù)虹钮,您仍然可以在Yii application中使用自己的數(shù)據(jù)庫(kù)。事實(shí)上膘融,Yii框架精心設(shè)計(jì)使得可以與其他第三方庫(kù)同時(shí)使用芙粱。

一、數(shù)據(jù)訪問(wèn)對(duì)象 (DAO)

Yii DAO 基于 PHP Data Objects (PDO) 構(gòu)建氧映。它是一個(gè)為眾多流行的DBMS提供統(tǒng)一數(shù)據(jù)訪問(wèn)的擴(kuò)展春畔,這些 DBMS 包括 MySQL, PostgreSQL 等等岛都。因此律姨,要使用 Yii DAO,PDO 擴(kuò)展和特定的 PDO 數(shù)據(jù)庫(kù)驅(qū)動(dòng)(例如 PDO_MYSQL) 必須安裝臼疫。
Yii DAO 主要包含如下四個(gè)類:

CDbConnection: 代表一個(gè)數(shù)據(jù)庫(kù)連接择份。
CDbCommand: 代表一條通過(guò)數(shù)據(jù)庫(kù)執(zhí)行的 SQL 語(yǔ)句。
CDbDataReader: 代表一個(gè)只向前移動(dòng)的烫堤,來(lái)自一個(gè)查詢結(jié)果集中的行的流荣赶。
CDbTransaction: 代表一個(gè)數(shù)據(jù)庫(kù)事務(wù)。

1塔逃、建立數(shù)據(jù)庫(kù)連接

要建立一個(gè)數(shù)據(jù)庫(kù)連接讯壶,創(chuàng)建一個(gè) CDbConnection 實(shí)例并將其激活。連接到數(shù)據(jù)庫(kù)需要一個(gè)數(shù)據(jù)源的名字(DSN)以指定連接信息湾盗。用戶名和密碼也可能會(huì)用到伏蚊。當(dāng)連接到數(shù)據(jù)庫(kù)的過(guò)程中發(fā)生錯(cuò)誤時(shí) (例如,錯(cuò)誤的 DSN 或無(wú)效的用戶名/密碼)格粪,將會(huì)拋出一個(gè)異常躏吊。

$connection=new CDbConnection($dsn,$username,$password);
// 建立連接氛改。你可以使用  try...catch 捕獲可能拋出的異常
$connection->active=true;
......
$connection->active=false;  // 關(guān)閉連接

DSN 的格式取決于所使用的 PDO 數(shù)據(jù)庫(kù)驅(qū)動(dòng)”确總體來(lái)說(shuō)胜卤, DSN 要含有 PDO 驅(qū)動(dòng)的名字,跟上一個(gè)冒號(hào)赁项,再跟上驅(qū)動(dòng)特定的連接語(yǔ)法葛躏。可查閱 PDO 文檔 獲取更多信息悠菜。下面是一個(gè)常用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,我們也可以將其作為一個(gè) 應(yīng)用組件 使用悔醋。要這樣做的話摩窃,請(qǐng)?jiān)?應(yīng)用配置 中配置一個(gè) db (或其他名字)應(yīng)用組件如下:

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

然后我們就可以通過(guò)Yii::app()->db 訪問(wèn)數(shù)據(jù)庫(kù)連接了。它已經(jīng)被自動(dòng)激活了芬骄,除非我們特意配置了 CDbConnection::autoConnect 為 false猾愿。通過(guò)這種方式,這個(gè)單獨(dú)的DB連接就可以在我們代碼中的很多地方共享账阻。

2蒂秘、執(zhí)行SQL語(yǔ)句

數(shù)據(jù)庫(kù)連接建立后,SQL 語(yǔ)句就可以通過(guò)使用 CDbCommand 執(zhí)行了宰僧。你可以通過(guò)使用指定的SQL語(yǔ)句作為參數(shù)調(diào)用 CDbConnection::createCommand()創(chuàng)建一個(gè) CDbCommand 實(shí)例材彪。

$connection=Yii::app()->db;   // 假設(shè)你已經(jīng)建立了一個(gè) "db" 連接
// 如果沒(méi)有观挎,你可能需要顯式建立一個(gè)連接:
// $connection=new CDbConnection($dsn,$username,$password);
$command=$connection->createCommand($sql);
// 如果需要琴儿,此 SQL 語(yǔ)句可通過(guò)如下方式修改:
// $command->text=$newSQL;

一條 SQL 語(yǔ)句會(huì)通過(guò) CDbCommand 以如下兩種方式被執(zhí)行:
execute(): 執(zhí)行一個(gè)無(wú)查詢 (non-query)SQL語(yǔ)句,例如 INSERT, UPDATE 和 DELETE 嘁捷。如果成功造成,它將返回此執(zhí)行所影響的行數(shù)。
query(): 執(zhí)行一條會(huì)返回若干行數(shù)據(jù)的 SQL 語(yǔ)句雄嚣,例如 SELECT晒屎。如果成功,它將返回一個(gè) CDbDataReader 實(shí)例缓升,通過(guò)此實(shí)例可以遍歷數(shù)據(jù)的結(jié)果行鼓鲁。為簡(jiǎn)便起見(jiàn),(Yii)還實(shí)現(xiàn)了一系列 queryXXX() 方法以直接返回查詢結(jié)果港谊。
執(zhí)行 SQL 語(yǔ)句時(shí)如果發(fā)生錯(cuò)誤骇吭,將會(huì)拋出一個(gè)異常。

$rowCount=$command->execute();   // 執(zhí)行無(wú)查詢SQL
$dataReader=$command->query();   // 執(zhí)行一個(gè)SQL查詢
$rows=$command->queryAll();      // 查詢并返回結(jié)果中的所有行
$row=$command->queryRow();       // 查詢并返回結(jié)果中的第一行
$column=$command->queryColumn(); // 查詢并返回結(jié)果中的第一列
$value=$command->queryScalar();  // 查詢并返回結(jié)果中第一行的第一個(gè)字段

3歧寺、獲取查詢結(jié)果

CDbCommand::query() 生成 CDbDataReader 實(shí)例之后燥狰,你可以通過(guò)重復(fù)調(diào)用CDbDataReader::read() 獲取結(jié)果中的行棘脐。你也可以在 PHP 的 foreach 語(yǔ)言結(jié)構(gòu)中使用 CDbDataReader 一行行檢索數(shù)據(jù)。

$dataReader=$command->query();
// 重復(fù)調(diào)用 read() 直到它返回 false
while(($row=$dataReader->read())!==false) { ... }
// 使用 foreach 遍歷數(shù)據(jù)中的每一行
foreach($dataReader as $row) { ... }
// 一次性提取所有行到一個(gè)數(shù)組
$rows=$dataReader->readAll();

注意: 不同于query(), 所有的queryXXX()方法會(huì)直接返回?cái)?shù)據(jù)龙致。例如蛀缝,queryRow()會(huì)返回代表查詢結(jié)果第一行的一個(gè)數(shù)組。

4目代、使用事務(wù)

事務(wù)屈梁,在 Yii 中表現(xiàn)為 CDbTransaction 實(shí)例,可能會(huì)在下面的情況中啟動(dòng):
* 開(kāi)始事務(wù).
* 一個(gè)個(gè)執(zhí)行查詢榛了。任何對(duì)數(shù)據(jù)庫(kù)的更新對(duì)外界不可見(jiàn)俘闯。
* 提交事務(wù)。如果事務(wù)成功忽冻,更新變?yōu)榭梢?jiàn)真朗。
* 如果查詢中的一個(gè)失敗,整個(gè)事務(wù)回滾僧诚。
上述工作流可以通過(guò)如下代碼實(shí)現(xiàn):

$transaction=$connection->beginTransaction();
try
{
    $connection->createCommand($sql1)->execute();
    $connection->createCommand($sql2)->execute();
    //.... other SQL executions
    $transaction->commit();
}
catch(Exception $e) // 如果有一條查詢失敗遮婶,則會(huì)拋出異常
{
    $transaction->rollBack();
}

5、綁定參數(shù)

要避免 SQL 注入攻擊 并提高重復(fù)執(zhí)行的 SQL 語(yǔ)句的效率湖笨,你可以 "準(zhǔn)備(prepare)"一條含有可選參數(shù)占位符的 SQL 語(yǔ)句旗扑,在參數(shù)綁定時(shí),這些占位符將被替換為實(shí)際的參數(shù)慈省。
參數(shù)占位符可以是命名的 (表現(xiàn)為一個(gè)唯一的標(biāo)記) 或未命名的 (表現(xiàn)為一個(gè)問(wèn)號(hào))瓮恭。調(diào)用 CDbCommand::bindParam()CDbCommand::bindValue() 以使用實(shí)際參數(shù)替換這些占位符疚膊。這些參數(shù)不需要使用引號(hào)引起來(lái):底層的數(shù)據(jù)庫(kù)驅(qū)動(dòng)會(huì)為你搞定這個(gè)。參數(shù)綁定必須在 SQL 語(yǔ)句執(zhí)行之前完成。

// 一條帶有兩個(gè)占位符 ":username" 和 ":email"的 SQL
$sql="INSERT INTO tbl_user (username, email) VALUES(:username,:email)";
$command=$connection->createCommand($sql);
// 用實(shí)際的用戶名替換占位符 ":username"
$command->bindParam(":username",$username,PDO::PARAM_STR);
// 用實(shí)際的 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ū)別就是前者使用一個(gè)PHP變量綁定參數(shù)秀睛,而后者使用一個(gè)值程储。對(duì)于那些內(nèi)存中的大數(shù)據(jù)塊參數(shù)瓤檐,處于性能的考慮,應(yīng)優(yōu)先使用前者排截。

6嫌蚤、綁定列

當(dāng)獲取查詢結(jié)果時(shí),你也可以使用PHP變量綁定列断傲。這樣在每次獲取查詢結(jié)果中的一行時(shí)就會(huì)自動(dòng)使用最新的值填充脱吱。

$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 含有當(dāng)前行中的 username 和 email
}

7、使用表前綴

要使用表前綴认罩,配置 CDbConnection::tablePrefix 屬性為所希望的表前綴箱蝠。然后,在 SQL 語(yǔ)句中使用 {{TableName}} 代表表的名字,其中的 TableName 是指不帶前綴的表名抡锈。例如疾忍,如果數(shù)據(jù)庫(kù)含有一個(gè)名為 tbl_user 的表,而 tbl_ 被配置為表前綴床三,那我們就可以使用如下代碼執(zhí)行用戶相關(guān)的查詢:

$sql='SELECT * FROM {{user}}';
$users=$connection->createCommand($sql)->queryAll();

二一罩、Active Record

雖然Yii DAO可以處理幾乎任何數(shù)據(jù)庫(kù)相關(guān)的任務(wù),但很可能我們會(huì)花費(fèi) 90% 的時(shí)間以編寫(xiě)一些執(zhí)行普通 CRUD(create, read, update 和 delete)操作的SQL語(yǔ)句撇簿。而且我們的代碼中混雜了SQL語(yǔ)句時(shí)也會(huì)變得難以維護(hù)聂渊。要解決這些問(wèn)題,我們可以使用Active Record四瘫。
Active Record(AR)是一個(gè)流行的對(duì)象-關(guān)系映射(ORM)技術(shù)汉嗽。每個(gè) AR 類代表一個(gè)數(shù)據(jù)表(或視圖),數(shù)據(jù)表(或視圖)的列在 AR 類中體現(xiàn)為類的屬性找蜜,一個(gè)AR實(shí)例則表示表中的一行饼暑。常見(jiàn)的 CRUD 操作作為 AR 的方法實(shí)現(xiàn)。因此洗做,我們可以以一種更加面向?qū)ο蟮姆绞皆L問(wèn)數(shù)據(jù)弓叛。例如,我們可以使用以下代碼向tbl_post表中插入一個(gè)新行诚纸。

$post=new Post;
$post->title='sample post';
$post->content='post body content';
$post->save();

注意: AR并非要解決所有數(shù)據(jù)庫(kù)相關(guān)的任務(wù)撰筷。它的最佳應(yīng)用是模型化數(shù)據(jù)表為PHP結(jié)構(gòu)和執(zhí)行不包含復(fù)雜SQL語(yǔ)句的查詢。 對(duì)于復(fù)雜查詢的場(chǎng)景畦徘,應(yīng)使用Yii DAO毕籽。

1、建立數(shù)據(jù)庫(kù)連接

AR依靠一個(gè)數(shù)據(jù)庫(kù)連接以執(zhí)行數(shù)據(jù)庫(kù)相關(guān)的操作井辆。默認(rèn)情況下关筒,它假定db應(yīng)用組件提供了所需的CDbConnection數(shù)據(jù)庫(kù)連接實(shí)例。如下應(yīng)用配置提供了一個(gè)例子:

return array(
    'components'=>array(
        'db'=>array(
            'class'=>'system.db.CDbConnection',
            'connectionString'=>'sqlite:path/to/dbfile',
            // 開(kāi)啟表結(jié)構(gòu)緩存(schema caching)提高性能
            // 'schemaCachingDuration'=>3600,
        ),
    ),
);

提示: 由于Active Record依靠表的元數(shù)據(jù)(metadata)測(cè)定列的信息掘剪,讀取元數(shù)據(jù)并解析需要時(shí)間平委。 如果你數(shù)據(jù)庫(kù)的表結(jié)構(gòu)很少改動(dòng)奈虾,你應(yīng)該通過(guò)配置CDbConnection::schemaCachingDuration屬性的值為一個(gè)大于零的值開(kāi)啟表結(jié)構(gòu)緩存夺谁。
如果你想使用一個(gè)不是db的應(yīng)用組件,或者如果你想使用AR處理多個(gè)數(shù)據(jù)庫(kù)肉微,你應(yīng)該覆蓋CActiveRecord::getDbConnection() 匾鸥。CActiveRecord類是所有AR類的基類。
提示: 通過(guò)AR使用多個(gè)數(shù)據(jù)庫(kù)有兩種方式碉纳。如果數(shù)據(jù)庫(kù)的結(jié)構(gòu)不同勿负,你可以創(chuàng)建不同的AR基類實(shí)現(xiàn)不同的getDbConnection()。否則,動(dòng)態(tài)改變靜態(tài)變量CActiveRecord::db是一個(gè)好主意奴愉。

2琅摩、定義AR類

要訪問(wèn)一個(gè)數(shù)據(jù)表,我們首先需要通過(guò)集成CActiveRecord定義一個(gè)AR類锭硼。每個(gè)AR類代表一個(gè)單獨(dú)的數(shù)據(jù)表房资,一個(gè)AR實(shí)例則代表那個(gè)表中的一行。
如下例子演示了代表tbl_post表的AR類的最簡(jiǎn)代碼:

class Post extends CActiveRecord
{
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
 
    public function tableName()
    {
        return 'tbl_post';
    }
}

提示: 由于 AR 類經(jīng)常在多處被引用檀头,我們可以導(dǎo)入包含 AR 類的整個(gè)目錄轰异,而不是一個(gè)個(gè)導(dǎo)入。 例如暑始,如果我們所有的 AR 類文件都在 protected/models 目錄中搭独,我們可以配置應(yīng)用如下:

 return array(
      'import'=>array(
          'application.models.*',
      ),
    );

默認(rèn)情況下,AR類的名字和數(shù)據(jù)表的名字相同廊镜。如果不同牙肝,請(qǐng)覆蓋tableName()方法。
要使用表前綴功能嗤朴,AR類的 tableName() 方法可以通過(guò)如下方式覆蓋

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

這就是說(shuō)惊奇,我們將沒(méi)有前綴的表名用雙大括號(hào)括起來(lái),這樣Yii就能自動(dòng)添加前綴播赁,從而返回完整的表名颂郎。
數(shù)據(jù)表行中列的值可以作為相應(yīng)AR實(shí)例的屬性訪問(wèn)。例如容为,如下代碼設(shè)置了 title 列 (屬性):

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

雖然我們從未在Post類中顯式定義屬性title乓序,我們還是可以通過(guò)上述代碼訪問(wèn)。這是因?yàn)閠itle是tbl_post表中的一個(gè)列坎背,CActiveRecord通過(guò)PHP的__get()魔術(shù)方法使其成為一個(gè)可訪問(wèn)的屬性替劈。如果我們嘗試以同樣的方式訪問(wèn)一個(gè)不存在的列,將會(huì)拋出一個(gè)異常得滤。
如果一個(gè)表沒(méi)有主鍵陨献,則必須在相應(yīng)的AR類中通過(guò)如下方式覆蓋 primaryKey() 方法指定哪一列或哪幾列作為主鍵。

public function primaryKey()
{
    return 'id';
    // 對(duì)于復(fù)合主鍵懂更,要返回一個(gè)類似如下的數(shù)組
    // return array('pk1', 'pk2');
}

3眨业、創(chuàng)建記錄

要向數(shù)據(jù)表中插入新行,我們要?jiǎng)?chuàng)建一個(gè)相應(yīng) AR 類的實(shí)例沮协,設(shè)置其與表的列相關(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實(shí)例將包含一個(gè)更新的主鍵聘殖。在上面的例子中,id屬性將反映出新插入帖子的主鍵值,即使我們從未顯式地改變它奸腺。
如果一個(gè)列在表結(jié)構(gòu)中使用了靜態(tài)默認(rèn)值(例如一個(gè)字符串餐禁,一個(gè)數(shù)字)定義。則AR實(shí)例中相應(yīng)的屬性將在此實(shí)例創(chuàng)建時(shí)自動(dòng)含有此默認(rèn)值突照。改變此默認(rèn)值的一個(gè)方式就是在AR類中顯示定義此屬性:

class Post extends CActiveRecord
{
    public $title='please enter a title';
    ......
}
$post=new Post;
echo $post->title;  // 這兒將顯示: please enter a title

記錄在保存(插入或更新)到數(shù)據(jù)庫(kù)之前坠宴,其屬性可以賦值為 CDbExpression 類型。例如绷旗,為保存一個(gè)由MySQL的 NOW() 函數(shù)返回的時(shí)間戳喜鼓,我們可以使用如下代碼:

$post=new Post;
$post->create_time=new CDbexpression_r('NOW()'); //CDbExpression類就是計(jì)算數(shù)據(jù)庫(kù)表達(dá)式的值
// $post->create_time='NOW()'; 不會(huì)起作用,因?yàn)?// 'NOW()' 將會(huì)被作為一個(gè)字符串處理衔肢。
$post->save();

提示: 由于AR允許我們無(wú)需寫(xiě)一大堆SQL語(yǔ)句就能執(zhí)行數(shù)據(jù)庫(kù)操作庄岖, 我們經(jīng)常會(huì)想知道AR在背后到底執(zhí)行了什么SQL語(yǔ)句。這可以通過(guò)開(kāi)啟Yii的日志功能實(shí)現(xiàn)角骤。例如隅忿,我們?cè)趹?yīng)用配置中開(kāi)啟了CWebLogRoute,我們將會(huì)在每個(gè)網(wǎng)頁(yè)的最后看到執(zhí)行過(guò)的SQL語(yǔ)句邦尊。 我們也可以在應(yīng)用配置中設(shè)置CDbConnection::enableParamLogging為true背桐,這樣綁定在SQL語(yǔ)句中的參數(shù)值也會(huì)被記錄。

4蝉揍、讀取記錄

要讀取數(shù)據(jù)表中的數(shù)據(jù)链峭,我們可以通過(guò)如下方式調(diào)用 find 系列方法中的一種:

// 查找滿足指定條件的結(jié)果中的第一行
$post=Post::model()->find($condition,$params);
// 查找具有指定主鍵值的那一行
$post=Post::model()->findByPk($postID,$condition,$params);
// 查找具有指定屬性值的行
$post=Post::model()->findByAttributes($attributes,$condition,$params);
// 通過(guò)指定的SQL語(yǔ)句查找結(jié)果中的第一行
$post=Post::model()->findBySql($sql,$params);

如上所示,我們通過(guò) Post::model() 調(diào)用find 方法又沾。請(qǐng)記住弊仪,靜態(tài)方法 model() 是每個(gè)AR類所必須的。此方法返回在對(duì)象上下文中的一個(gè)用于訪問(wèn)類級(jí)別方法(類似于靜態(tài)類方法的東西)的AR實(shí)例杖刷。
如果find方法找到了一個(gè)滿足查詢條件的行励饵,它將返回一個(gè)Post實(shí)例,實(shí)例的屬性含有數(shù)據(jù)表行中相應(yīng)列的值滑燃。然后我們就可以像讀取普通對(duì)象的屬性那樣讀取載入的值役听,例如 echo $post->title;
如果使用給定的查詢條件在數(shù)據(jù)庫(kù)中沒(méi)有找到任何東西表窘, find 方法將返回null典予。
調(diào)用find時(shí),我們使用 $condition$params 指定查詢條件蚊丐。此處 $condition 可以是 SQL 語(yǔ)句中的 WHERE 字符串熙参,$params 則是一個(gè)參數(shù)數(shù)組,其中的值應(yīng)綁定到 $condition 中的占位符麦备。例如:

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

注意: 在上面的例子中,我們可能需要在特定的 DBMS 中將 postID 列的引用進(jìn)行轉(zhuǎn)義。
例如凛篙,如果我們使用 PostgreSQL黍匾,我們必須將此表達(dá)式寫(xiě)為 "postID"=:postID,因?yàn)?PostgreSQL 在默認(rèn)情況下對(duì)列名大小寫(xiě)不敏感呛梆。
我們也可以使用 $condition 指定更復(fù)雜的查詢條件锐涯。不使用字符串,我們可以讓 $condition 成為一個(gè) CDbCriteria 的實(shí)例填物,它允許我們指定不限于 WHERE 的條件纹腌。例如:

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

注意,當(dāng)使用 CDbCriteria 作為查詢條件時(shí)滞磺,$params 參數(shù)不再需要了升薯,因?yàn)樗梢栽?CDbCriteria 中指定,就像上面那樣击困。
一種替代 CDbCriteria 的方法是給 find 方法傳遞一個(gè)數(shù)組涎劈。數(shù)組的鍵和值各自對(duì)應(yīng)標(biāo)準(zhǔn)(criterion)的屬性名和值,上面的例子可以重寫(xiě)為如下:

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

當(dāng)一個(gè)查詢條件是關(guān)于按指定的值匹配幾個(gè)列時(shí)阅茶,我們可以使用 findByAttributes()蛛枚。我們使 $attributes 參數(shù)是一個(gè)以列名做索引的值的數(shù)組。在一些框架中脸哀,此任務(wù)可以通過(guò)調(diào)用類似 findByNameAndTitle 的方法實(shí)現(xiàn)蹦浦。雖然此方法看起來(lái)很誘人, 但它常常引起混淆撞蜂,沖突和比如列名大小寫(xiě)敏感的問(wèn)題白筹。
當(dāng)有多行數(shù)據(jù)匹配指定的查詢條件時(shí),我們可以通過(guò)下面的 findAll 方法將他們?nèi)繋Щ亓律恪C總€(gè)都有其各自的 find 方法徒河,就像我們已經(jīng)講過(guò)的那樣。

// 查找滿足指定條件的所有行
$posts=Post::model()->findAll($condition,$params);
// 查找?guī)в兄付ㄖ麈I的所有行
$posts=Post::model()->findAllByPk($postIDs,$condition,$params);
// 查找?guī)в兄付▽傩灾档乃行?$posts=Post::model()->findAllByAttributes($attributes,$condition,$params);
// 通過(guò)指定的SQL語(yǔ)句查找所有行
$posts=Post::model()->findAllBySql($sql,$params);

如果沒(méi)有任何東西符合查詢條件送漠,findAll 將返回一個(gè)空數(shù)組顽照。
這跟 find 不同,find 會(huì)在沒(méi)有找到什么東西時(shí)返回 null闽寡。
除了上面講述的 find 和 findAll 方法代兵,為了方便,(Yii)還提供了如下方法:

// 獲取滿足指定條件的行數(shù)
$n=Post::model()->count($condition,$params);
// 通過(guò)指定的 SQL 獲取結(jié)果行數(shù)
$n=Post::model()->countBySql($sql,$params);
// 檢查是否至少有一行復(fù)合指定的條件
$exists=Post::model()->exists($condition,$params);

5爷狈、更新記錄

在 AR 實(shí)例填充了列的值之后植影,我們可以改變它們并把它們存回?cái)?shù)據(jù)表。

$post=Post::model()->findByPk(10);
$post->title='new post title';
$post->save(); // 將更改保存到數(shù)據(jù)庫(kù)

正如我們可以看到的涎永,我們使用同樣的 save() 方法執(zhí)行插入和更新操作思币。如果一個(gè) AR 實(shí)例是使用 new 操作符創(chuàng)建的鹿响,調(diào)用 save() 將會(huì)向數(shù)據(jù)表中插入一行新數(shù)據(jù);如果 AR 實(shí)例是某個(gè)findfindAll 方法的結(jié)果谷饿,調(diào)用 save() 將更新表中現(xiàn)有的行惶我。實(shí)際上,我們是使用 CActiveRecord::isNewRecord 說(shuō)明一個(gè) AR 實(shí)例是不是新的博投。
直接更新數(shù)據(jù)表中的一行或多行而不首先載入也是可行的绸贡。 AR 提供了如下方便的類級(jí)別方法實(shí)現(xiàn)此目的:

// 更新符合指定條件的行
Post::model()->updateAll($attributes,$condition,$params);
// 更新符合指定條件和主鍵的行
Post::model()->updateByPk($pk,$attributes,$condition,$params);
// 更新滿足指定條件的行的計(jì)數(shù)列
Post::model()->updateCounters($counters,$condition,$params);

在上面的代碼中, $attributes 是一個(gè)含有以 列名作索引的列值的數(shù)組毅哗; $counters是一個(gè)由列名索引的可增加的值的數(shù)組听怕;$condition$params在前面的段落中已有描述。

6虑绵、刪除記錄

如果一個(gè) AR 實(shí)例被一行數(shù)據(jù)填充,我們也可以刪除此行數(shù)據(jù)尿瞭。

$post=Post::model()->findByPk(10); // 假設(shè)有一個(gè)帖子,其 ID 為 10
$post->delete(); // 從數(shù)據(jù)表中刪除此行

注意蒸殿,刪除之后筷厘, AR 實(shí)例仍然不變,但數(shù)據(jù)表中相應(yīng)的行已經(jīng)沒(méi)了宏所。
使用下面的類級(jí)別代碼酥艳,可以無(wú)需首先加載行就可以刪除它。

// 刪除符合指定條件的行
Post::model()->deleteAll($condition,$params);
// 刪除符合指定條件和主鍵的行
Post::model()->deleteByPk($pk,$condition,$params);

7爬骤、數(shù)據(jù)驗(yàn)證

當(dāng)插入或更新一行時(shí)充石,我們常常需要檢查列的值是否符合相應(yīng)的規(guī)則。如果列的值是由最終用戶提供的霞玄,這一點(diǎn)就更加重要骤铃。總體來(lái)說(shuō)坷剧,我們永遠(yuǎn)不能相信任何來(lái)自客戶端的數(shù)據(jù)惰爬。
當(dāng)調(diào)用 save() 時(shí), AR 會(huì)自動(dòng)執(zhí)行數(shù)據(jù)驗(yàn)證惫企。驗(yàn)證是基于在 AR 類的 rules() 方法中指定的規(guī)則進(jìn)行的撕瞧。關(guān)于驗(yàn)證規(guī)則的更多詳情,請(qǐng)參考 聲明驗(yàn)證規(guī)則 一節(jié)狞尔。下面是保存記錄時(shí)所需的典型的工作流丛版。

if($post->save())
{
    // 數(shù)據(jù)有效且成功插入/更新
}
else
{
    // 數(shù)據(jù)無(wú)效,調(diào)用  getErrors() 提取錯(cuò)誤信息
}

當(dāng)要插入或更新的數(shù)據(jù)由最終用戶在一個(gè) HTML 表單中提交時(shí)偏序,我們需要將其賦給相應(yīng)的 AR 屬性页畦。我們可以通過(guò)類似如下的方式實(shí)現(xiàn):

$post->title=$_POST['title'];
$post->content=$_POST['content'];
$post->save();

如果有很多列,我們可以看到一個(gè)用于這種復(fù)制的很長(zhǎng)的列表研儒。這可以通過(guò)使用如下所示的 attributes 屬性簡(jiǎn)化操作豫缨。更多信息可以在 安全的特性賦值 一節(jié)和 創(chuàng)建動(dòng)作 一節(jié)找到独令。

// 假設(shè) $_POST['Post'] 是一個(gè)以列名索引列值為值的數(shù)組
$post->attributes=$_POST['Post'];
$post->save();

8、對(duì)比記錄

類似于表記錄州胳,AR實(shí)例由其主鍵值來(lái)識(shí)別记焊。因此逸月,要對(duì)比兩個(gè)AR實(shí)例栓撞,假設(shè)它們屬于相同的AR類, 我們只需要對(duì)比它們的主鍵值碗硬。然而,一個(gè)更簡(jiǎn)單的方式是調(diào)用 CActiveRecord::equals()瓤湘。
不同于AR在其他框架的執(zhí)行, Yii在其 AR 中支持多個(gè)主鍵. 一個(gè)復(fù)合主鍵由兩個(gè)或更多字段構(gòu)成。相應(yīng)地恩尾,主鍵值在Yii中表現(xiàn)為一個(gè)數(shù)組弛说。primaryKey屬性給出了一個(gè) AR 實(shí)例的主鍵值。

9翰意、自定義

CActiveRecord 提供了幾個(gè)占位符方法木人,它們可以在子類中被覆蓋以自定義其工作流。

beforeValidate 和 afterValidate:這兩個(gè)將在驗(yàn)證數(shù)據(jù)有效性之前和之后被調(diào)用冀偶。
beforeSave 和 afterSave: 這兩個(gè)將在保存 AR 實(shí)例之前和之后被調(diào)用醒第。
beforeDelete 和 afterDelete: 這兩個(gè)將在一個(gè) AR 實(shí)例被刪除之前和之后被調(diào)用。
afterConstruct: 這個(gè)將在每個(gè)使用 new 操作符創(chuàng)建 AR 實(shí)例后被調(diào)用进鸠。
beforeFind: 這個(gè)將在一個(gè) AR 查找器被用于執(zhí)行查詢(例如 find(), findAll())之前被調(diào)用稠曼。
afterFind: 這個(gè)將在每個(gè) AR 實(shí)例作為一個(gè)查詢結(jié)果創(chuàng)建時(shí)被調(diào)用。

10客年、使用AR處理事務(wù)

每個(gè) AR 實(shí)例都含有一個(gè)屬性名叫 dbConnection 霞幅,是一個(gè) CDbConnection 的實(shí)例,這樣我們可以在需要時(shí)配合 AR 使用由 Yii DAO 提供的 事務(wù) 功能:

$model=Post::model();
$transaction=$model->dbConnection->beginTransaction();
try
{
    // 查找和保存是可能由另一個(gè)請(qǐng)求干預(yù)的兩個(gè)步驟
    // 這樣我們使用一個(gè)事務(wù)以確保其一致性和完整性
    $post=$model->findByPk(10);
    $post->title='new post title';
    $post->save();
    $transaction->commit();
}
catch(Exception $e)
{
    $transaction->rollBack();
}

11量瓜、命名范圍

命名范圍(named scope)表示一個(gè)命名的(named)查詢規(guī)則司恳,它可以和其他命名范圍聯(lián)合使用并應(yīng)用于Active Record查詢。
命名范圍主要是在 CActiveRecord::scopes() 方法中以名字-規(guī)則對(duì)的方式聲明绍傲。如下代碼在Post模型類中聲明了兩個(gè)命名范圍, publishedrecently扔傅。

class Post extends CActiveRecord
{
    ......
    public function scopes()
    {
        return array(
            'published'=>array(
                'condition'=>'status=1',
            ),
            'recently'=>array(
                'order'=>'create_time DESC',
                'limit'=>5,
            ),
        );
    }
}

每個(gè)命名范圍聲明為一個(gè)可用于初始化 CDbCriteria 實(shí)例的數(shù)組。例如唧取,recently 命名范圍指定 order 屬性為 create_time DESC 铅鲤, limit 屬性為 5。他們翻譯為查詢規(guī)則后就會(huì)返回最近的5篇帖子枫弟。
命名范圍多用作 find 方法調(diào)用的修改器邢享。幾個(gè)命名范圍可以鏈到一起形成一個(gè)更有約束性的查詢結(jié)果集。例如淡诗,要找到最近發(fā)布的帖子骇塘,我們可以使用如下代碼:
$posts=Post::model()->published()->recently()->findAll();
總體來(lái)說(shuō)伊履,命名范圍必須出現(xiàn)在一個(gè) find 方法調(diào)用的左邊。它們中的每一個(gè)都提供一個(gè)查詢規(guī)則款违,并聯(lián)合到其他規(guī)則唐瀑,包括傳遞給 find 方法調(diào)用的那一個(gè)。最終結(jié)果就像給一個(gè)查詢添加了一系列過(guò)濾器插爹。
命名范圍也可用于 updatedelete 方法哄辣。例如,如下代碼將刪除所有最近發(fā)布的帖子:
Post::model()->published()->recently()->delete();
注意: 命名范圍只能用于類級(jí)別方法赠尾。也就是說(shuō)力穗,此方法必須使用 ClassName::model() 調(diào)用。

12气嫁、參數(shù)化的命名范圍

命名范圍可以參數(shù)化当窗。例如,我們想自定義 recently 命名范圍中指定的帖子數(shù)量寸宵,要實(shí)現(xiàn)此目的崖面,不是在CActiveRecord::scopes 方法中聲明命名范圍,而是需要定義一個(gè)名字和此命名范圍的名字相同的方法:

public function recently($limit=5)
{
    $this->getDbCriteria()->mergeWith(array(
        'order'=>'create_time DESC',
        'limit'=>$limit,
    ));
    return $this;
}

然后梯影,我們就可以使用如下語(yǔ)句獲取3條最近發(fā)布的帖子巫员。
$posts=Post::model()->published()->recently(3)->findAll();
上面的代碼中,如果我們沒(méi)有提供參數(shù) 3光酣,我們將默認(rèn)獲取 5 條最近發(fā)布的帖子疏遏。

13、默認(rèn)的命名范圍

模型類可以有一個(gè)默認(rèn)命名范圍救军,它將應(yīng)用于所有 (包括相關(guān)的那些) 關(guān)于此模型的查詢财异。例如,一個(gè)支持多種語(yǔ)言的網(wǎng)站可能只想顯示當(dāng)前用戶所指定的語(yǔ)言的內(nèi)容唱遭。因?yàn)榭赡軙?huì)有很多關(guān)于此網(wǎng)站內(nèi)容的查詢戳寸,我們可以定義一個(gè)默認(rèn)的命名范圍以解決此問(wèn)題。為實(shí)現(xiàn)此目的拷泽,我們覆蓋 CActiveRecord::defaultScope 方法如下:

class Content extends CActiveRecord
{
    public function defaultScope()
    {
        return array(
            'condition'=>"language='".Yii::app()->language."'",
        );
    }
}

現(xiàn)在疫鹊,如果下面的方法被調(diào)用,將會(huì)自動(dòng)使用上面定義的查詢規(guī)則:
$contents=Content::model()->findAll();
注意司致,默認(rèn)的命名范圍只會(huì)應(yīng)用于 SELECT 查詢拆吆。INSERT, UPDATEDELETE 查詢將被忽略。

三脂矫、Relational Active Record(關(guān)聯(lián)查詢)

我們已經(jīng)知道如何通過(guò)Active Record(AR)從單個(gè)數(shù)據(jù)表中取得數(shù)據(jù)了枣耀,在這一節(jié)中,我們將要介紹如何使用AR來(lái)連接關(guān)聯(lián)的數(shù)據(jù)表獲取數(shù)據(jù)庭再。
在使用關(guān)聯(lián)AR之前捞奕,首先要在數(shù)據(jù)庫(kù)中建立關(guān)聯(lián)的數(shù)據(jù)表之間的主鍵-外鍵關(guān)聯(lián)牺堰,AR需要通過(guò)分析數(shù)據(jù)庫(kù)中的定義數(shù)據(jù)表關(guān)聯(lián)的元信息,來(lái)決定如何連接數(shù)據(jù)颅围。

1伟葫、如何聲明關(guān)聯(lián)

在使用AR進(jìn)行關(guān)聯(lián)查詢之前,我們需要告訴AR各個(gè)AR類之間有怎樣的關(guān)聯(lián)院促。
AR類之間的關(guān)聯(lián)直接反映著數(shù)據(jù)庫(kù)中這個(gè)類所代表的數(shù)據(jù)表之間的關(guān)聯(lián)筏养。從關(guān)系數(shù)據(jù)庫(kù)的角度來(lái)說(shuō),兩個(gè)數(shù)據(jù)表A一疯,B之間可能的關(guān)聯(lián)有三種:一對(duì)多撼玄,一對(duì)一夺姑,多對(duì)多墩邀。而在AR中,關(guān)聯(lián)有以下四種:

BELONGS_TO: 如果數(shù)據(jù)表A和B的關(guān)系是一對(duì)多盏浙,那我們就說(shuō)B屬于A(B belongs to A)眉睹。
HAS_MANY: 如果數(shù)據(jù)表A和B的關(guān)系是多對(duì)一,那我們就說(shuō)B有多個(gè)A(B has many A)废膘。
HAS_ONE: 這是‘HAS_MANY’關(guān)系中的一個(gè)特例竹海,當(dāng)A最多有一個(gè)的時(shí)候,我們說(shuō)B有一個(gè)A (B has one A)丐黄。
MANY_MANY: 這個(gè)相當(dāng)于關(guān)系數(shù)據(jù)庫(kù)中的多對(duì)多關(guān)系斋配。因?yàn)榻^大多數(shù)關(guān)系數(shù)據(jù)庫(kù)并不直接支持多對(duì)多的關(guān)系,這時(shí)通常都需要一個(gè)單獨(dú)的關(guān)聯(lián)表灌闺,把多對(duì)多的關(guān)系分解為兩個(gè)一對(duì)多的關(guān)系艰争。用AR的方式去理解的話,我們可以認(rèn)為 MANY_MANY關(guān)系是由BELONGS_TO和HAS_MANY組成的桂对。

在AR中聲明關(guān)聯(lián)甩卓,是通過(guò)覆蓋(Override)父類CActiveRecord中的relations()方法來(lái)實(shí)現(xiàn)的。這個(gè)方法返回一個(gè)包含了關(guān)系定義的數(shù)組蕉斜,數(shù)組中的每一組鍵值代表一個(gè)關(guān)聯(lián):
'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', ...additional options)
這里的VarName是這個(gè)關(guān)聯(lián)的名稱逾柿;RelationType指定了這個(gè)關(guān)聯(lián)的類型,有四個(gè)常量代表了四種關(guān)聯(lián)的類型:
self::BELONGS_TO宅此,self::HAS_ONE机错,self::HAS_MANY和self::MANY_MANY; ClassName是這個(gè)關(guān)系關(guān)聯(lián)到的AR類的類名父腕;ForeignKey指定了這個(gè)關(guān)聯(lián)是通過(guò)哪個(gè)外鍵聯(lián)系起來(lái)的弱匪。后面的additional options可以加入一些額外的設(shè)置,后面會(huì)做介紹侣诵。
下面的代碼演示了如何定義UserPost之間的關(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'
                    ),
            );
    }
}

說(shuō)明: 有時(shí)外鍵可能由兩個(gè)或更多字段組成狱窘,在這里可以將多個(gè)字段名由逗號(hào)或空格分隔, 一并寫(xiě)在這里财搁。對(duì)于多對(duì)多的關(guān)系蘸炸,關(guān)聯(lián)表必須在外鍵中注明,例如在Post類的categories 關(guān)聯(lián)中尖奔,外鍵就需要寫(xiě)成PostCategory(postID, categoryID)搭儒。
在AR類中聲明關(guān)聯(lián)時(shí),每個(gè)關(guān)聯(lián)會(huì)作為一個(gè)屬性添加到AR類中提茁,屬性名就是關(guān)聯(lián)的名稱淹禾。在進(jìn)行關(guān)聯(lián)查詢時(shí),這些屬性就會(huì)被設(shè)置為關(guān)聯(lián)到的AR類的實(shí)例茴扁,例如在查詢?nèi)〉靡粋€(gè)Post實(shí)例時(shí)铃岔,它的$author屬性就是代表Post作者的一個(gè)User類的實(shí)例。

2峭火、關(guān)聯(lián)查詢

進(jìn)行關(guān)聯(lián)查詢最簡(jiǎn)單的方式就是訪問(wèn)一個(gè)關(guān)聯(lián)AR對(duì)象的某個(gè)關(guān)聯(lián)屬性毁习。如果這個(gè)屬性之前沒(méi)有被訪問(wèn)過(guò),這時(shí)就會(huì)啟動(dòng)一個(gè)關(guān)聯(lián)查詢卖丸,通過(guò)當(dāng)前AR對(duì)象的主鍵連接相關(guān)的表纺且,來(lái)取得關(guān)聯(lián)對(duì)象的值,然后將這些數(shù)據(jù)保存在對(duì)象的屬性中稍浆。這種方式叫做“延遲加載”载碌,也就是只有等到訪問(wèn)到某個(gè)屬性時(shí),才會(huì)真正到數(shù)據(jù)庫(kù)中把這些關(guān)聯(lián)的數(shù)據(jù)取出來(lái)衅枫。下面的例子描述了延遲加載的過(guò)程:

// 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)情況下嫁艇,如果沒(méi)有查詢到結(jié)果,其返回的值也不同:BELONGS_TOHAS_ONE 關(guān)聯(lián)为鳄,無(wú)結(jié)果時(shí)返回null; HAS_MANYMANY_MANY, 無(wú)結(jié)果時(shí)返回空數(shù)組裳仆。
延遲加載方法使用非常方便,但在某些情況下并不高效孤钦。例如歧斟,若我們要取得N個(gè)post的作者信息,使用延遲方法將執(zhí)行N次連接查詢偏形。此時(shí)我們應(yīng)當(dāng)使用所謂的急切加載方法静袖。
急切加載方法檢索主要的 AR 實(shí)例及其相關(guān)的 AR 實(shí)例. 這通過(guò)使用 with() 方法加上 findfindAll 方法完成。例如俊扭,
$posts=Post::model()->with('author')->findAll();
上面的代碼將返回一個(gè)由 Post 實(shí)例組成的數(shù)組. 不同于延遲加載方法队橙,每個(gè)Post 實(shí)例中的author 屬性在我們?cè)L問(wèn)此屬性之前已經(jīng)被關(guān)聯(lián)的 User 實(shí)例填充。不是為每個(gè)post 執(zhí)行一個(gè)連接查詢, 急切加載方法在一個(gè)單獨(dú)的連接查詢中取出所有的 post 以及它們的author!
我們可以在with()方法中指定多個(gè)關(guān)聯(lián)名字。例如, 下面的代碼將取回 posts 以及它們的作者和分類:
$posts=Post::model()->with('author','categories')->findAll();
我們也可以使用嵌套的急切加載捐康。不使用一個(gè)關(guān)聯(lián)名字列表, 我們將關(guān)聯(lián)名字以分層的方式傳遞到 with() 方法, 如下,

$posts=Post::model()->with(
    'author.profile',
    'author.posts',
    'categories')->findAll();

上面的代碼將取回所有的 posts 以及它們的作者和分類仇矾。它也將取出每個(gè)作者的profileposts.
急切加載也可以通過(guò)指定 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)查詢選項(xiàng)

之前我們提到額外的參數(shù)可以被指定在關(guān)聯(lián)聲明中解总。這些選項(xiàng)贮匕,指定為 name-value 對(duì),被用來(lái)定制關(guān)聯(lián)查詢花枫。它們被概述如下:

select: 為關(guān)聯(lián) AR 類查詢的字段列表刻盐。默認(rèn)是 '*', 意味著所有字段。
        查詢的字段名字可用別名表達(dá)式來(lái)消除歧義(例如:COUNT(??.name) AS nameCount)劳翰。
condition: WHERE 子語(yǔ)句敦锌。默認(rèn)為空。注意, 列要使用別名引用(例如:??.id=10)佳簸。
params: 被綁定到 SQL 語(yǔ)句的參數(shù). 應(yīng)當(dāng)為一個(gè)由 name-value 對(duì)組成的數(shù)組()乙墙。
on: ON 子語(yǔ)句. 這里指定的條件將使用 and 操作符被追加到連接條件中。
       此選項(xiàng)中的字段名應(yīng)被消除歧義溺蕉。此選項(xiàng)不適用于 MANY_MANY 關(guān)聯(lián)伶丐。
order: ORDER BY 子語(yǔ)句。默認(rèn)為空疯特。注意, 列要使用別名引用(例如:??.age DESC)。
with: 應(yīng)當(dāng)和此對(duì)象一同載入的子關(guān)聯(lián)對(duì)象列表. 注意, 不恰當(dāng)?shù)氖褂每赡軙?huì)形成一個(gè)無(wú)窮的關(guān)聯(lián)循環(huán)肛走。
joinType: 此關(guān)聯(lián)的連接類型漓雅。默認(rèn)是 LEFT OUTER JOIN。
aliasToken:列前綴占位符朽色。默認(rèn)是“??.”邻吞。
alias: 關(guān)聯(lián)的數(shù)據(jù)表的別名。默認(rèn)是 null, 意味著表的別名和關(guān)聯(lián)的名字相同葫男。
together: 是否關(guān)聯(lián)的數(shù)據(jù)表被強(qiáng)制與主表和其他表連接抱冷。此選項(xiàng)只對(duì)于HAS_MANY 和 MANY_MANY 關(guān)聯(lián)有意義。
          若此選項(xiàng)被設(shè)置為 false, ......(此處原文出錯(cuò)!).默認(rèn)為空梢褐。此選項(xiàng)中的字段名以被消除歧義旺遮。
having: HAVING 子語(yǔ)句。默認(rèn)是空盈咳。注意, 列要使用別名引用耿眉。
index: 返回的數(shù)組索引類型。確定返回的數(shù)組是關(guān)鍵字索引數(shù)組還是數(shù)字索引數(shù)組鱼响。
       不設(shè)置此選項(xiàng), 將使用數(shù)字索引數(shù)組鸣剪。此選項(xiàng)只對(duì)于HAS_MANY 和 MANY_MANY 有意義
       此外, 下面的選項(xiàng)在延遲加載中對(duì)特定關(guān)聯(lián)是可用的:
group: GROUP BY子句。默認(rèn)為空。注意, 列要使用別名引用(例如:??.age)筐骇。 
       本選項(xiàng)僅應(yīng)用于HAS_MANY 和 MANY_MANY 關(guān)聯(lián)债鸡。
having: HAVING子句。默認(rèn)為空铛纬。注意, 列要使用別名引用(例如:??.age)娘锁。
       本選項(xiàng)僅應(yīng)用于HAS_MANY 和 MANY_MANY 關(guān)聯(lián)。
limit: 限制查詢的行數(shù)饺鹃。本選項(xiàng)不能用于BELONGS_TO關(guān)聯(lián)莫秆。
offset: 偏移。本選項(xiàng)不能用于BELONGS_TO關(guān)聯(lián)悔详。

下面我們改變?cè)?User 中的 posts 關(guān)聯(lián)聲明,通過(guò)使用上面的一些選項(xiàng):

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)在若我們?cè)L問(wèn) $author->posts, 我們將得到用戶的根據(jù)發(fā)表時(shí)間降序排列的 posts. 每個(gè) post 實(shí)例也載入了它的分類镊屎。

4、為字段名消除歧義

當(dāng)一個(gè)字段的名字出現(xiàn)在被連接在一起的兩個(gè)或更多表中茄螃,需要消除歧義(disambiguated)缝驳。可以通過(guò)使用表的別名作為字段名的前綴實(shí)現(xiàn)归苍。
在關(guān)聯(lián)AR查詢中用狱,主表的別名確定為 t,而一個(gè)關(guān)聯(lián)表的別名和相應(yīng)的關(guān)聯(lián)的名字相同(默認(rèn)情況下)拼弃。 例如夏伊,在下面的語(yǔ)句中,Post 的別名是 t 吻氧,而 Comment 的別名是 comments:
$posts=Post::model()->with('comments')->findAll();
現(xiàn)在假設(shè) PostComment 都有一個(gè)字段 create_time , 我們希望取出 posts 及它們的 comments ,排序方式是先根據(jù) posts 的創(chuàng)建時(shí)間,然后根據(jù) comment 的創(chuàng)建時(shí)間溺忧。 我們需要消除create_time 字段的歧義,如下:

$posts=Post::model()->with('comments')->findAll(array(
    'order'=>'t.create_time, comments.create_time'
));

默認(rèn)情況下,Yii 自動(dòng)為每個(gè)關(guān)聯(lián)表產(chǎn)生一個(gè)表別名盯孙,我們必須使用此前綴 ??. 來(lái)指向這個(gè)自動(dòng)產(chǎn)生的別名鲁森。 主表的別名是表自身的名字。

5振惰、動(dòng)態(tài)關(guān)聯(lián)查詢選項(xiàng)

我們使用 with()with 均可使用動(dòng)態(tài)關(guān)聯(lián)查詢選項(xiàng)歌溉。 動(dòng)態(tài)選項(xiàng)將覆蓋在 relations() 方法中指定的
已存在的選項(xiàng)。例如骑晶,使用上面的 User 模型痛垛, 若我們想要使用急切加載方法以升序來(lái)取出屬于一個(gè)作者的 posts(關(guān)聯(lián)中的order 選項(xiàng)指定為降序), 我們可以這樣做:

User::model()->with(array(
    'posts'=>array('order'=>'posts.create_time ASC'),
    'profile',
))->findAll();

動(dòng)態(tài)查詢選項(xiàng)也可以在使用延遲加載方法時(shí)使用以執(zhí)行關(guān)聯(lián)查詢透罢。 要這樣做榜晦,我們應(yīng)當(dāng)調(diào)用一個(gè)方法,它的名字和關(guān)聯(lián)的名字相同羽圃,并傳遞動(dòng)態(tài)查詢選項(xiàng) 作為此方法的參數(shù)乾胶。例如抖剿,下面的代碼返回一個(gè)用戶的 status 為 1 的posts :

$user=User::model()->findByPk(1);
$posts=$user->posts(array('condition'=>'status=1'));

6、關(guān)聯(lián)查詢的性能

如上所述识窿,急切加載方法主要用于當(dāng)我們需要訪問(wèn)許多關(guān)聯(lián)對(duì)象時(shí)斩郎。 通過(guò)連接所有所需的表它產(chǎn)生一個(gè)大而復(fù)雜的 SQL 語(yǔ)句。一個(gè)大的 SQL 語(yǔ)句在許多情況下是首選的喻频。然而在一些情況下它并不高效缩宜。
考慮一個(gè)例子,若我們需要找出最新的文章以及它們的評(píng)論甥温。 假設(shè)每個(gè)文章有 10 條評(píng)論锻煌,使用一個(gè)大的 SQL 語(yǔ)句,我們將取回很多多余的 post 數(shù)據(jù), 因?yàn)槊總€(gè)post 將被它的每條評(píng)論反復(fù)使用。現(xiàn)在讓我們嘗試另外的方法:我們首先查詢最新的文章虐译, 然后查詢它們的評(píng)論。用新的方法捂龄,我們需要執(zhí)行執(zhí)行兩條 SQL 語(yǔ)句。有點(diǎn)是在查詢結(jié)果中沒(méi)有多余的數(shù)據(jù)加叁。
因此哪種方法更加高效倦沧?沒(méi)有絕對(duì)的答案。執(zhí)行一條大的 SQL 語(yǔ)句也許更加高效它匕,因?yàn)樗枰俚幕ㄤN(xiāo)來(lái)解析和執(zhí)行 SQL 語(yǔ)句展融。另一方面,使用單條 SQL 語(yǔ)句超凳,我們得到更多冗余的數(shù)據(jù)愈污,因此需要更多時(shí)間來(lái)閱讀和處理它們。 因?yàn)檫@個(gè)原因轮傍,Yii 提供了 together 查詢選項(xiàng)一邊我們?cè)谛枰獣r(shí)選擇兩種方法之一。默認(rèn)下首装, Yii 使用第一種方式创夜,即產(chǎn)生一個(gè)單獨(dú)的 SQL 語(yǔ)句來(lái)執(zhí)行急切加載。我們可以在關(guān)聯(lián)聲明中設(shè)置 together 選項(xiàng)為 false 以便一些表被連接在單獨(dú)的 SQL 語(yǔ)句中仙逻。例如驰吓,為了使用第二種方法來(lái)查詢最新的文章及它們的評(píng)論,我們可以在 Post 類中聲明 comments 關(guān)聯(lián)如下,

public function relations()
{
    return array(
        'comments' => array(self::HAS_MANY, 'Comment', 'post_id', 'together'=>false),
    );
}

當(dāng)我們執(zhí)行急切加載時(shí)系奉,我們也可以動(dòng)態(tài)地設(shè)置此選項(xiàng):
$posts = Post::model()->with(array('comments'=>array('together'=>false)))->findAll();

7檬贰、統(tǒng)計(jì)查詢

除了上面描述的關(guān)聯(lián)查詢,Yii 也支持所謂的統(tǒng)計(jì)查詢(或聚合查詢)缺亮。 它指的是檢索關(guān)聯(lián)對(duì)象的聚合信息翁涤,例如每個(gè) post 的評(píng)論的數(shù)量,每個(gè)產(chǎn)品的平均等級(jí)等。 統(tǒng)計(jì)查詢只被 HAS_MANY(例如葵礼,一個(gè) post 有很多評(píng)論) 或 MANY_MANY (例如号阿,一個(gè)post 屬于很多分類和一個(gè) category 有很多 post) 關(guān)聯(lián)對(duì)象執(zhí)行。
執(zhí)行統(tǒng)計(jì)查詢非常類似于之前描述的關(guān)聯(lián)查詢鸳粉。我們首先需要在 CActiveRecordrelations() 方法中聲明統(tǒng)計(jì)查詢扔涧。

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)'),
        );
    }
}

在上面,我們聲明了兩個(gè)統(tǒng)計(jì)查詢:commentCount 計(jì)算屬于一個(gè) post 的評(píng)論的數(shù)量届谈,categoryCount 計(jì)算一個(gè) post 所屬分類的數(shù)量枯夜。注意 PostComment 之間的關(guān)聯(lián)類型是 HAS_MANY, 而 PostCategory 之間的關(guān)聯(lián)類型是 MANY_MANY (使用連接表 PostCategory)艰山。 如我們所看到的湖雹,聲明非常類似于之間小節(jié)中的關(guān)聯(lián)。唯一的不同是這里的關(guān)聯(lián)類型是 STAT程剥。
有了上面的聲明劝枣,我們可以檢索使用表達(dá)式 $post->commentCount 檢索一個(gè) post 的評(píng)論的數(shù)量。 當(dāng)我們首次訪問(wèn)此屬性织鲸,一個(gè) SQL 語(yǔ)句將被隱含地執(zhí)行并檢索 對(duì)應(yīng)的結(jié)果舔腾。我們已經(jīng)知道,這是所謂的 lazy loading 方法搂擦。若我們需要得到多個(gè)post 的評(píng)論數(shù)目稳诚,我們也可以使用 eager loading 方法:
$posts=Post::model()->with('commentCount', 'categoryCount')->findAll();
上面的語(yǔ)句將執(zhí)行三個(gè) SQL 語(yǔ)句以取回所有的 post 及它們的評(píng)論數(shù)目和分類數(shù)目。使用延遲加載方法瀑踢, 若有 N 個(gè) post ,我們使用 2*N+1 條 SQL 查詢完成扳还。
默認(rèn)情況下,一個(gè)統(tǒng)計(jì)查詢將計(jì)算 COUNT 表達(dá)式(and thus the comment count and category count in the above example). 當(dāng)我們?cè)?relations()中聲明它時(shí)橱夭,通過(guò) 指定額外的選項(xiàng)氨距,可以定制它〖樱可用的選項(xiàng)簡(jiǎn)介如下俏让。

select:       統(tǒng)計(jì)表達(dá)式。默認(rèn)是 COUNT(*)茬暇,意味著子對(duì)象的個(gè)數(shù)首昔。
defaultValue: 沒(méi)有接收一個(gè)統(tǒng)計(jì)查詢結(jié)果時(shí)被賦予的值。例如糙俗,若一個(gè) post 沒(méi)有任何評(píng)論勒奇,
              它的 commentCount 將接收此值。此選項(xiàng)的默認(rèn)值是 0巧骚。
condition:    WHERE 子語(yǔ)句赊颠。默認(rèn)是空格二。
params:       被綁定到產(chǎn)生的SQL 語(yǔ)句中的參數(shù)。它應(yīng)當(dāng)是一個(gè) name-value 對(duì)組成的數(shù)組巨税。
order:        ORDER BY 子語(yǔ)句蟋定。默認(rèn)是空。
group:        GROUP BY 子語(yǔ)句草添。默認(rèn)是空驶兜。
having:       HAVING 子語(yǔ)句。默認(rèn)是空远寸。

8抄淑、關(guān)聯(lián)查詢命名空間

關(guān)聯(lián)查詢也可以和 命名空間一起執(zhí)行。有兩種形式驰后。第一種形式肆资,命名空間被應(yīng)用到主模型。第二種形式灶芝,命名空間被應(yīng)用到關(guān)聯(lián)模型郑原。
下面的代碼展示了如何應(yīng)用命名空間到主模型。
$posts=Post::model()->published()->recently()->with('comments')->findAll();
這非常類似于非關(guān)聯(lián)的查詢夜涕。唯一的不同是我們?cè)诿臻g后使用了 with() 調(diào)用犯犁。 此查詢應(yīng)當(dāng)返回最近發(fā)布的 post和它們的評(píng)論。
下面的代碼展示了如何應(yīng)用命名空間到關(guān)聯(lián)模型女器。
$posts=Post::model()->with('comments:recently:approved')->findAll();
上面的查詢將返回所有的 post 及它們審核后的評(píng)論酸役。注意 comments 指的是關(guān)聯(lián)名字,而recentlyapproved 指的是 在 Comment 模型類中聲明的命名空間驾胆。關(guān)聯(lián)名字和命名空間應(yīng)當(dāng)由冒號(hào)分隔涣澡。
命名空間也可以在 CActiveRecord::relations() 中聲明的關(guān)聯(lián)規(guī)則的 with 選項(xiàng)中指定。在下面的例子中丧诺, 若我們?cè)L問(wèn) $user->posts入桂,它將返回此post 的所有審核后的評(píng)論。

class User extends CActiveRecord
{
    public function relations()
    {
        return array(
            'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
                'with'=>'comments:approved'),
        );
    }
}

注意: 應(yīng)用到關(guān)聯(lián)模型的命名空間必須在 CActiveRecord::scopes 中指定驳阎。結(jié)果事格,它們不能被參數(shù)化。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末搞隐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子远搪,更是在濱河造成了極大的恐慌劣纲,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谁鳍,死亡現(xiàn)場(chǎng)離奇詭異癞季,居然都是意外死亡劫瞳,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)绷柒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)志于,“玉大人,你說(shuō)我怎么就攤上這事废睦∷耪溃” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵嗜湃,是天一觀的道長(zhǎng)奈应。 經(jīng)常有香客問(wèn)我,道長(zhǎng)购披,這世上最難降的妖魔是什么杖挣? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮刚陡,結(jié)果婚禮上惩妇,老公的妹妹穿的比我還像新娘。我一直安慰自己筐乳,他們只是感情好歌殃,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著哥童,像睡著了一般挺份。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贮懈,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天匀泊,我揣著相機(jī)與錄音,去河邊找鬼朵你。 笑死各聘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抡医。 我是一名探鬼主播躲因,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼忌傻!你這毒婦竟也來(lái)了大脉?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤水孩,失蹤者是張志新(化名)和其女友劉穎镰矿,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體俘种,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秤标,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年绝淡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苍姜。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡牢酵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出衙猪,到底是詐尸還是另有隱情馍乙,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布屈嗤,位于F島的核電站潘拨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏饶号。R本人自食惡果不足惜铁追,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茫船。 院中可真熱鬧琅束,春花似錦、人聲如沸算谈。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)然眼。三九已至艾船,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間高每,已是汗流浹背屿岂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鲸匿,地道東北人爷怀。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像带欢,于是被迫代替她去往敵國(guó)和親运授。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容