摘要:建立關聯關系后,通過可以獲取一個對象的數組,該數組代表當前客戶對象的訂單集。定義關聯關系使用一個可以返回對象的方法,對象有關聯上下文的相關信息,因此可以只查詢關聯數據。基于表外鍵定義關聯關系是最佳方法。
簡介
Yii 在操作數據庫方面提供了一個十分強大的類庫來支撐整個框架業務的運轉,這就是 Active Record (活動記錄,以下簡稱AR)。
基本概念AR類提供了一個面向對象的接口, 用以訪問數據庫中的數據。
例如,假定 Customer AR 類關聯著 customer 表,且該類的 name 屬性代表 customer 表的 name 列。 你可以寫以下代碼來哉customer 表里插入一行新的記錄:
用 AR 而不是原生的 SQL 語句去執行數據庫查詢,可以調用直觀方法來實現相同目標。
如,調用 yiidbActiveRecord::save() 方法將執行插入或更新輪詢,將在該 AR 類關聯的數據表新建或更新一行數據:
$customer = new Customer(); $customer->name = "Qiang"; $customer->save(); // 一行新數據插入 customer 表
上面的代碼和使用下面的原生 SQL 語句是等效的,但顯然前者更直觀, 更不易出錯,并且面對不同的數據庫系統(DBMS, Database Management System)時更不容易產生兼容性問題。
$db->createCommand("INSERT INTO customer (name) VALUES (:name)", [ ":name" => "Qiang", ])->execute();
下面是所有目前被 Yii 的 AR 功能所支持的數據庫列表:
MySQL 4.1 及以上:通過 yiidbActiveRecord
PostgreSQL 7.3 及以上:通過 yiidbActiveRecord
SQLite 2 和 3:通過 yiidbActiveRecord
Microsoft SQL Server 2010 及以上:通過 yiidbActiveRecord
Oracle: 通過 yiidbActiveRecord
CUBRID 9.1 及以上:通過 yiidbActiveRecord
Sphinx:通過 yiisphinxActiveRecord,需求 yii2-sphinx 擴展
ElasticSearch:通過 yiielasticsearchActiveRecord,需求 yii2-elasticsearch 擴展
Redis 2.6.12 及以上:通過 yiiredisActiveRecord,需求 yii2-redis 擴展
MongoDB 1.3.0 及以上:通過 yiimongodbActiveRecord,需求 yii2-mongodb 擴展
如你所見,Yii 不僅提供了對關系型數據庫的 AR 支持,還提供了 NoSQL 數據庫的支持。
聲明 AR 類要想聲明一個 AR 類,你需要擴展 yiidbActiveRecord 基類, 并實現 tableName 方法,返回與之相關聯的的數據表的名稱:
namespace appmodels; use yiidbActiveRecord; class Customer extends ActiveRecord{ /** * @return string 返回該AR類關聯的數據表名 */ public static function tableName() { return "customer"; } }訪問列數據
AR 把相應數據行的每一個字段映射為 AR 對象的一個個特性變量(Attribute) 一個特性就好像一個普通對象的公共屬性一樣(public property)。
特性變量的名稱和對應字段的名稱是一樣的,且大小姓名。
使用以下語法讀取列的值:
// "id" 和 "mail" 是 $customer 對象所關聯的數據表的對應字段名 $id = $customer->id; $email = $customer->email;
要改變列值,只要給關聯屬性賦新值并保存對象即可:
$customer->email = "james@example.com"; $customer->save();建立數據庫連接
AR 用一個 yiidbConnection 對象與數據庫交換數據。
默認的,它使用 db 組件作為其連接對象,你可以在應用程序配置文件中設置下 db 組件,就像這樣:
return [ "components" => [ "db" => [ "class" => "yiidbConnection", "dsn" => "mysql:host=localhost;dbname=testdb", "username" => "demo", "password" => "demo", ], ], ];
如果在你的應用中應用了不止一個數據庫,且你需要給你的 AR 類使用不同的數據庫鏈接(DB connection) ,你可以覆蓋掉 yiidbActiveRecord::getDb() 方法:
class Customer extends ActiveRecord{ // ... public static function getDb() { return Yii::$app->db2; // 使用名為 "db2" 的應用組件 } }查詢數據
AR 提供了兩種方法來構建 DB 查詢并向 AR 實例里填充數據:
yiidbActiveRecord::find()
yiidbActiveRecord::findBySql()
以上兩個方法都會返回 yiidbActiveQuery 實例,該類繼承自yiidbQuery, 因此,他們都支持同一套靈活且強大的 DB 查詢方法,如where(),join(),orderBy(),等等。
下面的這些案例展示了一些可能的玩法:
// 取回所有活躍客戶(狀態為 *active* 的客戶)并以他們的 ID 排序: $customers = Customer::find() ->where(["status" => Customer::STATUS_ACTIVE]) ->orderBy("id") ->all(); // 返回ID為1的客戶: $customer = Customer::find() ->where(["id" => 1]) ->one(); // 取回活躍客戶的數量: $count = Customer::find() ->where(["status" => Customer::STATUS_ACTIVE]) ->count(); // 以客戶ID索引結果集: // $customers 數組以 ID 為索引 $customers = Customer::find()->indexBy("id")->all(); // 用原生 SQL 語句檢索客戶: $sql = "SELECT * FROM customer"; $customers = Customer::findBySql($sql)->all();
小技巧:在上面的代碼中,Customer::STATUS_ACTIVE 是一個在 Customer 類里定義的常量。(譯注:這種常量的值一般都是tinyint)相較于直接在代碼中寫死字符串或數字,使用一個更有意義的常量名稱是一種更好的編程習慣。
有兩個快捷方法:findOne 和 findAll() 用來返回一個或者一組ActiveRecord實例。前者返回第一個匹配到的實例,后者返回所有。 例如:
// 返回 id 為 1 的客戶 $customer = Customer::findOne(1); // 返回 id 為 1 且狀態為 *active* 的客戶 $customer = Customer::findOne([ "id" => 1, "status" => Customer::STATUS_ACTIVE, ]); // 返回id為1、2、3的一組客戶 $customers = Customer::findAll([1, 2, 3]); // 返回所有狀態為 "deleted" 的客戶 $customer = Customer::findAll([ "status" => Customer::STATUS_DELETED, ]);以數組形式獲取數據
有時候,我們需要處理很大量的數據,這時可能需要用一個數組來存儲取到的數據, 從而節省內存。
你可以用 asArray() 函數做到這一點:
// 以數組而不是對象形式取回客戶信息: $customers = Customer::find() ->asArray() ->all(); // $customers 的每個元素都是鍵值對數組批量獲取數據
在 Query Builder(查詢構造器) 里,我們已經解釋了當需要從數據庫中查詢大量數據時,你可以用 batch query(批量查詢)來限制內存的占用。
你可能也想在 AR 里使用相同的技巧,比如這樣……
// 一次提取 10 個客戶信息 foreach (Customer::find()->batch(10) as $customers) { // $customers 是 10 個或更少的客戶對象的數組 } // 一次提取 10 個客戶并一個一個地遍歷處理 foreach (Customer::find()->each(10) as $customer) { // $customer 是一個 ”Customer“ 對象 } // 貪婪加載模式的批處理查詢 foreach (Customer::find()->with("orders")->each() as $customer) { }操作數據(CURD)
AR 提供以下方法插入、更新和刪除與 AR 對象關聯的那張表中的某一行:
yiidbActiveRecord::save()
yiidbActiveRecord::insert()
yiidbActiveRecord::update()
yiidbActiveRecord::delete()
AR 同時提供了一些靜態方法,可以應用在與某 AR 類所關聯的整張表上。
用這些方法的時候千萬要小心,因為他們作用于整張表!
比如,deleteAll() 會刪除掉表里所有的記錄。
yiidbActiveRecord::updateCounters()
yiidbActiveRecord::updateAll()
yiidbActiveRecord::updateAllCounters()
yiidbActiveRecord::deleteAll()
下面的這些例子里,詳細展現了如何使用這些方法:
// 插入新客戶的記錄 $customer = new Customer(); $customer->name = "James"; $customer->email = "james@example.com"; $customer->save(); // 等同于 $customer->insert(); // 更新現有客戶記錄 $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 對象是不是新對象(在函數內部,他會檢查 yiidbActiveRecord::isNewRecord 的值)。
若 AR 對象是由 new 操作符 初始化出來的,save() 方法會在表里插入一條數據; 如果一個 AR 是由 find() 方法獲取來的, 則 save() 會更新表里的對應行記錄。
由于AR繼承自yiibaseModel,所以它同樣也支持Model的數據輸入、驗證等特性。
例如,你可以聲明一個rules方法用來覆蓋掉yiibaseModel::rules()里的;你也可以給AR實例批量賦值;你也可以通過調用yiibaseModel::validate()執行數據驗證。
當你調用 save()、insert()、update() 這三個方法時,會自動調用yiibaseModel::validate()方法。如果驗證失敗,數據將不會保存進數據庫。
下面的例子演示了如何使用AR 獲取/驗證用戶輸入的數據并將他們保存進數據庫:
// 新建一條記錄 $model = new Customer; if ($model->load(Yii::$app->request->post()) && $model->save()) { // 獲取用戶輸入的數據,驗證并保存 } // 更新主鍵為$id的AR $model = Customer::findOne($id); if ($model === null) { throw new NotFoundHttpException; } if ($model->load(Yii::$app->request->post()) && $model->save()) { // 獲取用戶輸入的數據,驗證并保存 }讀取默認值
你的表列也許定義了默認值。有時候,你可能需要在使用web表單的時候給AR預設一些值。
如果你需要這樣做,可以在顯示表單內容前通過調用loadDefaultValues()方法來實現:
loadDefaultValues(); // ... 渲染 $customer 的 HTML 表單 ... ?>AR的生命周期
理解AR的生命周期對于你操作數據庫非常重要。
生命周期通常都會有些典型的事件存在。
對于開發AR的behaviors來說非常有用。
當你實例化一個新的AR對象時,我們將獲得如下的生命周期:
1. constructor 2. yiidbActiveRecord::init(): 會觸發一個 yiidbActiveRecord::EVENT_INIT 事件
當你通過 yiidbActiveRecord::find() 方法查詢數據時,每個AR實例都將有以下生命周期:
1. constructor 2. yiidbActiveRecord::init(): 會觸發一個 yiidbActiveRecord::EVENT_INIT 事件 3. yiidbActiveRecord::afterFind(): 會觸發一個 yiidbActiveRecord::EVENT_AFTER_FIND 事件
當通過 yiidbActiveRecord::save() 方法寫入或者更新數據時, 我們將獲得如下生命周期:
1. yiidbActiveRecord::beforeValidate(): 會觸發一個 yiidbActiveRecord::EVENT_BEFORE_VALIDATE 事件 2. yiidbActiveRecord::afterValidate(): 會觸發一個 yiidbActiveRecord::EVENT_AFTER_VALIDATE 事件 3. yiidbActiveRecord::beforeSave(): 會觸發一個 yiidbActiveRecord::EVENT_BEFORE_INSERT 或 yiidbActiveRecord::EVENT_BEFORE_UPDATE 事件 4. 執行實際的數據寫入或更新 5. yiidbActiveRecord::afterSave(): 會觸發一個 yiidbActiveRecord::EVENT_AFTER_INSERT 或 yiidbActiveRecord::EVENT_AFTER_UPDATE 事件
最后,當調用 yiidbActiveRecord::delete() 刪除數據時, 我們將獲得如下生命周期:
1. yiidbActiveRecord::beforeDelete(): 會觸發一個 yiidbActiveRecord::EVENT_BEFORE_DELETE 事件 2. 執行實際的數據刪除 3. yiidbActiveRecord::afterDelete(): 會觸發一個 yiidbActiveRecord::EVENT_AFTER_DELETE 事件查詢關聯的數據
使用 AR 方法也可以查詢數據表的關聯數據(如,選出表A的數據可以拉出表B的關聯數據)。
有了 AR, 返回的關聯數據連接就像連接關聯主表的 AR 對象的屬性一樣。
建立關聯關系后,通過 $customer->orders 可以獲取 一個 Order 對象的數組,該數組代表當前客戶對象的訂單集。
定義關聯關系使用一個可以返回 yiidbActiveQuery 對象的 getter 方法, yiidbActiveQuery對象有關聯上下文的相關信息,因此可以只查詢關聯數據。
例如:
class Customer extends yiidbActiveRecord{ public function getOrders() { // 客戶和訂單通過 Order.customer_id -> id 關聯建立一對多關系 return $this->hasMany(Order::className(), ["customer_id" => "id"]); } } class Order extends yiidbActiveRecord{ // 訂單和客戶通過 Customer.id -> customer_id 關聯建立一對一關系 public function getCustomer() { return $this->hasOne(Customer::className(), ["id" => "customer_id"]); } }
以上使用了 yiidbActiveRecord::hasMany() 和 yiidbActiveRecord::hasOne() 方法。
以上兩例分別是關聯數據多對一關系和一對一關系的建模范例。
如,一個客戶有很多訂單,一個訂單只歸屬一個客戶。
兩個方法都有兩個參數并返回 yiidbActiveQuery 對象。
$class:關聯模型類名,它必須是一個完全合格的類名。 $link: 兩個表的關聯列,應為鍵值對數組的形式。 數組的鍵是 $class 關聯表的列名, 而數組值是關聯類 $class 的列名。 基于表外鍵定義關聯關系是最佳方法。
建立關聯關系后,獲取關聯數據和獲取組件屬性一樣簡單, 執行以下相應getter方法即可:
// 取得客戶的訂單 $customer = Customer::findOne(1); $orders = $customer->orders; // $orders 是 Order 對象數組
以上代碼實際執行了以下兩條 SQL 語句:
SELECT * FROM customer WHERE id=1; SELECT * FROM order WHERE customer_id=1;
提示:再次用表達式 $customer->orders將不會執行第二次 SQL 查詢, SQL 查詢只在該表達式第一次使用時執行。
數據庫訪問只返回緩存在內部前一次取回的結果集,如果你想查詢新的 關聯數據,先要注銷現有結果集:
unset($customer->orders);。
有時候需要在關聯查詢中傳遞參數,如不需要返回客戶全部訂單, 只需要返回購買金額超過設定值的大訂單, 通過以下getter方法聲明一個關聯數據 bigOrders :
class Customer extends yiidbActiveRecord{ public function getBigOrders($threshold = 100) { return $this->hasMany(Order::className(), ["customer_id" => "id"]) ->where("subtotal > :threshold", [":threshold" => $threshold]) ->orderBy("id"); } }
hasMany() 返回 yiidbActiveQuery 對象,該對象允許你通過 yiidbActiveQuery 方法定制查詢。
如上聲明后,執行 $customer->bigOrders 就返回 總額大于100的訂單。使用以下代碼更改設定值:
$orders = $customer->getBigOrders(200)->all();
注意:關聯查詢返回的是 yiidbActiveQuery 的實例,如果像特性(如類屬性)那樣連接關聯數據, 返回的結果是關聯查詢的結果,即 yiidbActiveRecord 的實例, 或者是數組,或者是 null ,取決于關聯關系的多樣性。
如,$customer->getOrders() 返回ActiveQuery 實例,而 $customer->orders 返回Order 對象數組 (如果查詢結果為空則返回空數組)。中間關聯表
有時,兩個表通過中間表關聯,定義這樣的關聯關系, 可以通過調用 yiidbActiveQuery::via() 方法或 yiidbActiveQuery::viaTable() 方法來定制 yiidbActiveQuery 對象 。
舉例而言,如果 order 表和 item 表通過中間表 order_item 關聯起來, 可以在 Order 類聲明 items 關聯關系取代中間表:
class Order extends yiidbActiveRecord{ public function getItems() { return $this->hasMany(Item::className(), ["id" => "item_id"]) ->viaTable("order_item", ["order_id" => "id"]); } }
兩個方法是相似的,除了 yiidbActiveQuery::via() 方法的第一個參數是使用 AR 類中定義的關聯名。 以上方法取代了中間表,等價于:
class Order extends yiidbActiveRecord{ public function getOrderItems() { return $this->hasMany(OrderItem::className(), ["order_id" => "id"]); } public function getItems() { return $this->hasMany(Item::className(), ["id" => "item_id"]) ->via("orderItems"); } }延遲加載和即時加載(又稱惰性加載與貪婪加載)
如前所述,當你第一次連接關聯對象時, AR 將執行一個數據庫查詢 來檢索請求數據并填充到關聯對象的相應屬性。如果再次連接相同的關聯對象,不再執行任何查詢語句,這種數據庫查詢的執行方法稱為“延遲加載”。
如:
// 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 語句被執行 $orders2 = $customer->orders; //取回上次查詢的緩存數據
延遲加載非常實用,但是,在以下場景中使用延遲加載會遭遇性能問題:
// 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... }
假設數據庫查出的客戶超過100個,以上代碼將執行多少條 SQL 語句? 101 條!第一條 SQL 查詢語句取回100個客戶,然后, 每個客戶要執行一條 SQL 查詢語句以取回該客戶的所有訂單。
為解決以上性能問題,可以通過調用 yiidbActiveQuery::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 語句被執行 $orders = $customer->orders; // ...處理 $orders... }
如你所見,同樣的任務只需要兩個 SQL 語句。
須知:通常,即時加載 N 個關聯關系而通過 via() 或者 viaTable() 定義了 M 個關聯關系, 將有 1+M+N 條 SQL 查詢語句被執行:一個查詢取回主表行數, 一個查詢給每一個 (M) 中間表,一個查詢給每個 (N) 關聯表。
注意:當用即時加載定制 select() 時,確保連接 到關聯模型的列都被包括了,否則,關聯模型不會載入。如:
$orders = Order::find() ->select(["id", "amount"]) ->with("customer") ->all(); // $orders[0]->customer 總是空的,使用以下代碼解決這個問題: $orders = Order::find() ->select(["id", "amount", "customer_id"]) ->with("customer") ->all();
有時候,你想自由的自定義關聯查詢,延遲加載和即時加載都可以實現,如:
$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();逆關系
關聯關系通常成對定義,如:Customer 可以有個名為 orders 關聯項, 而 Order 也有個名為customer 的關聯項:
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"]); } }
如果我們執行以下查詢,可以發現訂單的 customer 和 找到這些訂單的客戶對象并不是同一個。
連接 customer->orders 將觸發一條 SQL 語句 而連接一個訂單的 customer 將觸發另一條 SQL 語句。
// SELECT * FROM customer WHERE id=1 $customer = Customer::findOne(1); // 輸出 "不相同" // SELECT * FROM order WHERE customer_id=1 // SELECT * FROM customer WHERE id=1 if ($customer->orders[0]->customer === $customer) { echo "相同"; } else { echo "不相同"; }
為避免多余執行的后一條語句,我們可以為 customer或 orders 關聯關系定義相反的關聯關系,通過調用 yiidbActiveQuery::inverseOf() 方法可以實現。
class Customer extends ActiveRecord{ .... public function getOrders() { return $this->hasMany(Order::className(), ["customer_id" => "id"]) ->inverseOf("customer"); } }
現在我們同樣執行上面的查詢,我們將得到:
// SELECT * FROM customer WHERE id=1 $customer = Customer::findOne(1); // 輸出相同 // SELECT * FROM order WHERE customer_id=1 if ($customer->orders[0]->customer === $customer) { echo "相同"; } else { echo "不相同"; }
以上我們展示了如何在延遲加載中使用相對關聯關系, 相對關系也可以用在即時加載中:
// 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 "不相同"; }
注意:相對關系不能在包含中間表的關聯關系中定義。
即是,如果你的關系是通過yiidbActiveQuery::via() 或 yiidbActiveQuery::viaTable()方法定義的, 就不能調用yiidbActiveQuery::inverseOf()方法了。JOIN 類型關聯查詢
使用關系數據庫時,普遍要做的是連接多個表并明確地運用各種 JOIN 查詢。
JOIN SQL語句的查詢條件和參數,使用 yiidbActiveQuery::joinWith() 可以重用已定義關系并調用 而不是使用 yiidbActiveQuery::join() 來實現目標。
// 查找所有訂單并以客戶 ID 和訂單 ID 排序,并貪婪加載 "customer" 表 $orders = Order::find() ->joinWith("customer") ->orderBy("customer.id, order.id") ->all(); // 查找包括書籍的所有訂單,并以 `INNER JOIN` 的連接方式即時加載 "books" 表 $orders = Order::find() ->innerJoinWith("books") ->all();
以上,方法 yiidbActiveQuery::innerJoinWith() 是訪問 INNER JOIN 類型的 yiidbActiveQuery::joinWith() 的快捷方式。
可以連接一個或多個關聯關系,可以自由使用查詢條件到關聯查詢, 也可以嵌套連接關聯查詢。如:
// 連接多重關系 // 找出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 先執行一條 JOIN SQL 語句把滿足 JOIN SQL 語句查詢條件的主要模型查出, 然后為每個關系執行一條查詢語句, bing填充相應的關聯記錄。
yiidbActiveQuery::joinWith() 和 yiidbActiveQuery::with() 的區別是: 前者連接主模型類和關聯模型類的數據表來檢索主模型, 而后者只查詢和檢索主模型類。 檢索主模型由于這個區別,你可以應用只針對一條 JOIN SQL 語句起效的查詢條件。 如,通過關聯模型的查詢條件過濾主模型,如前例, 可以使用關聯表的列來挑選主模型數據, 當使用 yiidbActiveQuery::joinWith() 方法時可以響應沒有歧義的列名。 當連接關聯關系時,關聯關系默認使用即時加載。 你可以 通過傳參數 $eagerLoading 來決定在指定關聯查詢中是否使用即時加載。 默認 yiidbActiveQuery::joinWith() 使用左連接來連接關聯表。 你也可以傳 $joinType 參數來定制連接類型。 你也可以使用 yiidbActiveQuery::innerJoinWith()。
以下是 INNER JOIN 的簡短例子:
// 查找包括書籍的所有訂單,但 "books" 表不使用即時加載 $orders = Order::find() ->innerJoinWith("books", false) ->all(); // 等價于: $orders = Order::find() ->joinWith("books", false, "INNER JOIN") ->all();
有時連接兩個表時,需要在關聯查詢的 ON 部分指定額外條件。
這可以通過調用 yiidbActiveQuery::onCondition() 方法實現:
class User extends ActiveRecord{ public function getBooks() { return $this->hasMany(Item::className(), ["owner_id" => "id"]) ->onCondition(["category_id" => 1]); } }
在上面, yiidbActiveRecord::hasMany() 方法回傳了一個 yiidbActiveQuery 對象, 當你用 yiidbActiveQuery::joinWith() 執行一條查詢時,取決于正被調用的是哪個 yiidbActiveQuery::onCondition(), 返回 category_id 為 1 的 items。
當你用 yiidbActiveQuery::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();
注意:如果通過 yiidbActiveQuery::with() 進行貪婪加載或使用惰性加載的話,則 on 條件會被放置在對應 SQL語句的 WHERE 部分。 因為,此時此處并沒有發生 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;關聯表操作
AR 提供了下面兩個方法用來建立和解除兩個關聯對象之間的關系:
yiidbActiveRecord::link()
yiidbActiveRecord::unlink()
例如,給定一個customer和order對象,我們可以通過下面的代碼使得customer對象擁有order對象:
$customer = Customer::findOne(1); $order = new Order(); $order->subtotal = 100; $customer->link("orders", $order);
作用域yiidbActiveRecord::link() 調用上述將設置 customer_id 的順序是 $customer 的主鍵值,然后調用 yiidbActiveRecord::save() 要將順序保存到數據庫中。
當你調用yiidbActiveRecord::find() 或 yiidbActiveRecord::findBySql()方法時,將會返回一個yiidbActiveQuery實例。
之后,你可以調用其他查詢方法,如 yiidbActiveQuery::where(),yiidbActiveQuery::orderBy(), 進一步的指定查詢條件。
有時候你可能需要在不同的地方使用相同的查詢方法。如果出現這種情況,你應該考慮定義所謂的作用域。
作用域是本質上要求一組的查詢方法來修改查詢對象的自定義查詢類中定義的方法。
之后你就可以像使用普通方法一樣使用作用域。只需兩步即可定義一個作用域。
首先給你的model創建一個自定義的查詢類,在此類中定義的所需的范圍方法。
例如,給Comment模型創建一個 CommentQuery類,然后在CommentQuery類中定義一個active()的方法為作用域,像下面的代碼:
namespace appmodels; use yiidbActiveQuery; class CommentQuery extends ActiveQuery{ public function active($state = true) { $this->andWhere(["active" => $state]); return $this; } }
重點:
類必須繼承 yiidbActiveQuery (或者是其他的 ActiveQuery ,比如 yiimongodbActiveQuery)。
必須是一個public類型的方法且必須返回 $this 實現鏈式操作。可以傳入參數。
檢查 yiidbActiveQuery 對于修改查詢條件是非常有用的方法。
其次,覆蓋yiidbActiveRecord::find() 方法使其返回自定義的查詢對象而不是常規的yiidbActiveQuery。
對于上述例子,你需要編寫如下代碼:
namespace appmodels; use yiidbActiveRecord; class Comment extends ActiveRecord{ /** * @inheritdoc * @return CommentQuery */ public static function find() { return new CommentQuery(get_called_class()); } }
就這樣,現在你可以使用自定義的作用域方法了:
$comments = Comment::find() ->active() ->all(); $inactiveComments = Comment::find() ->active(false) ->all();
你也能在定義的關聯里使用作用域方法,比如:
class Post extends yiidbActiveRecord{ public function getActiveComments() { return $this->hasMany(Comment::className(), ["post_id" => "id"]) ->active(); } }
或者在執行關聯查詢的時候使用(on-the-fly 是啥?):
$posts = Post::find() ->with([ "comments" => function($q) { $q->active(); } ]) ->all();默認作用域
如果你之前用過 Yii 1.1 就應該知道默認作用域的概念。
一個默認的作用域可以作用于所有查詢。
你可以很容易的通過重寫yiidbActiveRecord::find()方法來定義一個默認作用域,例如:
public static function find(){ return parent::find() ->where(["deleted" => false]); }
注意,你之后所有的查詢都不能用 yiidbActiveQuery::where(),但是可以用 yiidbActiveQuery::andWhere() 和 yiidbActiveQuery::orWhere(),他們不會覆蓋掉默認作用域。 (譯注:如果你要使用默認作用域,就不能在 xxx::find()后使用where()方法,你必須使用andXXX()或者orXXX()系的方法,否則默認作用域不會起效果,至于原因,打開where()方法的代碼一看便知)事務操作
當執行幾個相關聯的數據庫操作的時候
TODO: FIXME: WIP, TBD, https://github.com/yiisoft/yii2/issues/226 , yiidbActiveRecord::afterSave(), yiidbActiveRecord::beforeDelete() and/or yiidbActiveRecord::afterDelete()
生命周期周期方法(life cycle methods 我覺得這句翻譯成“模板方法”會不會更好點?)。
開發者可以通過重寫yiidbActiveRecord::save()方法然后在控制器里使用事務操作,嚴格地說是似乎不是一個好的做法 (召回"瘦控制器 / 肥模型"基本規則)。
這些方法在這里(如果你不明白自己實際在干什么,請不要使用他們),Models:
class Feature extends yiidbActiveRecord{ // ... public function getProduct() { return $this->hasOne(Product::className(), ["id" => "product_id"]); } } class Product extends yiidbActiveRecord{ // ... public function getFeatures() { return $this->hasMany(Feature::className(), ["product_id" => "id"]); } }
重寫 yiidbActiveRecord::save() 方法:
class ProductController extends yiiwebController{ public function actionCreate() { // FIXME: TODO: WIP, TBD } }
在控制器層使用事務:
class ProductController extends yiiwebController{ public function actionCreate() { // FIXME: TODO: WIP, TBD } }
作為這些脆弱方法的替代,你應該使用原子操作方案特性。
class Feature extends yiidbActiveRecord{ // ... 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 yiidbActiveRecord{ // ... 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 yiiwebController{ public function actionCreate() { // FIXME: TODO: WIP, TBD } }
控制器非常簡潔:
class ProductController extends yiiwebController{ public function actionCreate() { // FIXME: TODO: WIP, TBD } }被污染屬性
當你調用yiidbActiveRecord::save()用于保存活動記錄(Active Record)實例時,只有被污染的屬性才會被保存。
一個屬性是否認定為被污染取決于它的值自從最后一次從數據庫加載或者最近一次保存到數據庫后到現在是否被修改過。注意:無論活動記錄(Active Record)是否有被污染屬性,數據驗證始終會執行。
活動記錄(Active Record)會自動維護一個污染數據列表。它的工作方式是通過維護一個較舊屬性值版本,并且將它們與最新的進行比較。
你可以通過調用yiidbActiveRecord::getDirtyAttributes()來獲取當前的污染屬性。
你也可以調用yiidbActiveRecord::markAttributeDirty()來顯示的標記一個屬性為污染屬性。
如果你對最近一次修改前的屬性值感興趣,你可以調用yiidbActiveRecord::getOldAttributes() 或 yiidbActiveRecord::getOldAttribute()。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/21650.html
摘要:運行來安裝指定的擴展。這更便于用戶辨別是否是的擴展。當用戶運行安裝一個擴展時,文件會被自動更新使之包含新擴展的信息。上述代碼表明該擴展依賴于包。例如,上述的條目聲明將對應于別名。為達到這個目的,你應當在公開發布前做測試。 簡述 擴展是專門設計的在 Yii 應用中隨時可拿來使用的, 并可重發布的軟件包。 基礎 例如, yiisoft/yii2-debug 擴展在你的應用的每個頁面底部添加...
摘要:認證事件類在登錄和注銷流程引發一些事件。成功注銷后引發。提供兩種授權方法存取控制過濾器和基于角色的存取控制。允許已認證用戶執行操作。指定一個回調函數用于判定該規則是否滿足條件。 簡述 在程序開發過程中,往往都不能忽視安全問題,無論你的框架有多么完美,都會有破綻,所以完善自己的系統,從程序開發的安全角度去思考問題,把一切潛在的危機扼殺在搖籃中。 認證(Authentication) 認證...
摘要:簡述這里簡單歸納總結關于的錯誤處理和日志記錄的操作。錯誤處理器會正確地設置響應的狀態碼并使用合適的錯誤視圖頁面來顯示錯誤信息。記錄一個警告消息用來指示一些已經發生的意外。的義務是正確處理日志消息。相應的消息通過被記錄。 簡述 這里簡單歸納總結關于Yii的錯誤處理和日志記錄的操作。 錯誤處理(Errors) Yii 內置了一個yiiwebErrorHandler錯誤處理器,它使錯誤處理更...
摘要:把所有的增量數據庫遷移提交到生產環境數據庫當中。如果其中任意一個遷移提交失敗了,那么這條命令將會退出并停止剩下的那些還未執行的遷移。執行這條命令期間不會有任何的遷移會被提交或還原。 簡述 數據遷移就是數據庫表在團隊建的遷移操作,達到團隊相互間的信息同步,數據統一。 數據庫遷移 一般步驟: 1、在 yii2 的 migrate 中,通常用來對數據庫數據表進行修改操作,主要對結構和小部分數...
摘要:它由一個或多個類組成,它們在控制臺環境下通常被稱為命令。控制臺入口腳本通常被稱為,位于應用程序的根目錄。選項通過覆蓋在中的方法,你可以指定可用于控制臺命令選項。參數將傳遞給請求的子命令對應的操作方法。通常,執行成功的命令會返回。 簡述 控制臺應用程序的結構非常類似于 Yii 的一個 Web 應用程序,主要用于終端服務器執行。 控制臺命令 控制臺應用程序的結構非常類似于 Yii 的一個 ...
閱讀 671·2023-04-25 18:59
閱讀 1211·2021-09-22 16:00
閱讀 1889·2021-09-22 15:42
閱讀 3594·2021-09-22 15:27
閱讀 1246·2019-08-30 15:54
閱讀 1104·2019-08-30 11:16
閱讀 2445·2019-08-29 16:24
閱讀 820·2019-08-29 12:14