亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

direktori cari
閱讀前篇 簡介 Yii 是什么 從 Yii 1.1 升級 入門 安裝 Yii 運行應用 第一次問候 使用Forms 數(shù)據(jù)庫應用 使用 Gii 生成代碼 進階 應用結構 概述 入口腳本 應用(Applications) 應用組件(Application Components) 控制器(Controllers) 模型(Models) 視圖(views) 模塊(Modules) 過濾器(Filters) 小部件(Widgets) 前端資源(Assets) 擴展(Extensions) 請求處理 運行概述 啟動引導(Bootstrapping) 路由和創(chuàng)建URL 請求(Requests) 響應(Responses) Sessions 和 Cookies 錯誤處理(Handling Errors) 日志(Logging) 關鍵概念 組件(Component) 屬性(Property) 事件(Events) 行為(Behaviors) 配置(Configurations) 別名(Aliases) 類自動加載(Autoloading) 服務定位器(Service Locator) 依賴注入容器(Dependency Injection Container) 配合數(shù)據(jù)庫工作 數(shù)據(jù)庫訪問 (Data Access Objects) 查詢生成器(Query Builder) 活動記錄(Active Record) 數(shù)據(jù)庫遷移(Migrations) Sphinx Redis MongoDB Elasticsearch 接收用戶數(shù)據(jù) 創(chuàng)建表單(Creating Forms) 輸入驗證(Validating Input) 文件上傳(Uploading Files) 收集列表輸入(Collecting Tabular Input) 多模型的復合表單(Getting Data for Multiple Models) 顯示數(shù)據(jù) 格式化輸出數(shù)據(jù)(Data Formatting) 分頁(Pagination) 排序(Sorting) 數(shù)據(jù)提供器(Data Providers) 數(shù)據(jù)小部件(Data Widgets) 客戶端腳本使用(Working with Client Scripts) 主題(Theming) 安全 認證(Authentication) 授權(Authorization) 處理密碼(Working with Passwords) 客戶端認證(Auth Clients) 最佳安全實踐(Best Practices) 緩存 概述 數(shù)據(jù)緩存 片段緩存 頁面緩存 HTTP 緩存 RESTfull Web服務 快速入門(Quick Start) 資源(Resources) 控制器(Controllers) 路由(Routing) 格式化響應(Response Formatting) 授權認證(Authentication) 速率限制(Rate Limiting) 版本(Versioning) 錯誤處理(Error Handling) 開發(fā)工具 調試工具欄和調試器 使用Gii生成代碼 生成API文檔 測試 概述(Overview) 配置測試環(huán)境(Testing environment setup) 單元測試(Unit Tests) 功能測試(Function Tests) 驗收測試(Acceptance Tests) 測試夾具(Fixtures) 高級專題 高級應用模板 創(chuàng)建自定義應用程序結構 控制臺命令 核心驗證器(Core Validators) 國際化 收發(fā)郵件 性能優(yōu)化 共享主機環(huán)境 模板引擎 集成第三方代碼 小部件 Bootstrap 小部件 Jquery UI 助手類 概述 Array 助手(ArrayHelper) Html 助手(Html) Url 助手(Url)
watak

Active Record

Active Record

注意:該章節(jié)還在開發(fā)中。

Active Record?(活動記錄,以下簡稱AR)提供了一個面向對象的接口, 用以訪問數(shù)據(jù)庫中的數(shù)據(jù)。一個 AR 類關聯(lián)一張數(shù)據(jù)表, 每個 AR 對象對應表中的一行,對象的屬性(即 AR 的特性Attribute)映射到數(shù)據(jù)行的對應列。 一條活動記錄(AR對象)對應數(shù)據(jù)表的一行,AR對象的屬性則映射該行的相應列。 您可以直接以面向對象的方式來操縱數(shù)據(jù)表中的數(shù)據(jù),媽媽再不用擔心我需要寫原生 SQL 語句啦。

例如,假定?Customer?AR 類關聯(lián)著?customer?表,且該類的?name?屬性代表?customer?表的?name?列。 你可以寫以下代碼來哉customer?表里插入一行新的記錄:

用 AR 而不是原生的 SQL 語句去執(zhí)行數(shù)據(jù)庫查詢,可以調用直觀方法來實現(xiàn)相同目標。如,調用 yii\db\ActiveRecord::save() 方法將執(zhí)行插入或更新輪詢,將在該 AR 類關聯(lián)的數(shù)據(jù)表新建或更新一行數(shù)據(jù):

$customer = new Customer();
$customer->name = 'Qiang';
$customer->save();  // 一行新數(shù)據(jù)插入 customer 表

上面的代碼和使用下面的原生 SQL 語句是等效的,但顯然前者更直觀, 更不易出錯,并且面對不同的數(shù)據(jù)庫系統(tǒng)(DBMS, Database Management System)時更不容易產(chǎn)生兼容性問題。

$db->createCommand('INSERT INTO customer (name) VALUES (:name)', [
    ':name' => 'Qiang',
])->execute();

