程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> PHP綜合 >> yii添刪改查實例

yii添刪改查實例

編輯:PHP綜合

一、數據訪問對象 (DAO)

Yii

DAO 基於 PHP Data Objects (PDO) 構建。它是一個為眾多流行的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 (或其他名字)應用組件如下:

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 以如下兩種方式被執行:

execute(): 執行一個無查詢 (non-query)SQL語句,例如 INSERT, UPDATE 和 DELETE 。如果成功,它將返回此執行所影響的行數。

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();
// 重復調用 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.*',
),
);

默認情況下,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  提供了幾個占位符方法,它們可以在子類中被覆蓋以自定義其工作流。

beforeva lidate 和 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 查詢將被忽略。

三、Relational Active Record(關聯查詢)

我們已經知道如何通過Active Record(AR)從單個數據表中取得數據了,在這一節中,我們將要介紹如何使用AR來連接關聯的數據表獲取數據。

在使用關聯AR之前,首先要在數據庫中建立關聯的數據表之間的主鍵-外鍵關聯,AR需要通過分析數據庫中的定義數據表關聯的元信息,來決定如何連接數據。

1、如何聲明關聯

在使用AR進行關聯查詢之前,我們需要告訴AR各個AR類之間有怎樣的關聯。

AR類之間的關聯直接反映著數據庫中這個類所代表的數據表之間的關聯。從關系數據庫的角度來說,兩個數據表A,B之間可能的關聯有三種:一對多,一對一,多對多。而在AR中,關聯有以下四種:

BELONGS_TO: 如果數據表A和B的關系是一對多,那我們就說B屬於A(B belongs to A)。

HAS_MANY: 如果數據表A和B的關系是多對一,那我們就說B有多個A(B has many A)。

HAS_ONE: 這是‘HAS_MANY'關系中的一個特例,當A最多有一個的時候,我們說B有一個A (B has one A)。

MANY_MANY:

這個相當於關系數據庫中的多對多關系。因為絕大多數關系數據庫並不直接支持多對多的關系,這時通常都需要一個單獨的關聯表,把多對多的關系分解為兩個一對

多的關系。用AR的方式去理解的話,我們可以認為 MANY_MANY關系是由BELONGS_TO和HAS_MANY組成的。

在AR中聲明關聯,是通過覆蓋(Override)父類CActiveRecord中的relations()方法來實現的。這個方法返回一個包含了關系定義的數組,數組中的每一組鍵值代表一個關聯:

'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', ...additional options)

這裡的VarName是這個關聯的名稱;RelationType指定了這個關聯的類型,有四個常量代表了四種關聯的類型:

self::BELONGS_TO,self::HAS_ONE,self::HAS_MANY和self::MANY_MANY;

ClassName是這個關系關聯到的AR類的類名;ForeignKey指定了這個關聯是通過哪個外鍵聯系起來的。後面的additional

options可以加入一些額外的設置,後面會做介紹。

下面的代碼演示了如何定義User和Post之間的關聯。

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

說明: 有時外鍵可能由兩個或更多字段組成,在這裡可以將多個字段名由逗號或空格分隔,

一並寫在這裡。對於多對多的關系,關聯表必須在外鍵中注明,例如在Post類的categories

關聯中,外鍵就需要寫成PostCategory(postID, categoryID)。

在AR類中聲明關聯時,每個關聯會作為一個屬性添加到AR類中,屬性名就是關聯的名稱。在進行關聯查詢時,這些屬性就會被設置為關聯到的AR類的實例,例如在查詢取得一個Post實例時,它的$author屬性就是代表Post作者的一個User類的實例。

2、關聯查詢

進行關聯查詢最簡單的方式就是訪問一個關聯AR對象的某個關聯屬性。如果這個屬性之前沒有被訪問過,這時就會啟動一個關聯查詢,通過當前AR對象的主鍵連接

相關的表,來取得關聯對象的值,然後將這些數據保存在對象的屬性中。這種方式叫做“延遲加載”,也就是只有等到訪問到某個屬性時,才會真正到數據庫中把這

些關聯的數據取出來。下面的例子描述了延遲加載的過程:

