<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                ActiveRecord預定義的事件,都在?yiidbBaseActiveRecord?中進行了明確: ~~~ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface { const EVENT_INIT = 'init'; // 初始化對象時觸發 const EVENT_AFTER_FIND = 'afterFind'; // 執行查詢結束時觸發 const EVENT_BEFORE_INSERT = 'beforeInsert'; // 插入結束時觸發 const EVENT_AFTER_INSERT = 'afterInsert'; // 插入之前觸發 const EVENT_BEFORE_UPDATE = 'beforeUpdate'; // 更新記錄前觸發 const EVENT_AFTER_UPDATE = 'afterUpdate'; // 更新記錄后觸發 const EVENT_BEFORE_DELETE = 'beforeDelete'; // 刪除記錄前觸發 const EVENT_AFTER_DELETE = 'afterDelete'; // 刪除記錄后觸發 // ... ... } ~~~ 上述常量,定義了ActiveRecord對象常用的幾個事件。這是預定義事件,我們可以直接拿來 用。事件的定義具體看?[_事件(Event)_](http://www.digpage.com/event.html#event)?部分的內容。 此外,作為ActiveRecord類的祖宗,?yiibaseModel?類也定義了2個事件: ~~~ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayable { const EVENT_BEFORE_VALIDATE = 'beforeValidate'; // 在驗證之前觸發 const EVENT_AFTER_VALIDATE = 'afterValidate'; // 在驗證之后觸發 // ... ... } ~~~ 因此,上述總共10個事件,可供開發者在寫入業務流程時使用。 從上述事件來看,可以看出大部分事件是分別以before和after打頭的成對事件。 有些是“讀”操作時才會觸發的事件,有些是“寫”操作時發生的事件。 而且,“寫”與“寫”之間也是相互區別的。比如,增、改、刪3個寫操作, 都各自有一對事件先后在不同場景下觸發。但這3種“寫”操作不會被同時觸發。 ## 初始化事件[](http://www.digpage.com/active_record.html#id1 "Permalink to this headline") 首先,第一個事件,無可爭議的,是?EVENT_INIT?。這是由?yii\base\Object?所決定的。該事件在init()?方法中被觸發。而我們在?[_屬性(Property)_](http://www.digpage.com/property.html#property)?中已經說過這個方法是最早調用的幾個方法之一。具體代碼: ~~~ public function init() { parent::init(); // 這里觸發EVENT_INIT事件 $this->trigger(self::EVENT_INIT); } ~~~ 雖然這個事件觸發得早,但是實際使用中,這個事件使用頻率不高。 僅是因為有的代碼不得不在初始化階段執行,所以才提供了這個事件。 而且,這個事件由于所處階段特殊,不像有的事件,可以有一定的替代性。 比如,?EVENT_AFTER_VALIDATE?和?EVENT_BEFORE_UPDATE?盡管涇渭分明, 但是由于是相繼觸發,所以某些情況下可以在一定程度上互相替代。但是, 上述10個事件中,僅有?EVENT_INIT?是在初始化階段觸發。所以,其具有不可替代性。 EVENT_INIT?事件通常用于初始化一些東西,從模塊化的角度, 可以簡單看成是將當前類的?init()方法的內容, 作為一個Event Handler單獨劃分為一個模塊。 ## AfterFind事件[](http://www.digpage.com/active_record.html#afterfind "Permalink to this headline") EVENT_AFTER_FIND?事件在完成查詢后觸發,注意該事件少有地沒有對應的Before事件。 另外一個區別于其他事件的不同在于,該事件并非由 ActiveRecord 自身觸發。 而是由yii\db\ActiveQuery?觸發。準確的觸發時點,是在查詢完成后, 向ActiveRecord填充字段全部內容后觸發。 具體代碼在?yii\db\ActiveQuery::populate() ~~~ // 該方法為ActiveQuery將查詢到的內容 $rows 填充到ActiveReocrd中去的方法 public function populate($rows) { if (empty($rows)) { return []; } $models = $this->createModels($rows); if (!empty($this->join) && $this->indexBy === null) { $models = $this->removeDuplicatedModels($models); } if (!empty($this->with)) { $this->findWith($this->with, $models); } if (!$this->asArray) { // 重點在這個foreach里面的afterFind(), // afterFind()不干別的,就是專門調用 // $this->trigger(self::EVENT_AFTER_FIND) 來觸發事件的。 foreach ($models as $model) { $model->afterFind(); } } return $models; } ~~~ 上面的代碼我們可以看出,在完成查詢之后,查詢到了多少個記錄, 就會觸發多少次實例級別的EVENT_AFTER_FIND?事件。 事件的級別,請看?[_事件(Event)_](http://www.digpage.com/event.html#event)?部分的內容。 EVENT_AFTER_FIND?事件,用于查詢后一些內容的填充。比如,有一個ActiveRecord, 專門用于表示博客文章,那么通常他有一個字段,用于表示發布的確切時間。 假設客戶希望在前臺顯示文章時,不直接顯示確切時間,而是顯示如“3分鐘前” “2個月前”之類的相對時間。那么,我們就需要有一個將絕對時間轉化成相對時間的過程。 那么,就可以把這個轉換過程的代碼,寫在?EVENT_AFTER_FIND?事件的Event Handler里。 ## 驗證事件[](http://www.digpage.com/active_record.html#id2 "Permalink to this headline") 驗證事件是在驗證時先后觸發的2個事件,這2個事件均由?yii\base\Model::validate?觸發: ~~~ public function validate($attributeNames = null, $clearErrors = true) { if ($clearErrors) { $this->clearErrors(); } // 這里的 beforeValidate() 會調用 // $this->trigger(self::EVENT_BEFORE_VALIDATE, $event) // 來觸發 EVENT_BEFORE_VALIDATE 事件。 if (!$this->beforeValidate()) { return false; } // 下面是后續的驗證代碼,這里不用過多關注 $scenarios = $this->scenarios(); $scenario = $this->getScenario(); if (!isset($scenarios[$scenario])) { throw new InvalidParamException("Unknown scenario: $scenario"); } if ($attributeNames === null) { $attributeNames = $this->activeAttributes(); } foreach ($this->getActiveValidators() as $validator) { $validator->validateAttributes($this, $attributeNames); } // 這里的 afterValidate() 會調用 // $this->trigger(self::EVENT_AFTER_VALIDATE) // 來觸發 EVENT_AFTER_VALIDATE 事件。 $this->afterValidate(); return !$this->hasErrors(); } ~~~ 這兩個事件正如其名稱所表示的,觸發順序為先?EVENT_BEFORE_VALIDATE?后?EVENT_AFTER_VALIDATE?。 這兩個事件中,?EVENT_BEFORE_VALIDATE?常用于驗證前的一些規范化處理。 仍以博客文章的發布時間字段為例,在接收用戶輸入時, 我們的應用接收一個字符類似“2015年3月8日”之類的字符串。 但是數據庫中我們一般并不以字符串形式保存時間,而是使用一個整型字段來保存。 這主要涉及存儲空間,日期比較和排序,檢索效率等數據庫優化問題,具體不展開。 反正我們就是想把時間以整型形式進行保存。 那么,在驗證用戶輸入之前,我們就需要將字符串類型的日期時間, 轉換成整型類型的日期時間。否則的話,驗證就通不過。 這個轉換過程,就可以寫在?EVENT_BEFORE_VALIDATE?的 Event Handler里面。 EVENT_BEFORE_VALIDATE?還有一個比較吸引人的特性, 它的Event Handler可以返回一個?boolean?值,當為?false?時, 表示后續的驗證沒必要進行了: ~~~ public function beforeValidate() { // 創建一個 ModelEvent,并交給 trigger 在觸發事件時使用 $event = new ModelEvent; $this->trigger(self::EVENT_BEFORE_VALIDATE, $event); return $event->isValid; } ~~~ 上面的代碼中,?trigger()?將傳入的第二個?$event?傳遞給 Event Handler, 使得相關的這些個 Event Handler 可以在必要時修改?$event->isValid?的值。 以此來決定是否可以取消后續的驗證,直接視為驗證沒有通過。 EVENT_AFTER_VALIDATE?通常用于用戶輸入驗證后的一些處理。比如, 用于寫入操作前的一些通用處理。因為后頭接下來的事件, 會分成插入、更新等獨立事件。如果有一些寫入前的通用處理,放在EVENT_AFTER_VALIDATE?階段是比較合適的。 至于驗證通過與否,與?EVENT_AFTER_VALIDATE?事件沒有關系,只要執行完所有驗證了, 這個事件就會被觸發。而且,該事件的Event Handler沒有返回值,無法干預驗證結果。 ## “寫”事件[](http://www.digpage.com/active_record.html#id3 "Permalink to this headline") “寫”事件是指插入、更新、刪除等寫入操作時觸發的事件。一般情況下, 驗證事件先于“寫”事件被觸發。 但這不是絕對的。Yii允許在執行“寫”操作時,不調用?validate()?進行驗證, 也就不觸發驗證事件。 下面,我們以更新操作update為例,來分析“寫”事件。 首先,來看看?yii\db\BaseActiveRecord?里的有關代碼: ~~~ public function save($runValidation = true, $attributeNames = null) { // insert() 和 update() 具體實現由ActiveRecord定義 if ($this->getIsNewRecord()) { return $this->insert($runValidation, $attributeNames); } else { return $this->update($runValidation, $attributeNames) !== false; } } // updateInternal() 由 update() 調用, // 類似的有deleteInternal() ,由ActiveRecord定義,這里略去。 protected function updateInternal($attributes = null) { // beforeSave() 會觸發相應的before事件 // 而且如果beforeSave()返回false,就可以中止更新過程。 if (!$this->beforeSave(false)) { return false; } $values = $this->getDirtyAttributes($attributes); // 沒有字段有修改,那么實際上是不需要更新的。 // 因此,直接調用afterSave()來觸發相應的after事件。 if (empty($values)) { $this->afterSave(false, $values); return 0; } // 以下為實際更新操作,不必細究。 $condition = $this->getOldPrimaryKey(true); $lock = $this->optimisticLock(); if ($lock !== null) { $values[$lock] = $this->$lock + 1; $condition[$lock] = $this->$lock; } $rows = $this->updateAll($values, $condition); if ($lock !== null && !$rows) { throw new StaleObjectException('The object being updated is outdated.'); } $changedAttributes = []; foreach ($values as $name => $value) { $changedAttributes[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null; $this->_oldAttributes[$name] = $value; } // 重點看這里,觸發了事件 $this->afterSave(false, $changedAttributes); return $rows; } // 下面的beforeSave()和afterSave() 會根據判斷是更新操作還是插入操作, // 以此來決定是觸發 INSERT 事件還是 UPDATE 事件。 public function beforeSave($insert) { $event = new ModelEvent; // $insert 為 true 時,表示當前是插入操作,是個新記錄,要觸發INSERT事件 // $insert 為 false時,表示當前是插入操作,是個新記錄,要觸發INSERT事件 $this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event); return $event->isValid; } public function afterSave($insert, $changedAttributes) { // $insert 為 true 時,表示當前是插入操作,是個新記錄,要觸發INSERT事件 // $insert 為 false時,表示當前是插入操作,是個新記錄,要觸發INSERT事件 $this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE, new AfterSaveEvent([ 'changedAttributes' => $changedAttributes ])); } ~~~ 就“寫”操作而言,表面上調用的是?ActiveRecord?的?update()?insert()?delete()?等方法。 但是,更新最終調用的是?BaseActiveRecord::updateInteranl()?, 插入最終調用的是ActiveRecord::insertInternal()?, 而刪除最終調用的是?ActiveRecord::deleteInternal()?。 這些?internal?方法,會觸發相應的“寫”事件,但不會調用驗證方法, 也不會觸發驗證事件。驗證方法?validation()?由?update()?insert()?調用。 因此,驗證事件也由這兩個方法觸發。 而且,這些?update()?insert()?可以選擇不進行驗證,在壓根不觸發驗證事件的情況下,就可以完成“寫”操作。 因此,雖然?EVENT_AFTER_VALIDATE?和?EVENT_BEFORE_UPDATE?相繼發生, 在使用上有時可以有一定程度的替代。但是,其實兩者是有嚴格界限的。 原因就是驗證事件可能在“寫”操作過程中不被觸發。 此外,刪除過程不觸發驗證事件。都要刪掉的東西了,還需要驗證么? 對于?internal?方法們,只是觸發了相應的before和after“寫”事件。 其中,before事件的Event Handler可以通過將?$event->isValid?設為?false?來中止“寫”操作。 與在驗證事件階段中止時,視為驗證沒通過不同,這里的中止視為“寫”操作失敗。 與驗證事件階段類似,after事件時由于生米已成熟飯,再也無法干預“寫”操作的結果。 ## 響應事件[](http://www.digpage.com/active_record.html#id4 "Permalink to this headline") 前面提到的諸多預定義事件,為我們開發提供了方便。基本上使用這些預定義事件, 就可以滿足各種開發需求了。 但是凡事總有例外,特別是對于業務邏輯復雜的情況。 比如,默認的刪除事件,會在確確實實地要從數據表中刪除記錄時觸發。 但是,有的時候,我們并非真的想從數據表中刪除記錄,我們可能使用一個類似于“狀態” 的字段,在想要刪除時,只是將記錄的“狀態”標記為“刪除”。 這種需求并不少見。這樣便于我們在后悔時,可以“恢復”刪除。 從實質上是看,這其實是一個更新操作。那么預定義的?EVENT_BEFORE_DELETE?和?EVENT_AFTER_DELETE就不適用了。 對此,我們可以自己定義事件來使用。具體的方法可以參見?[_事件(Event)_](http://www.digpage.com/event.html#event)?部分的內容。 大致的代碼可以是這樣的: ~~~ class Post extends \yii\db\ActiveRecord { // 第一步:定義自己的事件 const EVENT_BEFORE_MARK_DELETE = 'beforeMarkDelete'; const EVENT_AFTER_MARK_DELETE = 'afterMarkDelete'; // 第二步:定義Event Handler public function onBeforeMarkDelete () { // ... do sth ... } // 第三步:在初始化階段綁定事件和Event Handler public function init() { parent::init(); $this->trigger(self::EVENT_INIT); // 完成綁定 $this->on(self::EVENT_BEFORE_MARK_DELETE, [$this, 'onBeforeMarkDelete']); } // 第四步:觸發事件 public function beforeSave($insert) { // 注意,重載之后要調用父類同名函數 if (parent::beforeSave($insert)) { $status = $this->getDirtyAttributes(['status']); // 這個判斷意會即可 if (!empty($status) && $status['status'] == self::STATUS_DELETE) { // 觸發事件 $this->trigger(self::EVENT_BEFORE_MARK_DELETE); } return true; } else { return false; } } } ~~~ 上面的代碼理解個大致流程就OK了,不用細究。 在事件的響應上,我們有2個方法來寫入我們的代碼。 最直觀的方式,是使用?[_事件(Event)_](http://www.digpage.com/event.html#event)?中介紹的 Event Handler。也就是上面代碼展現的, 為類定義一個成員函數,作為Event Handler。同時,在類的構造函數或初始化方法中, 把事件和Event Handler綁定起來。最后,在合適的時候,觸發事件即可。 另一種方式,是直接重載上面多次提到的各種?beforeSave()?afterSave()?beforeValidate()afterValidate()?等方法。比如,上面的例子可以改成: ~~~ class Post extends \yii\db\ActiveRecord { // 不需要定義自己的事件 //const EVENT_BEFORE_MARK_DELETE = 'beforeMarkDelete'; //const EVENT_AFTER_MARK_DELETE = 'afterMarkDelete'; // 不需要定義Event Handler //public function onBeforeMarkDelete () { // ... do sth ... //} // 不需要綁定事件和Event Handler //public function init() //{ // parent::init(); // $this->trigger(self::EVENT_INIT); // $this->on(self::EVENT_BEFORE_MARK_DELETE, [$this, 'onBeforeMarkDelete']); //} // 只需要重載 public function beforeSave($insert) { // 注意,重載之后要調用父類同名函數 if (parent::beforeSave($insert)) { $status = $this->getDirtyAttributes(['status']); // 這個判斷意會即可 if (!empty($status) && $status['status'] == self::STATUS_DELETE) { // 不需要觸發事件 //$this->trigger(self::EVENT_BEFORE_MARK_DELETE); // 但是需要把原來 Event Handler的內容放到這里來 // ... do sth ... } return true; } else { return false; } } } ~~~ 對比來看,重載?beforeSave()?的方式要簡潔很多。但是這種方式從嚴格意義上來講, 并不是正規的事件處理機制。只不過是利用了Yii已經預先定義好的函數調用流程。 在使用中,需要格外注意的是,一定要在重載的函數中,調用父類的同名函數。否則的話,?trigger()?不再被自動調用,相關事件就不會再被觸發。整個類的事件機制, 就全被破壞了。 ## 關聯操作[](http://www.digpage.com/active_record.html#id5 "Permalink to this headline") 在實際開發中,有一種典型的場景,即對數據庫某個表的某個記錄進行修改時,需要對關聯 的表中的相關記錄做相應的修改。 比如,一個典型的博客,表示文章的數據表中有一個字段用于記錄當前文章有多少條評論。 那么,當用戶發表新評論時,另一個用于表示評論的表中,理所當然地要插入一條新記錄。 不可避免的,文章表中,被評論文章所對應的記錄,其評論計數字段應當加1。 那么這一過程怎么編程實現呢? 最直白的方法,是在操作評論記錄的代碼之前(后),寫入相應的增加文章評論計數的代碼 。 這樣好理解,但是不同功能代碼的界限不清晰。 另一種方法,是借助事件(Event),將增加文章評論計數的代碼,寫到 評論ActiveReocrd的相應Event Handler中。比如,?EVENT_AFTER_INSERT?。 這樣子代碼功能界限清晰,便于查找、修改和擴展。 缺點是可能需要多看幾個方法才能了解整個業務流程。實際中我們多采用這種方法。 在實現數據庫記錄的關聯操作時,第一步就是要利用上述的各種事件,來產生關聯性。 其次,是要把這些關聯性綁死在一起。也就是用數據庫的事務。具體的原理, 參考?[_事務(Transaction)_](http://www.digpage.com/transaction.html#transaction)?部分的內容。 下面,我們以上面提到的博客文章新增一個評論為例,講解如何實現關聯操作。 ### 聲明需要事務支持的操作[](http://www.digpage.com/active_record.html#id6 "Permalink to this headline") 在ActiveRecord中有一個方法,用于告訴Yii我們的哪些操作需要事務支持。對于插入、 更新、刪除的1個或多個操作需要事務支持時,可以在這個方法中進行聲明。 這個方法就是ActiveRecord::transactions() ~~~ class ActiveRecord extends BaseActiveRecord { // 定義插入、更新、刪除操作,及表示3合1的ALL const OP_INSERT = 0x01; const OP_UPDATE = 0x02; const OP_DELETE = 0x04; const OP_ALL = 0x07; // 需要事務支持時,重載這個方法。 public function transactions() { return []; } // ... ... } ~~~ 默認情況下,這個?transactions()?返回一個空數組,表示不需要任何的事務支持。 我們的博客文章增加評論的案例中是要用到的,那么,我們可以在評論模型?Comment?中,作如下聲明: ~~~ public function transactions() { return [ 'addComment' => self::OP_INSERT, ]; } ~~~ 這個方法所返回的數組中,元素的鍵,如上面的?addComment?表示場景(scenario), 元素值,表示的是操作的類型,即?OP_INSERT?等。 ActiveRecord定義了3種可能會用到事務支持的操作?OP_INSERT?OP_UPDATE?OP_DELETE?分別表示插入、更新、刪除。 可以把這3個操作兩兩組合作為?transactions()?所返回數組元素的值。 如,self::OP_INSERT|self::OP_UPDATE?``?表示插入和更新操作。?也可以直接使用?``OP_ALL?表示3種操作都包含。 ### 啟用事務[](http://www.digpage.com/active_record.html#id7 "Permalink to this headline") 上一步中的?transactions()?被?ActiveRecord::isTransactional()?所調用: ~~~ // $operation就是預定義的OP_INSERT 等3種單一操作類型 public function isTransactional($operation) { // 獲取當前的scenario $scenario = $this->getScenario(); $transactions = $this->transactions(); return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation); } ~~~ 這個?isTransactional()?就是判斷當前場景下,當前操作類型,是否已經在?transactions()?中聲明為需要事務支持。 而這個?isTransactional()?又被各種“寫”操作方法所調用。 在我們的博客文章新增評論的案例中,就是被?insert()?所調用: ~~~ public function insert($runValidation = true, $attributes = null) { if ($runValidation && !$this->validate($attributes)) { Yii::info('Model not inserted due to validation error.', __METHOD__); return false; } // 這里調用了 isTransactional(),判斷當前場景下, // 插入操作是否需要事務支持 if (!$this->isTransactional(self::OP_INSERT)) { // 無需事務支持,那就直接insert了事 return $this->insertInternal($attributes); } // 以下是需要事務支持的情況,那就啟用事務 $transaction = static::getDb()->beginTransaction(); try { $result = $this->insertInternal($attributes); if ($result === false) { $transaction->rollBack(); } else { $transaction->commit(); } return $result; } catch (\Exception $e) { $transaction->rollBack(); throw $e; } } ~~~ 很明顯的,我們只需要在?transactions()?中聲明需要事務支持的操作就足夠了。 后續的怎么使聲明生效的,Yii框架已經替我們寫好了。 在上面?insert()?的代碼中,通過我們的聲明,Yii發現需要事務支持, 于是就調用了static::getDb()->beginTransaction()?來啟用事務。 事務的原理,請看?[_事務(Transaction)_](http://www.digpage.com/transaction.html#transaction)?部分的內容。 ### 在事件響應中寫入關聯操作[](http://www.digpage.com/active_record.html#id8 "Permalink to this headline") 接下來,我們在關聯的事件,如?EVENT_AFTER_INSERT?中,寫入關聯操作。 這里,我們就是要更新博客文章模型?Post?的評論計數字段。 因此,可以在評論模型?Comment?完成插入后的?EVENT_AFTER_INSERT?階段, 寫入更新Post::comment_counter?的代碼。如果使用簡潔形式的事件響應方式, 那么代碼可以是: ~~~ class Comment extends \yii\db\ActiveRecord { // 通過重載afterSave來“響應”事件 public function afterSave($insert) { if (parent::beforeSave($insert)) { // 新增一個評論 if ($insert) { // 關聯Post的操作,評論計數字段+1 $post = Post::find($this->postId); $post->comment_counter += 1; $post->save(false); } } } } ~~~ 回顧下實現關聯操作的過程,其實就2步: * 先是在?transactions()?中聲明要事務支持的操作類型,比如上面的例子, 聲明的是插入操作。 * 在合適事件響應函數中,寫下關聯操作代碼。 如果覺得《深入理解Yii2.0》對您有所幫助,也請[幫助《深入理解Yii2.0》](http://www.digpage.com/donate.html#donate)。 謝謝!
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看