下面是所有目前被 Yii 的 AR 功能所支持的數(shù)據(jù)庫列表:

  • MySQL 4.1 及以上:通過 yii\db\ActiveRecord
  • PostgreSQL 7.3 及以上:通過 yii\db\ActiveRecord
  • SQLite 2 和 3:通過 yii\db\ActiveRecord
  • Microsoft SQL Server 2010 及以上:通過 yii\db\ActiveRecord
  • Oracle: 通過 yii\db\ActiveRecord
  • CUBRID 9.1 及以上:通過 yii\db\ActiveRecord
  • Sphinx:通過 yii\sphinx\ActiveRecord,需求?yii2-sphinx?擴展
  • ElasticSearch:通過 yii\elasticsearch\ActiveRecord,需求?yii2-elasticsearch?擴展
  • Redis 2.6.12 及以上:通過 yii\redis\ActiveRecord,需求?yii2-redis?擴展
  • MongoDB 1.3.0 及以上:通過 yii\mongodb\ActiveRecord,需求?yii2-mongodb?擴展

如你所見,Yii 不僅提供了對關系型數(shù)據(jù)庫的 AR 支持,還提供了 NoSQL 數(shù)據(jù)庫的支持。 在這個教程中,我們會主要描述對關系型數(shù)據(jù)庫的 AR 用法。 然而,絕大多數(shù)的內容在 NoSQL 的 AR 里同樣適用。

聲明 AR 類

要想聲明一個 AR 類,你需要擴展 yii\db\ActiveRecord 基類, 并實現(xiàn)?tableName?方法,返回與之相關聯(lián)的的數(shù)據(jù)表的名稱:

namespace app\models;

use yii\db\ActiveRecord;

class Customer extends ActiveRecord{
    
    public static function tableName()
    {
        return 'customer';
    }
}

訪問列數(shù)據(jù)

AR 把相應數(shù)據(jù)行的每一個字段映射為 AR 對象的一個個特性變量(Attribute) 一個特性就好像一個普通對象的公共屬性一樣(public property)。 特性變量的名稱和對應字段的名稱是一樣的,且大小姓名。

使用以下語法讀取列的值:

// "id" 和 "mail" 是 $customer 對象所關聯(lián)的數(shù)據(jù)表的對應字段名$id = $customer->id;
$email = $customer->email;

要改變列值,只要給關聯(lián)屬性賦新值并保存對象即可:

$customer->email = 'james@example.com';
$customer->save();

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

AR 用一個 yii\db\Connection 對象與數(shù)據(jù)庫交換數(shù)據(jù)。 默認的,它使用?db?組件作為其連接對象。詳見數(shù)據(jù)庫基礎章節(jié), 你可以在應用程序配置文件中設置下?db?組件,就像這樣,

return [
    'components' => [
        'db' => [
            'class' => 'yii\db\Connection',
            'dsn' => 'mysql:host=localhost;dbname=testdb',
            'username' => 'demo',
            'password' => 'demo',
        ],
    ],
];

如果在你的應用中應用了不止一個數(shù)據(jù)庫,且你需要給你的 AR 類使用不同的數(shù)據(jù)庫鏈接(DB connection) ,你可以覆蓋掉 yii\db\ActiveRecord::getDb() 方法:

class Customer extends ActiveRecord{
    // ...

    public static function getDb()
    {
        return \Yii::$app->db2;  // 使用名為 "db2" 的應用組件
    }
}

查詢數(shù)據(jù)

AR 提供了兩種方法來構建 DB 查詢并向 AR 實例里填充數(shù)據(jù):

  • yii\db\ActiveRecord::find()
  • yii\db\ActiveRecord::findBySql()

以上兩個方法都會返回 yii\db\ActiveQuery 實例,該類繼承自yii\db\Query, 因此,他們都支持同一套靈活且強大的 DB 查詢方法,如where(),join(),orderBy(),等等。 下面的這些案例展示了一些可能的玩法:

// 取回所有活躍客戶(狀態(tài)為 *active* 的客戶)并以他們的 ID 排序:$customers = Customer::find()
    ->where(['status' => Customer::STATUS_ACTIVE])
    ->orderBy('id')
    ->all();

// 返回ID為1的客戶:$customer = Customer::find()
    ->where(['id' => 1])
    ->one();

// 取回活躍客戶的數(shù)量:$count = Customer::find()
    ->where(['status' => Customer::STATUS_ACTIVE])
    ->count();

// 以客戶ID索引結果集:$customers = Customer::find()->indexBy('id')->all();
// $customers 數(shù)組以 ID 為索引

// 用原生 SQL 語句檢索客戶:$sql = 'SELECT * FROM customer';
$customers = Customer::findBySql($sql)->all();

小技巧:在上面的代碼中,Customer::STATUS_ACTIVE?是一個在?Customer?類里定義的常量。(譯注:這種常量的值一般都是tinyint)相較于直接在代碼中寫死字符串或數(shù)字,使用一個更有意義的常量名稱是一種更好的編程習慣。

有兩個快捷方法:findOne?和?findAll()?用來返回一個或者一組ActiveRecord實例。前者返回第一個匹配到的實例,后者返回所有。 例如:

// 返回 id 為 1 的客戶$customer = Customer::findOne(1);

// 返回 id 為 1 且狀態(tài)為 *active* 的客戶$customer = Customer::findOne([
    'id' => 1,
    'status' => Customer::STATUS_ACTIVE,
]);

// 返回id為1、2、3的一組客戶$customers = Customer::findAll([1, 2, 3]);

// 返回所有狀態(tài)為 "deleted" 的客戶$customer = Customer::findAll([
    'status' => Customer::STATUS_DELETED,
]);

以數(shù)組形式獲取數(shù)據(jù)

有時候,我們需要處理很大量的數(shù)據(jù),這時可能需要用一個數(shù)組來存儲取到的數(shù)據(jù), 從而節(jié)省內存。你可以用?asArray()?函數(shù)做到這一點:

// 以數(shù)組而不是對象形式取回客戶信息:$customers = Customer::find()
    ->asArray()
    ->all();
// $customers 的每個元素都是鍵值對數(shù)組

批量獲取數(shù)據(jù)

在?Query Builder(查詢構造器)?里,我們已經(jīng)解釋了當需要從數(shù)據(jù)庫中查詢大量數(shù)據(jù)時,你可以用?batch query(批量查詢)來限制內存的占用。 你可能也想在 AR 里使用相同的技巧,比如這樣……

// 一次提取 10 個客戶信息foreach (Customer::find()->batch(10) as $customers) {
    // $customers 是 10 個或更少的客戶對象的數(shù)組
}
// 一次提取 10 個客戶并一個一個地遍歷處理foreach (Customer::find()->each(10) as $customer) {
    // $customer 是一個 ”Customer“ 對象
}
// 貪婪加載模式的批處理查詢foreach (Customer::find()->with('orders')->each() as $customer) {
}

操作數(shù)據(jù)

AR 提供以下方法插入、更新和刪除與 AR 對象關聯(lián)的那張表中的某一行:

  • yii\db\ActiveRecord::save()
  • yii\db\ActiveRecord::insert()
  • yii\db\ActiveRecord::update()
  • yii\db\ActiveRecord::delete()

AR 同時提供了一下靜態(tài)方法,可以應用在與某 AR 類所關聯(lián)的整張表上。 用這些方法的時候千萬要小心,因為他們作用于整張表! 比如,deleteAll()?會刪除掉表里所有的記錄。

  • yii\db\ActiveRecord::updateCounters()
  • yii\db\ActiveRecord::updateAll()
  • yii\db\ActiveRecord::updateAllCounters()
  • yii\db\ActiveRecord::deleteAll()

下面的這些例子里,詳細展現(xiàn)了如何使用這些方法:

// 插入新客戶的記錄$customer = new Customer();
$customer->name = 'James';
$customer->email = 'james@example.com';
$customer->save();  // 等同于 $customer->insert();

// 更新現(xiàn)有客戶記錄$customer = Customer::findOne($id);
$customer->email = 'james@example.com';
$customer->save();  // 等同于 $customer->update();

// 刪除已有客戶記錄$customer = Customer::findOne($id);
$customer->delete();

// 刪除多個年齡大于20,性別為男(Male)的客戶記錄
Customer::deleteAll('age > :age AND gender = :gender', [':age' => 20, ':gender' => 'M']);

// 所有客戶的age(年齡)字段加1:
Customer::updateAllCounters(['age' => 1]);

須知:save()?方法會調用?insert()?和?update()?中的一個, 用哪個取決于當前 AR 對象是不是新對象(在函數(shù)內部,他會檢查 yii\db\ActiveRecord::isNewRecord 的值)。 若 AR 對象是由?new?操作符 初始化出來的,save()?方法會在表里插入一條數(shù)據(jù); 如果一個 AR 是由?find()?方法獲取來的, 則?save()?會更新表里的對應行記錄。

數(shù)據(jù)輸入與有效性驗證

由于AR繼承自yii\base\Model,所以它同樣也支持Model的數(shù)據(jù)輸入、驗證等特性。例如,你可以聲明一個rules方法用來覆蓋掉yii\base\Model::rules()里的;你也可以給AR實例批量賦值;你也可以通過調用yii\base\Model::validate()執(zhí)行數(shù)據(jù)驗證。

當你調用?save()、insert()update()?這三個方法時,會自動調用yii\base\Model::validate()方法。如果驗證失敗,數(shù)據(jù)將不會保存進數(shù)據(jù)庫。

下面的例子演示了如何使用AR 獲取/驗證用戶輸入的數(shù)據(jù)并將他們保存進數(shù)據(jù)庫:

// 新建一條記錄$model = new Customer;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
    // 獲取用戶輸入的數(shù)據(jù),驗證并保存
}

// 更新主鍵為$id的AR$model = Customer::findOne($id);
if ($model === null) {
    throw new NotFoundHttpException;
}
if ($model->load(Yii::$app->request->post()) && $model->save()) {
    // 獲取用戶輸入的數(shù)據(jù),驗證并保存
}

讀取默認值

你的表列也許定義了默認值。有時候,你可能需要在使用web表單的時候給AR預設一些值。如果你需要這樣做,可以在顯示表單內容前通過調用loadDefaultValues()方法來實現(xiàn):?`php $customer = new Customer(); $customer->loadDefaultValues(); // ... 渲染 $customer 的 HTML 表單 ...?`

AR的生命周期

理解AR的生命周期對于你操作數(shù)據(jù)庫非常重要。生命周期通常都會有些典型的事件存在。對于開發(fā)AR的behaviors來說非常有用。

當你實例化一個新的AR對象時,我們將獲得如下的生命周期:

  1. constructor
  2. yii\db\ActiveRecord::init(): 會觸發(fā)一個 yii\db\ActiveRecord::EVENT_INIT 事件

當你通過 yii\db\ActiveRecord::find() 方法查詢數(shù)據(jù)時,每個AR實例都將有以下生命周期:

  1. constructor
  2. yii\db\ActiveRecord::init(): 會觸發(fā)一個 yii\db\ActiveRecord::EVENT_INIT 事件
  3. yii\db\ActiveRecord::afterFind(): 會觸發(fā)一個 yii\db\ActiveRecord::EVENT_AFTER_FIND 事件

當通過 yii\db\ActiveRecord::save() 方法寫入或者更新數(shù)據(jù)時, 我們將獲得如下生命周期:

  1. yii\db\ActiveRecord::beforeValidate(): 會觸發(fā)一個 yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE 事件
  2. yii\db\ActiveRecord::afterValidate(): 會觸發(fā)一個 yii\db\ActiveRecord::EVENT_AFTER_VALIDATE 事件
  3. yii\db\ActiveRecord::beforeSave(): 會觸發(fā)一個 yii\db\ActiveRecord::EVENT_BEFORE_INSERT 或 yii\db\ActiveRecord::EVENT_BEFORE_UPDATE 事件
  4. 執(zhí)行實際的數(shù)據(jù)寫入或更新
  5. yii\db\ActiveRecord::afterSave(): 會觸發(fā)一個 yii\db\ActiveRecord::EVENT_AFTER_INSERT 或 yii\db\ActiveRecord::EVENT_AFTER_UPDATE 事件

最后,當調用 yii\db\ActiveRecord::delete() 刪除數(shù)據(jù)時, 我們將獲得如下生命周期:

  1. yii\db\ActiveRecord::beforeDelete(): 會觸發(fā)一個 yii\db\ActiveRecord::EVENT_BEFORE_DELETE 事件
  2. 執(zhí)行實際的數(shù)據(jù)刪除
  3. yii\db\ActiveRecord::afterDelete(): 會觸發(fā)一個 yii\db\ActiveRecord::EVENT_AFTER_DELETE 事件

查詢關聯(lián)的數(shù)據(jù)

使用 AR 方法也可以查詢數(shù)據(jù)表的關聯(lián)數(shù)據(jù)(如,選出表A的數(shù)據(jù)可以拉出表B的關聯(lián)數(shù)據(jù))。 有了 AR, 返回的關聯(lián)數(shù)據(jù)連接就像連接關聯(lián)主表的 AR 對象的屬性一樣。

建立關聯(lián)關系后,通過?$customer->orders?可以獲取 一個?Order?對象的數(shù)組,該數(shù)組代表當前客戶對象的訂單集。

定義關聯(lián)關系使用一個可以返回 yii\db\ActiveQuery 對象的 getter 方法, yii\db\ActiveQuery對象有關聯(lián)上下文的相關信息,因此可以只查詢關聯(lián)數(shù)據(jù)。

例如:

class Customer extends \yii\db\ActiveRecord{
    public function getOrders()
    {
        // 客戶和訂單通過 Order.customer_id -> id 關聯(lián)建立一對多關系
        return $this->hasMany(Order::className(), ['customer_id' => 'id']);
    }
}

class Order extends \yii\db\ActiveRecord{
    // 訂單和客戶通過 Customer.id -> customer_id 關聯(lián)建立一對一關系
    public function getCustomer()
    {
        return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
    }
}

以上使用了 yii\db\ActiveRecord::hasMany() 和 yii\db\ActiveRecord::hasOne() 方法。 以上兩例分別是關聯(lián)數(shù)據(jù)多對一關系和一對一關系的建模范例。 如,一個客戶有很多訂單,一個訂單只歸屬一個客戶。 兩個方法都有兩個參數(shù)并返回 yii\db\ActiveQuery 對象。

  • $class:關聯(lián)模型類名,它必須是一個完全合格的類名。
  • $link: 兩個表的關聯(lián)列,應為鍵值對數(shù)組的形式。 數(shù)組的鍵是?$class?關聯(lián)表的列名, 而數(shù)組值是關聯(lián)類 $class 的列名。 基于表外鍵定義關聯(lián)關系是最佳方法。

建立關聯(lián)關系后,獲取關聯(lián)數(shù)據(jù)和獲取組件屬性一樣簡單, 執(zhí)行以下相應getter方法即可:

// 取得客戶的訂單$customer = Customer::findOne(1);
$orders = $customer->orders; // $orders 是 Order 對象數(shù)組

以上代碼實際執(zhí)行了以下兩條 SQL 語句:

SELECT * FROM customer WHERE id=1;SELECT * FROM order WHERE customer_id=1;

提示:再次用表達式?$customer->orders將不會執(zhí)行第二次 SQL 查詢, SQL 查詢只在該表達式第一次使用時執(zhí)行。 數(shù)據(jù)庫訪問只返回緩存在內部前一次取回的結果集,如果你想查詢新的 關聯(lián)數(shù)據(jù),先要注銷現(xiàn)有結果集:unset($customer->orders);。

有時候需要在關聯(lián)查詢中傳遞參數(shù),如不需要返回客戶全部訂單, 只需要返回購買金額超過設定值的大訂單, 通過以下getter方法聲明一個關聯(lián)數(shù)據(jù)?bigOrders?:

class Customer extends \yii\db\ActiveRecord{
    public function getBigOrders($threshold = 100)
    {
        return $this->hasMany(Order::className(), ['customer_id' => 'id'])
            ->where('subtotal > :threshold', [':threshold' => $threshold])
            ->orderBy('id');
    }
}

hasMany()?返回 yii\db\ActiveQuery 對象,該對象允許你通過 yii\db\ActiveQuery 方法定制查詢。

如上聲明后,執(zhí)行?$customer->bigOrders?就返回 總額大于100的訂單。使用以下代碼更改設定值:

$orders = $customer->getBigOrders(200)->all();

>注意:關聯(lián)查詢返回的是 yii\db\ActiveQuery 的實例,如果像特性(如類屬性)那樣連接關聯(lián)數(shù)據(jù), 返回的結果是關聯(lián)查詢的結果,即 yii\db\ActiveRecord 的實例, 或者是數(shù)組,或者是 null ,取決于關聯(lián)關系的多樣性。如,$customer->getOrders()?返回?ActiveQuery實例,而?$customer->orders?返回Order?對象數(shù)組 (如果查詢結果為空則返回空數(shù)組)。

中間關聯(lián)表

有時,兩個表通過中間表關聯(lián),定義這樣的關聯(lián)關系, 可以通過調用 yii\db\ActiveQuery::via() 方法或 yii\db\ActiveQuery::viaTable() 方法來定制 yii\db\ActiveQuery 對象 。

舉例而言,如果?order?表和?item?表通過中間表?order_item?關聯(lián)起來, 可以在?Order?類聲明?items?關聯(lián)關系取代中間表:

class Order extends \yii\db\ActiveRecord{
    public function getItems()
    {
        return $this->hasMany(Item::className(), ['id' => 'item_id'])
            ->viaTable('order_item', ['order_id' => 'id']);
    }
}

兩個方法是相似的,除了 yii\db\ActiveQuery::via() 方法的第一個參數(shù)是使用 AR 類中定義的關聯(lián)名。 以上方法取代了中間表,等價于:

class Order extends \yii\db\ActiveRecord{
    public function getOrderItems()
    {
        return $this->hasMany(OrderItem::className(), ['order_id' => 'id']);
    }

    public function getItems()
    {
        return $this->hasMany(Item::className(), ['id' => 'item_id'])
            ->via('orderItems');
    }
}

延遲加載和即時加載(又稱惰性加載與貪婪加載)

如前所述,當你第一次連接關聯(lián)對象時, AR 將執(zhí)行一個數(shù)據(jù)庫查詢 來檢索請求數(shù)據(jù)并填充到關聯(lián)對象的相應屬性。 如果再次連接相同的關聯(lián)對象,不再執(zhí)行任何查詢語句,這種數(shù)據(jù)庫查詢的執(zhí)行方法稱為“延遲加載”。如:

// SQL executed: SELECT * FROM customer WHERE id=1$customer = Customer::findOne(1);
// SQL executed: SELECT * FROM order WHERE customer_id=1$orders = $customer->orders;
// 沒有 SQL 語句被執(zhí)行$orders2 = $customer->orders; //取回上次查詢的緩存數(shù)據(jù)

延遲加載非常實用,但是,在以下場景中使用延遲加載會遭遇性能問題:

// SQL executed: SELECT * FROM customer LIMIT 100$customers = Customer::find()->limit(100)->all();

foreach ($customers as $customer) {
    // SQL executed: SELECT * FROM order WHERE customer_id=...
    $orders = $customer->orders;
    // ...處理 $orders...
}

假設數(shù)據(jù)庫查出的客戶超過100個,以上代碼將執(zhí)行多少條 SQL 語句? 101 條!第一條 SQL 查詢語句取回100個客戶,然后, 每個客戶要執(zhí)行一條 SQL 查詢語句以取回該客戶的所有訂單。

為解決以上性能問題,可以通過調用 yii\db\ActiveQuery::with() 方法使用即時加載解決。

// SQL executed: SELECT * FROM customer LIMIT 100;//               SELECT * FROM orders WHERE customer_id IN (1,2,...)$customers = Customer::find()->limit(100)
    ->with('orders')->all();

foreach ($customers as $customer) {
    // 沒有 SQL 語句被執(zhí)行
    $orders = $customer->orders;
    // ...處理 $orders...
}

如你所見,同樣的任務只需要兩個 SQL 語句。 >須知:通常,即時加載 N 個關聯(lián)關系而通過 via() 或者 viaTable() 定義了 M 個關聯(lián)關系, 將有 1+M+N 條 SQL 查詢語句被執(zhí)行:一個查詢取回主表行數(shù), 一個查詢給每一個 (M) 中間表,一個查詢給每個 (N) 關聯(lián)表。 注意:當用即時加載定制 select() 時,確保連接 到關聯(lián)模型的列都被包括了,否則,關聯(lián)模型不會載入。如:

$orders = Order::find()->select(['id', 'amount'])->with('customer')->all();
// $orders[0]->customer 總是空的,使用以下代碼解決這個問題:$orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all();

有時候,你想自由的自定義關聯(lián)查詢,延遲加載和即時加載都可以實現(xiàn),如:

$customer = Customer::findOne(1);
// 延遲加載: SELECT * FROM order WHERE customer_id=1 AND subtotal>100$orders = $customer->getOrders()->where('subtotal>100')->all();

// 即時加載: SELECT * FROM customer LIMIT 100//          SELECT * FROM order WHERE customer_id IN (1,2,...) AND subtotal>100$customers = Customer::find()->limit(100)->with([
    'orders' => function($query) {
        $query->andWhere('subtotal>100');
    },
])->all();

逆關系

關聯(lián)關系通常成對定義,如:Customer 可以有個名為 orders 關聯(lián)項, 而 Order 也有個名為customer 的關聯(lián)項:

class Customer extends ActiveRecord{
    ....
    public function getOrders()
    {
        return $this->hasMany(Order::className(), ['customer_id' => 'id']);
    }
}

class Order extends ActiveRecord{
    ....
    public function getCustomer()
    {
        return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
    }
}

如果我們執(zhí)行以下查詢,可以發(fā)現(xiàn)訂單的 customer 和 找到這些訂單的客戶對象并不是同一個。連接 customer->orders 將觸發(fā)一條 SQL 語句 而連接一個訂單的 customer 將觸發(fā)另一條 SQL 語句。

// SELECT * FROM customer WHERE id=1$customer = Customer::findOne(1);
// 輸出 "不相同"// SELECT * FROM order WHERE customer_id=1// SELECT * FROM customer WHERE id=1if ($customer->orders[0]->customer === $customer) {
    echo '相同';
} else {
    echo '不相同';
}

為避免多余執(zhí)行的后一條語句,我們可以為 customer或 orders 關聯(lián)關系定義相反的關聯(lián)關系,通過調用 yii\db\ActiveQuery::inverseOf() 方法可以實現(xiàn)。

class Customer extends ActiveRecord{
    ....
    public function getOrders()
    {
        return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer');
    }
}

現(xiàn)在我們同樣執(zhí)行上面的查詢,我們將得到:

// SELECT * FROM customer WHERE id=1$customer = Customer::findOne(1);
// 輸出相同// SELECT * FROM order WHERE customer_id=1if ($customer->orders[0]->customer === $customer) {
    echo '相同';
} else {
    echo '不相同';
}

以上我們展示了如何在延遲加載中使用相對關聯(lián)關系, 相對關系也可以用在即時加載中:

// SELECT * FROM customer// SELECT * FROM order WHERE customer_id IN (1, 2, ...)$customers = Customer::find()->with('orders')->all();
// 輸出相同if ($customers[0]->orders[0]->customer === $customers[0]) {
    echo '相同';
} else {
    echo '不相同';
}

>注意:相對關系不能在包含中間表的關聯(lián)關系中定義。 即是,如果你的關系是通過yii\db\ActiveQuery::via() 或 yii\db\ActiveQuery::viaTable()方法定義的, 就不能調用yii\db\ActiveQuery::inverseOf()方法了。

JOIN 類型關聯(lián)查詢

使用關系數(shù)據(jù)庫時,普遍要做的是連接多個表并明確地運用各種 JOIN 查詢。 JOIN SQL語句的查詢條件和參數(shù),使用 yii\db\ActiveQuery::joinWith() 可以重用已定義關系并調用 而不是使用 yii\db\ActiveQuery::join() 來實現(xiàn)目標。

// 查找所有訂單并以客戶 ID 和訂單 ID 排序,并貪婪加載 "customer" 表$orders = Order::find()->joinWith('customer')->orderBy('customer.id, order.id')->all();
// 查找包括書籍的所有訂單,并以 `INNER JOIN` 的連接方式即時加載 "books" 表$orders = Order::find()->innerJoinWith('books')->all();

以上,方法 yii\db\ActiveQuery::innerJoinWith() 是訪問?INNER JOIN?類型的 yii\db\ActiveQuery::joinWith() 的快捷方式。

可以連接一個或多個關聯(lián)關系,可以自由使用查詢條件到關聯(lián)查詢, 也可以嵌套連接關聯(lián)查詢。如:

// 連接多重關系// 找出24小時內注冊客戶包含書籍的訂單$orders = Order::find()->innerJoinWith([
    'books',
    'customer' => function ($query) {
        $query->where('customer.created_at > ' . (time() - 24 * 3600));
    }
])->all();
// 連接嵌套關系:連接 books 表及其 author 列$orders = Order::find()->joinWith('books.author')->all();

代碼背后, Yii 先執(zhí)行一條 JOIN SQL 語句把滿足 JOIN SQL 語句查詢條件的主要模型查出, 然后為每個關系執(zhí)行一條查詢語句, bing填充相應的關聯(lián)記錄。

yii\db\ActiveQuery::joinWith() 和 yii\db\ActiveQuery::with() 的區(qū)別是 前者連接主模型類和關聯(lián)模型類的數(shù)據(jù)表來檢索主模型, 而后者只查詢和檢索主模型類。 檢索主模型

由于這個區(qū)別,你可以應用只針對一條 JOIN SQL 語句起效的查詢條件。 如,通過關聯(lián)模型的查詢條件過濾主模型,如前例, 可以使用關聯(lián)表的列來挑選主模型數(shù)據(jù),

當使用 yii\db\ActiveQuery::joinWith() 方法時可以響應沒有歧義的列名。 In the above examples, we use?item.id?and?order.id?to disambiguate the?id?column references 因為訂單表和項目表都包括?id?列。

當連接關聯(lián)關系時,關聯(lián)關系默認使用即時加載。你可以 通過傳參數(shù)?$eagerLoading?來決定在指定關聯(lián)查詢中是否使用即時加載。

默認 yii\db\ActiveQuery::joinWith() 使用左連接來連接關聯(lián)表。 你也可以傳?$joinType?參數(shù)來定制連接類型。 你也可以使用 yii\db\ActiveQuery::innerJoinWith()。

以下是?INNER JOIN?的簡短例子:

// 查找包括書籍的所有訂單,但 "books" 表不使用即時加載$orders = Order::find()->innerJoinWith('books', false)->all();
// 等價于:$orders = Order::find()->joinWith('books', false, 'INNER JOIN')->all();

有時連接兩個表時,需要在關聯(lián)查詢的 ON 部分指定額外條件。 這可以通過調用 yii\db\ActiveQuery::onCondition() 方法實現(xiàn):

class User extends ActiveRecord{
    public function getBooks()
    {
        return $this->hasMany(Item::className(), ['owner_id' => 'id'])->onCondition(['category_id' => 1]);
    }
}

在上面, yii\db\ActiveRecord::hasMany() 方法回傳了一個 yii\db\ActiveQuery 對象, 當你用 yii\db\ActiveQuery::joinWith() 執(zhí)行一條查詢時,取決于正被調用的是哪個 yii\db\ActiveQuery::onCondition(), 返回?category_id?為 1 的 items

當你用 yii\db\ActiveQuery::joinWith() 進行一次查詢時,“on-condition”條件會被放置在相應查詢語句的 ON 部分, 如:

// SELECT user.* FROM user LEFT JOIN item ON item.owner_id=user.id AND category_id=1// SELECT * FROM item WHERE owner_id IN (...) AND category_id=1$users = User::find()->joinWith('books')->all();

注意:如果通過 yii\db\ActiveQuery::with() 進行貪婪加載或使用惰性加載的話,則 on 條件會被放置在對應 SQL語句的?WHERE?部分。 因為,此時此處并沒有發(fā)生 JOIN 查詢。比如:

// SELECT * FROM user WHERE id=10$user = User::findOne(10);
// SELECT * FROM item WHERE owner_id=10 AND category_id=1$books = $user->books;

關聯(lián)表操作

AR 提供了下面兩個方法用來建立和解除兩個關聯(lián)對象之間的關系:

  • yii\db\ActiveRecord::link()
  • yii\db\ActiveRecord::unlink()

例如,給定一個customer和order對象,我們可以通過下面的代碼使得customer對象擁有order對象:

$customer = Customer::findOne(1);
$order = new Order();
$order->subtotal = 100;
$customer->link('orders', $order);

yii\db\ActiveRecord::link() 調用上述將設置 customer_id 的順序是 $customer 的主鍵值,然后調用 yii\db\ActiveRecord::save() 要將順序保存到數(shù)據(jù)庫中。

作用域

當你調用yii\db\ActiveRecord::find() 或 yii\db\ActiveRecord::findBySql()方法時,將會返回一個yii\db\ActiveQuery實例。之后,你可以調用其他查詢方法,如 yii\db\ActiveQuery::where(),yii\db\ActiveQuery::orderBy(), 進一步的指定查詢條件。

有時候你可能需要在不同的地方使用相同的查詢方法。如果出現(xiàn)這種情況,你應該考慮定義所謂的作用域。作用域是本質上要求一組的查詢方法來修改查詢對象的自定義查詢類中定義的方法。 之后你就可以像使用普通方法一樣使用作用域。

只需兩步即可定義一個作用域。首先給你的model創(chuàng)建一個自定義的查詢類,在此類中定義的所需的范圍方法。例如,給Comment模型創(chuàng)建一個 CommentQuery類,然后在CommentQuery類中定義一個active()的方法為作用域,像下面的代碼:

namespace app\models;

use yii\db\ActiveQuery;

class CommentQuery extends ActiveQuery{
    public function active($state = true)
    {
        $this->andWhere(['active' => $state]);
        return $this;
    }
}

重點:

  1. 類必須繼承 yii\db\ActiveQuery (或者是其他的 ActiveQuery ,比如 yii\mongodb\ActiveQuery)。
  2. 必須是一個public類型的方法且必須返回 $this 實現(xiàn)鏈式操作??梢詡魅?yún)?shù)。
  3. 檢查 yii\db\ActiveQuery 對于修改查詢條件是非常有用的方法。

其次,覆蓋yii\db\ActiveRecord::find() 方法使其返回自定義的查詢對象而不是常規(guī)的yii\db\ActiveQuery。對于上述例子,你需要編寫如下代碼:

namespace app\models;

use yii\db\ActiveRecord;

class Comment extends ActiveRecord{
    
    public static function find()
    {
        return new CommentQuery(get_called_class());
    }
}

就這樣,現(xiàn)在你可以使用自定義的作用域方法了:

$comments = Comment::find()->active()->all();
$inactiveComments = Comment::find()->active(false)->all();

你也能在定義的關聯(lián)里使用作用域方法,比如:

class Post extends \yii\db\ActiveRecord{
    public function getActiveComments()
    {
        return $this->hasMany(Comment::className(), ['post_id' => 'id'])->active();

    }
}

或者在執(zhí)行關聯(lián)查詢的時候使用(on-the-fly 是啥?):

$posts = Post::find()->with([
    'comments' => function($q) {
        $q->active();
    }
])->all();

默認作用域

如果你之前用過 Yii 1.1 就應該知道默認作用域的概念。一個默認的作用域可以作用于所有查詢。你可以很容易的通過重寫yii\db\ActiveRecord::find()方法來定義一個默認作用域,例如:

public static function find(){
    return parent::find()->where(['deleted' => false]);
}

注意,你之后所有的查詢都不能用 yii\db\ActiveQuery::where(),但是可以用 yii\db\ActiveQuery::andWhere() 和 yii\db\ActiveQuery::orWhere(),他們不會覆蓋掉默認作用域。(譯注:如果你要使用默認作用域,就不能在 xxx::find()后使用where()方法,你必須使用andXXX()或者orXXX()系的方法,否則默認作用域不會起效果,至于原因,打開where()方法的代碼一看便知)

事務操作

當執(zhí)行幾個相關聯(lián)的數(shù)據(jù)庫操作的時候

TODO: FIXME: WIP, TBD,?https://github.com/yiisoft/yii2/issues/226

, yii\db\ActiveRecord::afterSave(), yii\db\ActiveRecord::beforeDelete() and/or yii\db\ActiveRecord::afterDelete() 生命周期周期方法(life cycle methods 我覺得這句翻譯成“模板方法”會不會更好點?)。開發(fā)者可以通過重寫yii\db\ActiveRecord::save()方法然后在控制器里使用事務操作,嚴格地說是似乎不是一個好的做法 (召回"瘦控制器 / 肥模型"基本規(guī)則)。

這些方法在這里(如果你不明白自己實際在干什么,請不要使用他們),Models:

class Feature extends \yii\db\ActiveRecord{
    // ...

    public function getProduct()
    {
        return $this->hasOne(Product::className(), ['id' => 'product_id']);
    }
}

class Product extends \yii\db\ActiveRecord{
    // ...

    public function getFeatures()
    {
        return $this->hasMany(Feature::className(), ['product_id' => 'id']);
    }
}

重寫 yii\db\ActiveRecord::save() 方法:

class ProductController extends \yii\web\Controller{
    public function actionCreate()
    {
        // FIXME: TODO: WIP, TBD
    }
}

(譯注:我覺得上面應該是原手冊里的bug)

在控制器層使用事務:

class ProductController extends \yii\web\Controller{
    public function actionCreate()
    {
        // FIXME: TODO: WIP, TBD
    }
}

作為這些脆弱方法的替代,你應該使用原子操作方案特性。

class Feature extends \yii\db\ActiveRecord{
    // ...

    public function getProduct()
    {
        return $this->hasOne(Product::className(), ['product_id' => 'id']);
    }

    public function scenarios()
    {
        return [
            'userCreates' => [
                'attributes' => ['name', 'value'],
                'atomic' => [self::OP_INSERT],
            ],
        ];
    }
}

class Product extends \yii\db\ActiveRecord{
    // ...

    public function getFeatures()
    {
        return $this->hasMany(Feature::className(), ['id' => 'product_id']);
    }

    public function scenarios()
    {
        return [
            'userCreates' => [
                'attributes' => ['title', 'price'],
                'atomic' => [self::OP_INSERT],
            ],
        ];
    }

    public function afterValidate()
    {
        parent::afterValidate();
        // FIXME: TODO: WIP, TBD
    }

    public function afterSave($insert)
    {
        parent::afterSave($insert);
        if ($this->getScenario() === 'userCreates') {
            // FIXME: TODO: WIP, TBD
        }
    }
}

Controller里的代碼將變得很簡潔:

class ProductController extends \yii\web\Controller{
    public function actionCreate()
    {
        // FIXME: TODO: WIP, TBD
    }
}

控制器非常簡潔:

class ProductController extends \yii\web\Controller{
    public function actionCreate()
    {
        // FIXME: TODO: WIP, TBD
    }
}

樂觀鎖(Optimistic Locks)

TODO

被污染屬性

當你調用yii\db\ActiveRecord::save()用于保存活動記錄(Active Record)實例時,只有被污染的屬性才會被保存。一個屬性是否認定為被污染取決于它的值自從最后一次從數(shù)據(jù)庫加載或者最近一次保存到數(shù)據(jù)庫后到現(xiàn)在是否被修改過。注意:無論活動記錄(Active Record)是否有被污染屬性,數(shù)據(jù)驗證始終會執(zhí)行。

活動記錄(Active Record)會自動維護一個污染數(shù)據(jù)列表。它的工作方式是通過維護一個較舊屬性值版本,并且將它們與最新的進行比較。你可以通過調用yii\db\ActiveRecord::getDirtyAttributes()來獲取當前的污染屬性。你也可以調用yii\db\ActiveRecord::markAttributeDirty()來顯示的標記一個屬性為污染屬性。

如果你對最近一次修改前的屬性值感興趣,你可以調用yii\db\ActiveRecord::getOldAttributes() 或 yii\db\ActiveRecord::getOldAttribute()。

另見

  • 模型(Model)
  • yii\db\ActiveRecord
Artikel sebelumnya: Artikel seterusnya: