天天看點

雲客Drupal源碼分析之配置實體查詢

本系列已經釋出過内容實體的實體查詢,配置實體也是有實體查詢的,比如:塊知識庫服務(\Drupal\block\BlockRepository)中就用到配置實體查詢,以查出相同主題下的塊配置實體。配置實體的資料通過配置系統儲存,先回顧一下我們擷取一個配置值的方式:

$config = \Drupal::configFactory()->get('system.performance');
        $config->get('cache.page.max_age');
           

在擷取配置對象後,我們是依據點号分隔的配置路徑表明我們需要的配置值的,在擷取值時,這個路徑就像内容實體的字段一樣,drupal提供的配置實體查詢非常強大,隻需要提供路徑、期望的值、對值的操作符作為條件,然後通過and或or連結詞将條件連接配接起來就可以依據配置中的任意值進行查詢或統計了。

預設查詢工廠:

和内容實體一樣,配置實體也有一個通用查詢工廠服務,她為所有的配置實體類型執行個體化出查詢對象,後者是我們操作查詢的互動對象(見後),配置查詢工廠服務定義如下:

服務id:entity.query.config

類:Drupal\Core\Config\Entity\Query\QueryFactory

擷取方法:\Drupal::service('entity.query.config');

通過她的get方法可以擷取要使用的查詢對象:

\Drupal::service(‘entity.query.config’)->get($entityTypeID, $conjunction);

擷取查詢對象更通用的方法(和内容實體有相同的形式):

\Drupal::entityQuery($entityTypeID, $conjunction = 'AND');

查詢對象使用示例:

$entity_query =\Drupal::entityQuery('block');
        $entity_query->accessCheck(FALSE);
        $entity_query->condition('theme', 'bartik');
        $result = $entity_query->execute();
        print_r($result);
        die;
           

以上代碼将傳回bartik主題下的所有塊配置實體id。

以上工廠服務是配置實體查詢的通用工廠,配置實體在實體儲存處理器的getQueryServiceName方法中可以指定自定義工廠。

原理:

我們知道内容實體是分字段存放資料庫表的,通過建構SQL語句執行查詢,而配置實體的資料是存放在配置系統中,在配置系統中資料是序列化後儲存在一個字段中的,這就不能再使用sql查詢了,隻能加載全部配置實體對應的配置對象,然後逐一解析篩選,原本由資料庫進行的工作現在由php完成,這樣的工作量是很大的,有沒有什麼優化方法呢?

首先分析一下配置資料的特性,在很多使用場景下,内容資料的任意字段都可能成為搜尋依據,而配置資料不太一樣,往往隻有一個或幾個值會成為搜尋依據,其次配置資料在數量上是比較少的,根據這些特性,drupal的做法是為配置實體專門建立用于搜尋優化的資料,對可預見的搜尋配置項進行特别處理:

在定義配置實體時,我們在實體類釋文中以lookup_keys根鍵指出要做搜尋優化的配置項路徑,可以有多個,這稱為定義查找鍵,路徑由點号分隔的配置鍵名組成,和從配置對象擷取值時指定的路徑一樣,但不一樣的是可以使用通配符“*”比對某層級的所有鍵名,開始層級和最後層級不能使用通配符,如:

tour實體的lookup_keys 的值為:"routes.*.route_name",

塊實體的為:"theme"

沒有被定義為查找鍵的配置路徑不做搜尋優化(但同樣可以搜尋),系統在所有的配置實體中都預設追加了uuid作為配置查找鍵,是以通過uuid查詢是經過優化的,速度會很快。

系統使用鍵值儲存系統(見本系列鍵值儲存主題)來儲存配置實體的搜尋優化資料,對應的資料庫表為:key_value,以“config.entity.key_store.實體類型id”作為鍵值儲存集,以此執行個體化鍵值儲存對象,搜尋優化資料的結構如下:

搜尋鍵:

儲存在資料庫表的name字段,字元串值,格式如下:

“值路徑:配置值”

其中值路徑就是上文講到的實體釋文根鍵lookup_keys指定的配置值路徑(沒有替換通配符),配置值必須是标量資料,如果不是标量資料,那麼需要修改值路徑以進一步精确指定,配置值是經過配置覆寫後的值;每行儲存一個配置值。

配置名數組:

儲存在資料庫表的value字段,以序列化方式儲存,值是一個由配置名構成的數組,可通過這些配置名得出配置實體的id,這些配置實體在對應的配置路徑上的配置值是一樣的。

有了這樣的資料結構,在進行lookup_keys 中定義路徑的“=”操作查詢時,就大大提高了搜尋速度,進而起到搜尋優化的作用,如果是進行其他搜尋操作,那麼還得老老實實加載配置對象,然後具體解析,該工作由查詢對象來完成,好在系統中絕大部分查詢都是“=”操作,是以很有必要通過設定lookup_keys來提高查詢速度。

以上這些搜尋優化資料是什麼時候儲存的呢?因為配置查詢服務實作了訂閱器,她訂閱了配置的儲存和删除事件,以此維護這些資料。

查詢對象:

提供完整的搜尋功能,取得配置查詢對象可以使用以下語句:

$entity_query =\Drupal::entityQuery($entityTypeID, $conjunction = 'AND');

或者在明确知道是配置實體時使用以下語句:

$entity_query =\Drupal::service(‘entity.query.config’)->get($entityTypeID, $conjunction);

我們可以在上面設定查詢條件,以得到查詢結果,預設的配置查詢對象是:

\Drupal\Core\Config\Entity\Query\Query

該類設計的非常精妙,這裡做一些說明幫助了解:

\Drupal\Core\Config\Entity\Query\Query::loadRecords:

為減小搜尋範圍,該方法将傳回一個通過上文搜尋優化資料計算出的配置記錄,不管是and還是or條件,最終的查詢結果一定在該範圍内,傳回值是一個數組,鍵名是配置實體id,值為配置資料(數組形式,$config->get()傳回的值),傳回的這個範圍數組會送入條件對象中運用其他條件,進一步減小範圍,直到最終的搜尋結果

\Drupal\Core\Config\Entity\Query\Condition::compile:

查詢對象将使用該條件對象,編譯方法将計算所有的條件,最終确定搜尋結果

查詢對象方法:

$entity_query =\Drupal::entityQuery($entityTypeID);

$entity_query ->condition($property, $value = NULL, $operator = NULL, $langcode = NULL):

參數解釋如下:

條件屬性$property:

稱為路徑,是指配置值所在的路徑,以點号分隔,就像在配置對象上擷取值時所用路徑是一樣的,隻不過這裡允許用通配符“*”代表路徑中某層級的所有鍵名,這提供了強大的能力,在路徑帶有“*”的情況下,隻要被其比對的項有一個滿足條件,結果就滿足條件,需要注意的是這裡和前文講到的設定lookup_keys不同,“*”可以在路徑的如何層級,包括開始和結束。

該參數也可以是條件對象,詳見條件組

條件值:

配置實體查詢中條件值必須是标量資料(如果操作符是IN、NOT IN則可以是标量資料組成的數組),值是大小寫不敏感的,在内部将全部轉換為小寫,配置實體查詢不支援針對大小寫的查詢,建議小寫。

操作符:

操作符是大小寫敏感的,必須大寫,條件連接配接詞(AND,OR)大小寫不敏感;在預設配置實體查詢對象上隻能使用以下操作符(其他操作符将抛出無效操作符異常):

大于:>

小于:<

等于:=, (可省略,預設操作符)

大于等于:>=

小于等于:<=

不等于:<> (注意不可以使用!=)

不在其中:NOT IN (提供的值為數組)

在其中: IN (提供的值為數組,可省略,預設操作符)

以值字元串開始:STARTS_WITH

以值字元串結束:ENDS_WITH

包含值字元串:CONTAINS

判斷是否為空:'IS NULL', 'IS NOT NULL' (等同于isset($value),空字元串不是NULL)

exists($property, $langcode = NULL)

notExists($property, $langcode = NULL)

判斷配置是否存在,相當于使用條件方法時将值設為NULL,再使用操作符'IS NULL', 'IS NOT NULL'

range($start = NULL, $length = NULL):

限定查詢範圍

sort($field, $direction = 'ASC', $langcode = NULL)

排序,比如按權重排序

count():