// 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;

在不同的關聯情況下,如果沒有查詢到結果,其返回的值也不同:BELONGS_TO 和 HAS_ONE 關聯,無結果時返回null; HAS_MANY 和 MANY_MANY, 無結果時返回空數組。

延遲加載方法使用非常方便,但在某些情況下並不高效。例如,若我們要取得N個post的作者信息,使用延遲方法將執行N次連接查詢。此時我們應當使用所謂的急切加載方法。

急切加載方法檢索主要的 AR 實例及其相關的 AR 實例. 這通過使用 with() 方法加上 find 或 findAll 方法完

成。例如,

$posts=Post::model()->with('author')->findAll();

上面的代碼將返回一個由 Post 實例組成的數組. 不同於延遲加載方法,每個Post 實例中的author 屬性在我們訪問此屬性之前已經被關聯的

User 實例填充。不是為每個post 執行一個連接查詢, 急切加載方法在一個單獨的連接查詢中取出所有的 post 以及它們的author!

我們可以在with()方法中指定多個關聯名字。例如, 下面的代碼將取回 posts 以及它們的作者和分類:

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

我們也可以使用嵌套的急切加載。不使用一個關聯名字列表, 我們將關聯名字以分層的方式傳遞到 with() 方法, 如下,

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

上面的代碼將取回所有的 posts 以及它們的作者和分類。它也將取出每個作者的profile和 posts.

急切加載也可以通過指定 CDbCriteria::with 屬性被執行, 如下:

$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、關聯查詢選項

之前我們提到額外的參數可以被指定在關聯聲明中。這些選項,指定為 name-value 對,被用來定制關聯查詢。它們被概述如下:

select: 為關聯 AR 類查詢的字段列表。默認是 '*', 意味著所有字段。查詢的字段名字可用別名表達式來消除歧義(例如:COUNT(??.name) AS nameCount)。

condition: WHERE 子語句。默認為空。注意, 列要使用別名引用(例如:??.id=10)。

params: 被綁定到 SQL 語句的參數. 應當為一個由 name-value 對組成的數組()。

on: ON 子語句. 這裡指定的條件將使用 and 操作符被追加到連接條件中。此選項中的字段名應被消除歧義。此選項不適用於 MANY_MANY 關聯。

order: ORDER BY 子語句。默認為空。注意, 列要使用別名引用(例如:??.age DESC)。

with: 應當和此對象一同載入的子關聯對象列表. 注意, 不恰當的使用可能會形成一個無窮的關聯循環。

joinType: 此關聯的連接類型。默認是 LEFT OUTER JOIN。

aliasToken:列前綴占位符。默認是“??.”。

alias: 關聯的數據表的別名。默認是 null, 意味著表的別名和關聯的名字相同。

together: 是否關聯的數據表被強制與主表和其他表連接。此選項只對於HAS_MANY 和 MANY_MANY 關聯有意義。若此選項被設置為 false, ......(此處原文出錯!).默認為空。此選項中的字段名以被消除歧義。

having: HAVING 子語句。默認是空。注意, 列要使用別名引用。

index: 返回的數組索引類型。確定返回的數組是關鍵字索引數組還是數字索引數組。不設置此選項, 將使用數字索引數組。此選項只對於HAS_MANY 和 MANY_MANY 有意義

此外, 下面的選項在延遲加載中對特定關聯是可用的:

group: GROUP BY子句。默認為空。注意, 列要使用別名引用(例如:??.age)。 本選項僅應用於HAS_MANY 和 MANY_MANY 關聯。

having: HAVING子句。默認為空。注意, 列要使用別名引用(例如:??.age)。本選項僅應用於HAS_MANY 和 MANY_MANY 關聯。

limit: 限制查詢的行數。本選項不能用於BELONGS_TO關聯。

offset: 偏移。本選項不能用於BELONGS_TO關聯。

下面我們改變在 User 中的 posts 關聯聲明,通過使用上面的一些選項:

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

現在若我們訪問 $author->posts, 我們將得到用戶的根據發表時間降序排列的 posts. 每個post 實例也載入了它的分類。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved