Yii AR(Active Record) 詳解


雖 然Yii DAO可以處理事實上任何數據庫相關的任務,但很可能我們會花費90%的時間  用來編寫一些通用的SQL語句來執行CRUD操作(創建,讀取,更新和刪除)。  同時我們也很難維護這些PHP和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和用它來執行CRUD操作。在下一小節我們將展示如何使用AR 處理數據庫中的關系。為了簡單起見,我們使用本節下面的數據庫表作為例子。 請注意,如果你使用MySQL數據庫,在下面的SQL中您應該替換 AUTOINCREMENT 為 AUTO_INCREMENT 。

CREATE TABLE tbl_post (
    id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    title VARCHAR(128) NOT NULL,
    content TEXT NOT NULL,
    create_time INTEGER NOT NULL
);

注意: AR不是要解決所有與數據庫相關的任務。它最好用於在PHP結構中模型化數據表和執行不復雜的 SQL 語句. 而 Yii DAO 應該用於復雜的情況下。 建立數據庫連接 AR需要一個數據庫連接以執行數據庫相關的操作。默認情況下,應用中的 db 組件提供了 CDbConnection實例作為我們需要的數據庫連接。下面的應用程序配置提供了一個例子:

return array(
    'components'=>array(
        'db'=>array(
            'class'=>'system.db.CDbConnection',
            'connectionString'=>'sqlite:path/to/dbfile',
            // turn on schema caching to improve performance
            // 'schemaCachingDuration'=>3600,
        ),
    ),
);

提示: 由於Active Record需要表的元數據來確定數據表的字段信息,  這需要時間來讀取和分析元數據。如果您的數據庫結構是比較固定的,你應該打開緩存。  打開方法是配置CDbConnection::schemaCachingDuration屬 性為一個大於0的值。 AR的支持受限於數據庫管理系統。目前,只有以下數據庫管理系統支持: MySQL 4.1 或以后版本 PostgreSQL 7.3 或以后版本 SQLite 2 和 3 Microsoft SQL Server 2000 或以后版本 Oracle 注意: Microsoft SQL Server自1.0.4版本提供支持;而對 Oracle 自1.0.5版本即提供支持。 如果你想使用其他組件而不是 db ,或者你使用 AR 訪問多個數據庫,你應該重寫CActiveRecord::getDbConnection()。 CActiveRecord類 是所有AR類的基類。 提示: 有兩種方法可以在AR模式下使用多種數據庫系統。如果數據庫的模式不同, 您可以以不同的getDbConnection()來 創建不同的 AR 類。否則,動態改變靜態變量CActiveRecord::db 是一個更好的主意。 定義 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() 方法. 方法 model() is declared as such for every AR class (to be explained shortly). 信息: 要使用版本 1.1.0 引入的表前綴特征, AR 的方法 tableName() 可以被如下重寫,

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

這樣,不是返回一個完整的表名, 我們返回去掉了前綴的表名,並把它環繞在雙彎曲括號中. 一條數據的字段可以作為相應 AR 實例的屬性被訪問. 例如, 下面的代碼設置了 title 字段(屬性):

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

雖然我們沒有在 Post 類中明確聲明 title 屬性, 我們仍然可以在上面的代碼中訪問它. 這是因為 title 是表 tbl_post 中的字段, 在 PHP __get() 魔術方法的幫助下,CActiveRecord 可以將其作為一個屬性來訪問. 若以同樣方式嘗試訪問不存在的字段,一個異常將被拋出. 信息:在此指南中,我們為所有的數據表和字段采取小寫格式.這是因為在不同的DBMS中,對 於大小寫的敏感是不 同的.例如,PostgreSQL默認對字段名字是大小寫不敏感的, and we must quote a column in  a query condition if the column contains mixed-case  letters.使用小寫格式可以避免此問題. AR 依賴於數據表良好定義的主鍵. 若一個表沒有一個主鍵, 需要相應的 AR 類指定哪些字段應當為主鍵,通過重寫 primaryKey() 方法,

public function primaryKey()
{
    return ’id’;
    // For composite primary key, return an array like the following
    // return array(’pk1’, ’pk2’);
}

創建記錄 要插入新的一行記錄到數據表中, 我們創建一個新的對應的 AR 類實例, 設置和字段對應的屬性的值, 並調用 save() 方法來完成插入.

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

若表的主鍵是自增的, 在插入后 AR 實例將包含一個更新后的主鍵. 在上面的例子中, 屬性id 將映射為新插入的主鍵值, 即使我們沒有明確更改它.

若在表模式中,一個字段被定義為一些靜態默認值(static default value) (例如一個字符串, 一個數字), 在這個AR實例被創建后, 實例中相應的屬性將自動有相應的默認值. 改變此默認值的一個方式是在 AR 類中明確聲明此屬性:

class Post extends CActiveRecord
{
    public $title=’please enter a title’;
    ......
}
$post=new Post;
echo $post->title;  // this would display: please enter a title

從版本 1.0.2 開始, 在記錄被保存前(插入或更新)一個屬性可以賦值為 CDbExpression 類型的值. 例如, 為了保存由 MySQL NOW() 函數返回的時間戳, 我們可以使用下面的代碼:

$post=new Post;
$post->create time=new CDbExpression(’NOW()’);
// $post->create time=’NOW()’; will not work because
// ’NOW()’ will be treated as a string
$post->save();

提示:AR 允許我們執行數據庫操作而無需編寫麻煩的 SQL 語句, 我們常常想要知道什么 SQL 語句被 AR 在下面執行了. 這可以通過打開 Yii 的記錄(logging)特征來實現. 例如, 我們可以在應用配置中打開 CWebLogRoute , 我們將看到被執行的 SQL 語句被顯示在每個頁面的底部. 自版本 1.0.5 開始, 我們可以在應用配置設置 CDbConnection::enableParamLogging 為 true 以便 綁定到 SQL 語句的參數值也被記錄. 讀取記錄 要讀取數據表中的數據,我們可以調用下面其中一個 find 方法:

// find the first row satisfying the specified condition
$post=Post::model()->find($condition,$params);
// find the row with the specified primary key
$post=Post::model()->findByPk($postID,$condition,$params);
// find the row with the specified attribute values
$post=Post::model()->findByAttributes($attributes,$condition,$params);
// find the first row using the specified SQL statement
$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 是一個參數數組,其中的值應被綁定到 $condition 的占位符.例如,

// find the row with postID=10
$post=Post::model()->find(’postID=:postID’, array(’:postID’=>10));

注意: 在上面的例子中, 對於某些 DBMS 我們可能需要轉義對 postID 字段的引用. 例如, 若我們使用 PostgreSQL, 我們需要寫condition 為 “postID”=:postID ,因為 PostgreSQL 默認情況下對待字段名字為大小寫不敏感的. 我們也可以使用 $condition 來指定更復雜的查詢條件. 不使用字符串,我們讓 $condition 為一個 CDbCriteria 實例,可以讓我們指定條件而不限於 WHERE 子語句。例如,

$criteria=new CDbCriteria;
$criteria->select=’title’; // only select the ’title’ column
$criteria->condition=’postID=:postID’;
$criteria->params=array(’:postID’=>10);
$post=Post::model()->find($criteria); // $params is not needed

注意, 當使用 CDbCriteria 作為查詢條件, 不再需要參數 $params 因為它可以在 CDbCriteria 中被指定, 如上所示.

一個可選的 CDbCriteria 方式是傳遞一個數組到 find 方法. 數組的鍵和值分別對應於 criteria 的屬性名字和值.上面的例子可以被如下重寫,

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

信息: 當一個查詢條件是關於匹配一些字段用指定的值, 我們可以使用 findByAttributes()。 我們讓參數 $attributes 為一個數組,數組的值由字段名字索引. 在一些框架中,此任務可以通過調用類似於 findByNameAndTitle 的方法來實現. 雖然這個方法看起來很有吸引力, 但它常常引起混淆和沖突,例如字段名字的大小寫敏感性問題. 當多行記錄滿足指定的查詢條件, 我們可以使用下面的 findAll 方法將它們聚合在一起, 每個都有它們自己的副本 find 方法。

// find all rows satisfying the specified condition
$posts=Post::model()->findAll($condition,$params);
// find all rows with the specified primary keys
$posts=Post::model()->findAllByPk($postIDs,$condition,$params);
// find all rows with the specified attribute values
$posts=Post::model()->findAllByAttributes($attributes,$condition,$params);
// find all rows using the specified SQL statement
$posts=Post::model()->findAllBySql($sql,$params);

若沒有符合條件的記錄, findAll 返回一個空數組. 不同於 find 方法,find方法會返回 null.

除了上面所說的 find 和 findAll 方法, 為了方便, 下面的方法也可以使用:

// get the number of rows satisfying the specified condition
$n=Post::model()->count($condition,$params);
// get the number of rows using the specified SQL statement
$n=Post::model()->countBySql($sql,$params);
// check if there is at least a row satisfying the specified condition
$exists=Post::model()->exists($condition,$params);

更新記錄 一個 AR 實例被字段值填充后, 我們可以改變它們並保存回它們到數據表中.

 

$post=Post::model()->findByPk(10);
$post->title=’new post title’;
$post->save(); // save the change to database

若我們所見, 我們使用相同的 save() 方法來執行插入和更新操作. 若一個 AR 實例被使用 new 操作符創建,調用 save() 將插入一行新記錄到數據表中; 若此 AR 實例是一些 find 或 findAll 方法調用的結果, 調用 save() 將更新表中已存在的記錄. 事實上, 我們可以使用 CActiveRecord::isNewRecord 來檢查一個 AR 實例是否是新建的.

更新一行或多行表中的記錄而不預先載入它們也是可能的. AR 提供如下方便的類水平的(class-level)方法來實現它:

 

// update the rows matching the specified condition
Post::model()->updateAll($attributes,$condition,$params);
// update the rows matching the specified condition and primary key(s)
Post::model()->updateByPk($pk,$attributes,$condition,$params);
// update counter columns in the rows satisfying the specified conditions
Post::model()->updateCounters($counters,$condition,$params);

在上面, $attributes 是一個值由字段名索引的數組; $counters 是一個增加值由字段名索引的數組; $condition 和 $params 已在之前被描述. 刪除記錄 我們也可以刪除一行記錄若一個 AR 實例已被此行記錄填充.

 

$post=Post::model()->findByPk(10); // assuming there is a post whose ID is 10
$post->delete(); // delete the row from the database table

注意, 在刪除后, 此 AR 實例仍然未改變, 但相應的表記錄已經不存在了.

下面類水平的(class-level)方法被用來刪除記錄,而無需預先載入它們:

 

// delete the rows matching the specified condition
Post::model()->deleteAll($condition,$params);
// delete the rows matching the specified condition and primary key(s)
Post::model()->deleteByPk($pk,$condition,$params);

數據驗證 當插入或更新一行記錄, 我們常常需要檢查字段的值是否符合指定的規則. 若字段值來自用戶時這一點特別重要.通常我們永遠不要信任用戶提交的數據.

AR 自動執行數據驗證在 save() 被調用時.驗證基於在 AR 類中的 rules()方 法中指定的規則。如何指定驗證規則的更多信息, 參考 聲明驗證規則 部分. 下面是保存一條記錄典型的工作流程:

 

if($post->save())
{
    // data is valid and is successfully inserted/updated
}
else
{
    // data is invalid. call getErrors() to retrieve error messages
}

當插入或更新的數據被用戶在 HTML 表單中提交, 我們需要賦值它們到對象的 AR 屬性. 我們可以這樣做:

 

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

若有很多字段, 我們可以看到一個很長的賦值列表. 可以使用下面的 attributes 屬性來緩解. 更多細節可以在 Securing Attribute Assignments 部分和 Creating Action 部分找到.

 

// assume $_POST[’Post’] is an array of column values indexed by column names
$post->attributes=$_POST[’Post’];
$post->save();

對比記錄 類似於表記錄, AR 實例由它們的主鍵值來被識別. 因此,要對比兩個 AR 實例, 我們只需要對比它們的主鍵值, 假設它們屬於相同的 AR 類. 然而,一個更簡單的方式是調用 CActiveRecord::equals(). 信息: 不同於 AR 在其他框架的執行, Yii 在其 AR 中支持多個主鍵. 一個復合主鍵由兩個或更多字段構成. 對應的, 主鍵值在 Yii 中表示為一個數組. The primaryKey 屬性給出一個 AR 實例的主鍵值。 Customization CActiveRecord 提供了一些占位符(placeholder)方法可被用來在子類中重寫以自定義它的工作流程. beforeValidate 和 afterValidate: 它們在驗證執行 之前/后 被調用. beforeSave 和 afterSave: 它們在保存一個 AR 實例之前/后 被調用. beforeDelete 和 afterDelete: 它們在一個 AR 實例被刪除 之前/后 被調用. afterConstruct: 這將在每個 AR 實例被使用 new 操作符創建之后被調用. beforeFind: 它在一個 AR finder 被用來執行一個查詢之前被調用 (例如 find(), findAll()). 從版本 1.0.9 可用. afterFind: 它在每個 AR 實例被創建作為一個查詢結果后被調用. 在 AR 中使用事務處理 每個 AR 實例包含一個名為 dbConnection 的屬性,它是一個 CDbConnection 實例. 這樣我們在使用 AR 時就可以使用 Yii DAO 提供的事務處理特征:

 

$model=Post::model();
$transaction=$model->dbConnection->beginTransaction();
try
{
    // find and save are two steps which may be intervened by another request
    // we therefore use a transaction to ensure consistency and integrity
    $post=$model->findByPk(10);
    $post->title=’new post title’;
    $post->save();
    $transaction->commit();
}
catch(Exception $e)
{
    $transaction->rollBack();
}

命名空間(new!) 注意: 對於命名空間的支持從版本 1.0.5 開始. 思路來源於 Ruby on Rails. a named scope represents a named query  criteria that can be combined with other named scopes and applied to an  active record query.

命名空間被主要在 CActiveRecord::scopes() 方法中聲明,格式是 name-criteria 對。 下面的代碼在 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方法的 modi?er 來使用。幾個命名空間可以連接在一起,則樣可以得到一個更加有限制性的查詢結果集。 例如,要找到最近發表的帖子,我們可以使用下面的代碼:

 

$posts=Post::model()->published()->recently()->findAll();

通常命名空間必須出現在一個 find 方法的左邊。 Each of them  provides a query criteria, which is combined with other criterias,  including the one passed to the find method call. The net e?ect is like  adding a list of ?lters to a query.

從版本 1.0.6 開始, 命名空間也可以使用 update 和 delete 方法。例如,下面的代碼將刪除 所有最近發表的帖子:

 

Post::model()->published()->recently()->delete();

注意: 命名空間只可以被作為類水平的方法使用。也就是說,此方法必須使用 ClassName::model() 來調用它。 參數化命名空間(Parameterized Named Scopes) 命名空間可以被參數化。例如,我們想要定制命名空間 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 個最近發表的內容。 默認命名空間 一個模型類可以有一個默認命名空間,它被應用於此模型所有的查詢 (包括  relational ones)。例如,一個支持多種語言的網站只是以當前用戶指定的語言來顯示內容。  因為有很多關於站點內容的查詢,我們可以定義一個默認命名空間來解決這個問題。要這樣做,我們重寫  CActiveRecord::defaultScope 方法如下,

 

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

現在,若調用下面的方法將自動使用上面定義的查詢條件:

 

$contents=Content::model()->findAll();

注意默認命名空間只應用於 SELECT 查詢。它忽視 INSERT,UPDATE 和 DELETE 查詢。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM