87. 配置实体查询
配置实体查询
本系列已经发布过内容实体的实体查询,配置实体也是有实体查询的,比如:块知识库服务(\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、在配置实体有配置覆写的情况下需要注意:在配置实体查询的查询优化数据中(也就是键值储存系统中)保存优化数据是使用的带覆写的值,而删除时使用不带覆写的值,这在一些情况下可能导致错误或数据冗余