yii添刪改查實例
一、數(shù)據(jù)訪問對象 (DAO)
Yii
DAO 基于 PHP Data Objects (PDO) 構(gòu)建。它是一個為眾多流行的DBMS提供統(tǒng)一數(shù)據(jù)訪問的擴(kuò)展,這些 DBMS 包括
MySQL, PostgreSQL 等等。因此,要使用 Yii DAO,PDO 擴(kuò)展和特定的 PDO 數(shù)據(jù)庫驅(qū)動(例如 PDO_MYSQL)
必須安裝。
Yii DAO 主要包含如下四個類:
CDbConnection: 代表一個數(shù)據(jù)庫連接。
CDbCommand: 代表一條通過數(shù)據(jù)庫執(zhí)行的 SQL 語句。
CDbDataReader: 代表一個只向前移動的,來自一個查詢結(jié)果集中的行的流。
CDbTransaction: 代表一個數(shù)據(jù)庫事務(wù)。
1、建立數(shù)據(jù)庫連接
要建立一個數(shù)據(jù)庫連接,創(chuàng)建一個 CDbConnection
實例并將其激活。連接到數(shù)據(jù)庫需要一個數(shù)據(jù)源的名字(DSN)以指定連接信息。用戶名和密碼也可能會用到。當(dāng)連接到數(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,我們也可以將其作為一個 應(yīng)用組件 使用。要這樣做的話,請在 應(yīng)用配置 中配置一個 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 ), ), )
然后我們就可以通過 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; // 假設(shè)你已經(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 實例之后,你可以通過重復(fù)調(diào)用
CDbDataReader::read() 獲取結(jié)果中的行。你也可以在 PHP 的 foreach 語言結(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) { ... } // 一次性提取所有行到一個數(shù)組 $rows=$dataReader->readAll();
注意: 不同于query(), 所有的queryXXX()方法會直接返回數(shù)據(jù)。例如,queryRow()會返回代表查詢結(jié)果第一行的一個數(shù)組。
4、使用事務(wù)
事務(wù),在 Yii 中表現(xiàn)為 CDbTransaction 實例,可能會在下面的情況中啟動:
* 開始事務(wù).
* 一個個執(zhí)行查詢。任何對數(shù)據(jù)庫的更新對外界不可見。
* 提交事務(wù)。如果事務(wù)成功,更新變?yōu)榭梢姟?/p>
* 如果查詢中的一個失敗,整個事務(wù)回滾。
上述工作流可以通過如下代碼實現(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 注入攻擊 并提高重復(fù)執(zhí)行的 SQL 語句的效率,你可以 "準(zhǔn)備(prepare)"一條含有可選參數(shù)占位符的 SQL 語句,在參數(shù)綁定時,這些占位符將被替換為實際的參數(shù)。
參數(shù)占位符可以是命名的 (表現(xiàn)為一個唯一的標(biāo)記) 或未命名的 (表現(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īng)優(yōu)先使用前者。
6、綁定列
當(dāng)獲取查詢結(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 含有當(dāng)前行中的 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)的任務(wù),但很可能我們會花費 90% 的時間以編寫一些執(zhí)行普通 CRUD(create, read,
update 和 delete)操作的SQL語句。而且我們的代碼中混雜了SQL語句時也會變得難以維護(hù)。要解決這些問題,我們可以使用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)的任務(wù)。它的最佳應(yīng)用是模型化數(shù)據(jù)表為PHP結(jié)構(gòu)和執(zhí)行不包含復(fù)雜SQL語句的查詢。 對于復(fù)雜查詢的場景,應(yīng)使用Yii DAO。
1、建立數(shù)據(jù)庫連接
AR依靠一個數(shù)據(jù)庫連接以執(zhí)行數(shù)據(jù)庫相關(guān)的操作。默認(rèn)情況下,它假定db應(yīng)用組件提供了所需的CDbConnection數(shù)據(jù)庫連接實例。如下應(yīng)用配置提供了一個例子:
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)很少改動,你應(yīng)該通過配置CDbConnection::schemaCachingDuration屬性的值為一個大于零的值開啟表結(jié)構(gòu)緩存。
如果你想使用一個不是db的應(yīng)用組件,或者如果你想使用AR處理多個數(shù)據(jù)庫,你應(yīng)該覆蓋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)常在多處被引用,我們可以導(dǎo)入包含 AR 類的整個目錄,而不是一個個導(dǎo)入。 例如,如果我們所有的 AR 類文件都在 protected/models 目錄中,我們可以配置應(yīng)用如下:
return array( 'import'=>array( 'application.models.*', ), );
默認(rèn)情況下,AR類的名字和數(shù)據(jù)表的名字相同。如果不同,請覆蓋tableName()方法。
要使用表前綴功能,AR類的 tableName() 方法可以通過如下方式覆蓋
public function tableName() { return '{{post}}'; }
這就是說,我們將沒有前綴的表名用雙大括號括起來,這樣Yii就能自動添加前綴,從而返回完整的表名。
數(shù)據(jù)表行中列的值可以作為相應(yīng)AR實例的屬性訪問。例如,如下代碼設(shè)置了 title 列 (屬性):
$post=new Post; $post->title='a sample post';
雖然我們從未在Post類中顯式定義屬性title,我們還是可以通過上述代碼訪問。這是因為title是tbl_post表中的一個
列,CActiveRecord通過PHP的__get()魔術(shù)方法使其成為一個可訪問的屬性。如果我們嘗試以同樣的方式訪問一個不存在的列,將會拋出一個異常。
如果一個表沒有主鍵,則必須在相應(yīng)的AR類中通過如下方式覆蓋 primaryKey() 方法指定哪一列或哪幾列作為主鍵。
public function primaryKey() { return 'id'; // 對于復(fù)合主鍵,要返回一個類似如下的數(shù)組 // return array('pk1', 'pk2'); }
3、創(chuàng)建記錄
要向數(shù)據(jù)表中插入新行,我們要創(chuàng)建一個相應(yīng) AR 類的實例,設(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實例將包含一個更新的主鍵。在上面的例子中,id屬性將反映出新插入帖子的主鍵值,即使我們從未顯式地改變它。
如果一個列在表結(jié)構(gòu)中使用了靜態(tài)默認(rèn)值(例如一個字符串,一個數(shù)字)定義。則AR實例中相應(yīng)的屬性將在此實例創(chuàng)建時自動含有此默認(rèn)值。改變此默認(rèn)值的一個方式就是在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('NOW()'); //CDbExpression類就是計算數(shù)據(jù)庫表達(dá)式的值 // $post->create_time='NOW()'; 不會起作用,因為 // 'NOW()' 將會被作為一個字符串處理。 $post->save();
提示: 由于AR允許我們無需寫一大堆SQL語句就能執(zhí)行數(shù)據(jù)庫操作,我們經(jīng)常會想知道AR在背后到底執(zhí)行了什么SQL語句。這可以通過開啟Yii的日志功能實現(xiàn)。例如,我們在應(yīng)用配置中開啟了CWebLogRoute,我們將會在每個網(wǎng)頁的最后看到執(zhí)行過的SQL語句。
我們也可以在應(yīng)用配置中設(shè)置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ù)表行中相應(yīng)列的值。然后我們就可以像讀取普通對象的屬性那樣讀取載入的值,例如 echo $post->title;。
如果使用給定的查詢條件在數(shù)據(jù)庫中沒有找到任何東西, find 方法將返回null。
調(diào)用find時,我們使用 $condition 和 $params 指定查詢條件。此處 $condition 可以是 SQL 語句中的 WHERE 字符串,$params 則是一個參數(shù)數(shù)組,其中的值應(yīng)綁定到 $condation 中的占位符。例如:
// 查找 postID=10 的那一行 $post=Post::model()->find('postID=:postID', array(':postID'=>10));
注意: 在上面的例子中,我們可能需要在特定的 DBMS 中將 postID 列的引用進(jìn)行轉(zhuǎn)義。 例如,如果我們使用 PostgreSQL,我們必須將此表達(dá)式寫為 "postID"=:postID,因為 PostgreSQL 在默認(rèn)情況下對列名大小寫不敏感。
我們也可以使用 $condition 指定更復(fù)雜的查詢條件。不使用字符串,我們可以讓 $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 不需要了
注意,當(dāng)使用 CDbCriteria 作為查詢條件時,$params 參數(shù)不再需要了,因為它可以在 CDbCriteria 中指定,就像上面那樣。
一種替代 CDbCriteria 的方法是給 find 方法傳遞一個數(shù)組。數(shù)組的鍵和值各自對應(yīng)標(biāo)準(zhǔn)(criterion)的屬性名和值,上面的例子可以重寫為如下:
$post=Post::model()->find(array( 'select'=>'title', 'condition'=>'postID=:postID', 'params'=>array(':postID'=>10), ));
當(dāng)一個查詢條件是關(guān)于按指定的值匹配幾個列時,我們可以使用 findByAttributes()。我們使 $attributes
參數(shù)是一個以列名做索引的值的數(shù)組。在一些框架中,此任務(wù)可以通過調(diào)用類似 findByNameAndTitle
的方法實現(xiàn)。雖然此方法看起來很誘人, 但它常常引起混淆,沖突和比如列名大小寫敏感的問題。
當(dāng)有多行數(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); // 檢查是否至少有一行復(fù)合指定的條件 $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); // 假設(shè)有一個帖子,其 ID 為 10 $post->delete(); // 從數(shù)據(jù)表中刪除此行
注意,刪除之后, AR 實例仍然不變,但數(shù)據(jù)表中相應(yīng)的行已經(jīng)沒了。
使用下面的類級別代碼,可以無需首先加載行就可以刪除它。
// 刪除符合指定條件的行 Post::model()->deleteAll($condition,$params); // 刪除符合指定條件和主鍵的行 Post::model()->deleteByPk($pk,$condition,$params);
7、數(shù)據(jù)驗證
當(dāng)插入或更新一行時,我們常常需要檢查列的值是否符合相應(yīng)的規(guī)則。如果列的值是由最終用戶提供的,這一點就更加重要??傮w來說,我們永遠(yuǎn)不能相信任何來自客戶端的數(shù)據(jù)。
當(dāng)調(diào)用 save() 時, AR 會自動執(zhí)行數(shù)據(jù)驗證。驗證是基于在 AR 類的 rules() 方法中指定的規(guī)則進(jìn)行的。關(guān)于驗證規(guī)則的更多詳情,請參考 聲明驗證規(guī)則 一節(jié)。下面是保存記錄時所需的典型的工作流。
if($post->save()) { // 數(shù)據(jù)有效且成功插入/更新 } else { // 數(shù)據(jù)無效,調(diào)用 getErrors() 提取錯誤信息 }
當(dāng)要插入或更新的數(shù)據(jù)由最終用戶在一個 HTML 表單中提交時,我們需要將其賦給相應(yīng)的 AR 屬性。我們可以通過類似如下的方式實現(xiàn):
$post->title=$_POST['title']; $post->content=$_POST['content']; $post->save();
如果有很多列,我們可以看到一個用于這種復(fù)制的很長的列表。這可以通過使用如下所示的 attributes 屬性簡化操作。更多信息可以在 安全的特性賦值 一節(jié)和 創(chuàng)建動作 一節(jié)找到。
// 假設(shè) $_POST['Post'] 是一個以列名索引列值為值的數(shù)組 $post->attributes=$_POST['Post']; $post->save();
8、對比記錄
類似于表記錄,AR實例由其主鍵值來識別。因此,要對比兩個AR實例,假設(shè)它們屬于相同的AR類, 我們只需要對比它們的主鍵值。然而,一個更簡單的方式是調(diào)用 CActiveRecord::equals()。
不同于AR在其他框架的執(zhí)行, Yii在其 AR 中支持多個主鍵. 一個復(fù)合主鍵由兩個或更多字段構(gòu)成。相應(yīng)地,主鍵值在Yii中表現(xiàn)為一個數(shù)組。primaryKey屬性給出了一個 AR 實例的主鍵值。
9、自定義
CActiveRecord 提供了幾個占位符方法,它們可以在子類中被覆蓋以自定義其工作流。
beforeva lidate 和 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處理事務(wù)
每個 AR 實例都含有一個屬性名叫 dbConnection ,是一個 CDbConnection 的實例,這樣我們可以在需要時配合 AR 使用由 Yii DAO 提供的 事務(wù) 功能:
$model=Post::model(); $transaction=$model->dbConnection->beginTransaction(); try { // 查找和保存是可能由另一個請求干預(yù)的兩個步驟 // 這樣我們使用一個事務(wù)以確保其一致性和完整性 $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)合使用并應(yīng)用于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,我們將默認(rèn)獲取 5 條最近發(fā)布的帖子。
13、默認(rèn)的命名范圍
模型類可以有一個默認(rèn)命名范圍,它將應(yīng)用于所有 (包括相關(guān)的那些)
關(guān)于此模型的查詢。例如,一個支持多種語言的網(wǎng)站可能只想顯示當(dāng)前用戶所指定的語言的內(nèi)容。因為可能會有很多關(guān)于此網(wǎng)站內(nèi)容的查詢,我們可以定義一個默認(rèn)
的命名范圍以解決此問題。為實現(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();
注意,默認(rèn)的命名范圍只會應(yīng)用于 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進(jìn)行關(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)系中的一個特例,當(dāng)A最多有一個的時候,我們說B有一個A (B has one A)。
MANY_MANY:
這個相當(dāng)于關(guān)系數(shù)據(jù)庫中的多對多關(guān)系。因為絕大多數(shù)關(guān)系數(shù)據(jù)庫并不直接支持多對多的關(guān)系,這時通常都需要一個單獨的關(guān)聯(lián)表,把多對多的關(guān)系分解為兩個一對
多的關(guān)系。用AR的方式去理解的話,我們可以認(rèn)為 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可以加入一些額外的設(shè)置,后面會做介紹。
下面的代碼演示了如何定義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)的名稱。在進(jìn)行關(guān)聯(lián)查詢時,這些屬性就會被設(shè)置為關(guān)聯(lián)到的AR類的實例,例如在查詢?nèi)〉靡粋€Post實例時,它的$author屬性就是代表Post作者的一個User類的實例。
2、關(guān)聯(lián)查詢
進(jìn)行關(guān)聯(lián)查詢最簡單的方式就是訪問一個關(guān)聯(lián)AR對象的某個關(guān)聯(lián)屬性。如果這個屬性之前沒有被訪問過,這時就會啟動一個關(guān)聯(lián)查詢,通過當(dāng)前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次連接查詢。此時我們應(yīng)當(dāng)使用所謂的急切加載方法。
急切加載方法檢索主要的 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 類查詢的字段列表。默認(rèn)是 '*', 意味著所有字段。查詢的字段名字可用別名表達(dá)式來消除歧義(例如:COUNT(??.name) AS nameCount)。
condition: WHERE 子語句。默認(rèn)為空。注意, 列要使用別名引用(例如:??.id=10)。
params: 被綁定到 SQL 語句的參數(shù). 應(yīng)當(dāng)為一個由 name-value 對組成的數(shù)組()。
on: ON 子語句. 這里指定的條件將使用 and 操作符被追加到連接條件中。此選項中的字段名應(yīng)被消除歧義。此選項不適用于 MANY_MANY 關(guān)聯(lián)。
order: ORDER BY 子語句。默認(rèn)為空。注意, 列要使用別名引用(例如:??.age DESC)。
with: 應(yīng)當(dāng)和此對象一同載入的子關(guān)聯(lián)對象列表. 注意, 不恰當(dāng)?shù)氖褂每赡軙纬梢粋€無窮的關(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)制與主表和其他表連接。此選項只對于HAS_MANY 和 MANY_MANY 關(guān)聯(lián)有意義。若此選項被設(shè)置為 false, ......(此處原文出錯!).默認(rèn)為空。此選項中的字段名以被消除歧義。
having: HAVING 子語句。默認(rèn)是空。注意, 列要使用別名引用。
index: 返回的數(shù)組索引類型。確定返回的數(shù)組是關(guān)鍵字索引數(shù)組還是數(shù)字索引數(shù)組。不設(shè)置此選項, 將使用數(shù)字索引數(shù)組。此選項只對于HAS_MANY 和 MANY_MANY 有意義
此外, 下面的選項在延遲加載中對特定關(guān)聯(lián)是可用的:
group: GROUP BY子句。默認(rèn)為空。注意, 列要使用別名引用(例如:??.age)。 本選項僅應(yīng)用于HAS_MANY 和 MANY_MANY 關(guān)聯(lián)。
having: HAVING子句。默認(rèn)為空。注意, 列要使用別名引用(例如:??.age)。本選項僅應(yīng)用于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 實例也載入了它的分類。
相關(guān)文章
php強(qiáng)制文件下載而非在瀏覽器打開的自定義函數(shù)分享
這篇文章主要介紹了php強(qiáng)制文件下載而非在瀏覽器打開的自定義函數(shù)分享,需要的朋友可以參考下2014-05-05Laravel學(xué)習(xí)基礎(chǔ)之migrate的使用教程
這篇文章主要給大家介紹了關(guān)于Laravel學(xué)習(xí)基礎(chǔ)之migrate使用的相關(guān)資料,文中通過示例代碼介紹非常詳細(xì),分別介紹了生成遷移、遷移結(jié)構(gòu)、運行遷移和回滾遷移等實現(xiàn)的方法,需要的朋友可以參考借鑒,下面來一起看看吧。2017-10-10laravel 實現(xiàn)阿里云oss文件上傳功能的示例
這篇文章主要介紹了laravel 實現(xiàn)阿里云oss文件上傳功能,本文通過示例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2021-09-09CodeIgniter實現(xiàn)從網(wǎng)站抓取圖片并自動下載到文件夾里的方法
這篇文章主要介紹了CodeIgniter實現(xiàn)從網(wǎng)站抓取圖片并自動下載到文件夾里的方法,實例分析了CodeIgniter網(wǎng)頁圖片操作的相關(guān)技巧,需要的朋友可以參考下2015-06-06ThinkPHP字符串函數(shù)及常用函數(shù)匯總
這篇文章主要介紹了ThinkPHP字符串函數(shù)及常用函數(shù)匯總,可供開發(fā)人員參考使用,需要的朋友可以參考下2014-07-07yii2中LinkPager增加總頁數(shù)和總記錄數(shù)的實例
本篇文章主要介紹了php中LinkPager增加總頁數(shù)和總記錄數(shù)的實例,具有一定的參考價值,有興趣的可以了解一下2017-08-08php實現(xiàn)銀聯(lián)商務(wù)公眾號+服務(wù)窗支付的示例代碼
這篇文章主要介紹了php實現(xiàn)銀聯(lián)商務(wù)公眾號+服務(wù)窗支付的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10