設定查詢為計數,調用後将傳回查詢所得的配置總數

execute():

傳回内容是一個實體id構成的數組,鍵名和鍵值相同,都是實體id,如果調用過計數方法将傳回一個整數。

注意:預設配置查詢對象上的許多方法來自查詢基類,他們在配置查詢中用不到,調用是無意義的,如:通路檢查、聚合方法等,他們是為内容實體查詢準備的。

示例代碼:

找出bartik主題下第一側邊欄中排序值小于-5的塊:

$entity_query = \Drupal::entityQuery('block', 'and');
        $entity_query->condition('theme', 'bartik');
        $entity_query->condition('region', 'sidebar_first');
        $entity_query->condition('weight', -5, '<');
        $result = $entity_query->execute();
        $entities = \Drupal::entityTypeManager()->getStorage("block")->loadMultiple($result);
        print_r($entities);
        die;
           

找出存在角色條件限制的所有區塊:

$entity_query = \Drupal::entityQuery('block', 'and');
        $entity_query->exists('visibility.user_role');
        //$entity_query->exists('visibility.*'); //這樣将找出有任意條件限制的區塊
        $result = $entity_query->execute();
        $entities = \Drupal::entityTypeManager()->getStorage("block")->loadMultiple($result);
        print_r($entities);
        die;
           

找出區塊配置中标題以“自定義”三個字開頭的區塊:

$entity_query = \Drupal::entityQuery('block', 'and');
        $entity_query->condition('settings.label', '自定義','STARTS_WITH');
        $result = $entity_query->execute();
        $entities = \Drupal::entityTypeManager()->getStorage("block")->loadMultiple($result);
        print_r($entities);
        die;
           

查詢某主題下所有區塊的數量:

$entity_query = \Drupal::entityQuery('block', 'and');
        $entity_query->condition('theme', 'bartik');
        $entity_query->count();
        echo $num = $entity_query->execute();
        die;
           

查詢某主題下特定id的塊:

$entity_query = \Drupal::entityQuery('block', 'and');
        $entity_query->condition('theme', 'bartik');
        $entity_query->condition('*.id', 'system_messages_block');
        $result = $entity_query->execute();
        $entities = \Drupal::entityTypeManager()->getStorage("block")->loadMultiple($result);
        print_r($entities);
        die;
           

設定條件組:

有時我們需要進行複雜的查詢,将and和or混用,這就設計到如何将條件分組了,這和内容實體查詢一樣,這裡提供一個代碼,查詢出bartik或者seven下啟用的且具備可視條件設定的塊實體:

$query = \Drupal::entityQuery('block', "AND");
        $or_group = $query->orConditionGroup()
            ->condition('theme', 'bartik')
            ->condition('theme', 'seven');
        $query->condition($or_group);
        $and_group = $query->andConditionGroup()
            ->condition('status', '1')
            ->condition('visibility.*', NULL, 'IS NOT NULL');
        $query->condition($and_group);
        $result = $query->execute();
        $entities = \Drupal::entityTypeManager()->getStorage("block")->loadMultiple($result);
        print_r($entities);
        die;
           

該示例分了兩個組,然後将他們以and連接配接,相當于:

((a || b)&&(c && d))
           

其他實體查詢:

除内容實體查詢、配置實體查詢外,系統還提供了一些其他類型的實體查詢:

鍵值對實體查詢:

用于使用鍵值對儲存系統來儲存資料的實體的查詢:

服務id:entity.query.keyvalue

空實體查詢:

不進行任何有意義的查詢,一些不需要進行實體查詢的實體類型用她來保持程式形式上的統一,進而在調用時不發生異常:

服務id:entity.query.null

補充:

1、注意配置沒有更新事件,儲存和删除足以建立搜尋優化資料

2、配置實體不支援聚合Aggregate查詢,這沒有意義

3、在配置實體有配置覆寫的情況下需要注意:在配置實體查詢的查詢優化資料中(也就是鍵值儲存系統中)儲存優化資料是使用的帶覆寫的值,而删除時使用不帶覆寫的值,這在一些情況下可能導緻錯誤或資料備援

我是雲客,【雲遊天下,做客四方】,聯系方式見首頁,歡迎轉載,但須注明出處

繼續閱讀