Query.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. <?php
  2. namespace Drupal\Core\Config\Entity\Query;
  3. use Drupal\Core\Config\ConfigFactoryInterface;
  4. use Drupal\Core\Entity\EntityTypeInterface;
  5. use Drupal\Core\Entity\Query\QueryBase;
  6. use Drupal\Core\Entity\Query\QueryInterface;
  7. use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
  8. /**
  9. * Defines the entity query for configuration entities.
  10. */
  11. class Query extends QueryBase implements QueryInterface {
  12. /**
  13. * Information about the entity type.
  14. *
  15. * @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface
  16. */
  17. protected $entityType;
  18. /**
  19. * The config factory used by the config entity query.
  20. *
  21. * @var \Drupal\Core\Config\ConfigFactoryInterface
  22. */
  23. protected $configFactory;
  24. /**
  25. * The key value factory.
  26. *
  27. * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
  28. */
  29. protected $keyValueFactory;
  30. /**
  31. * Constructs a Query object.
  32. *
  33. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  34. * The entity type definition.
  35. * @param string $conjunction
  36. * - AND: all of the conditions on the query need to match.
  37. * - OR: at least one of the conditions on the query need to match.
  38. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
  39. * The config factory.
  40. * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
  41. * The key value factory.
  42. * @param array $namespaces
  43. * List of potential namespaces of the classes belonging to this query.
  44. */
  45. public function __construct(EntityTypeInterface $entity_type, $conjunction, ConfigFactoryInterface $config_factory, KeyValueFactoryInterface $key_value_factory, array $namespaces) {
  46. parent::__construct($entity_type, $conjunction, $namespaces);
  47. $this->configFactory = $config_factory;
  48. $this->keyValueFactory = $key_value_factory;
  49. }
  50. /**
  51. * Overrides \Drupal\Core\Entity\Query\QueryBase::condition().
  52. *
  53. * Additional to the syntax defined in the QueryInterface you can use
  54. * placeholders (*) to match all keys of an subarray. Let's take the follow
  55. * yaml file as example:
  56. * @code
  57. * level1:
  58. * level2a:
  59. * level3: 1
  60. * level2b:
  61. * level3: 2
  62. * @endcode
  63. * Then you can filter out via $query->condition('level1.*.level3', 1).
  64. */
  65. public function condition($property, $value = NULL, $operator = NULL, $langcode = NULL) {
  66. return parent::condition($property, $value, $operator, $langcode);
  67. }
  68. /**
  69. * {@inheritdoc}
  70. */
  71. public function execute() {
  72. // Load the relevant config records.
  73. $configs = $this->loadRecords();
  74. // Apply conditions.
  75. $result = $this->condition->compile($configs);
  76. // Apply sort settings.
  77. foreach ($this->sort as $sort) {
  78. $direction = $sort['direction'] == 'ASC' ? -1 : 1;
  79. $field = $sort['field'];
  80. uasort($result, function ($a, $b) use ($field, $direction) {
  81. $properties = explode('.', $field);
  82. foreach ($properties as $property) {
  83. if (isset($a[$property]) || isset($b[$property])) {
  84. $a = isset($a[$property]) ? $a[$property] : NULL;
  85. $b = isset($b[$property]) ? $b[$property] : NULL;
  86. }
  87. }
  88. return ($a <= $b) ? $direction : -$direction;
  89. });
  90. }
  91. // Let the pager do its work.
  92. $this->initializePager();
  93. if ($this->range) {
  94. $result = array_slice($result, $this->range['start'], $this->range['length'], TRUE);
  95. }
  96. if ($this->count) {
  97. return count($result);
  98. }
  99. // Create the expected structure of entity_id => entity_id. Config
  100. // entities have string entity IDs.
  101. foreach ($result as $key => &$value) {
  102. $value = (string) $key;
  103. }
  104. return $result;
  105. }
  106. /**
  107. * Loads the config records to examine for the query.
  108. *
  109. * @return array
  110. * Config records keyed by entity IDs.
  111. */
  112. protected function loadRecords() {
  113. $prefix = $this->entityType->getConfigPrefix() . '.';
  114. $prefix_length = strlen($prefix);
  115. // Search the conditions for restrictions on configuration object names.
  116. $names = FALSE;
  117. $id_condition = NULL;
  118. $id_key = $this->entityType->getKey('id');
  119. if ($this->condition->getConjunction() == 'AND') {
  120. $lookup_keys = $this->entityType->getLookupKeys();
  121. $conditions = $this->condition->conditions();
  122. foreach ($conditions as $condition_key => $condition) {
  123. $operator = $condition['operator'] ?: (is_array($condition['value']) ? 'IN' : '=');
  124. if (is_string($condition['field']) && ($operator == 'IN' || $operator == '=')) {
  125. // Special case ID lookups.
  126. if ($condition['field'] == $id_key) {
  127. $ids = (array) $condition['value'];
  128. $names = array_map(function ($id) use ($prefix) {
  129. return $prefix . $id;
  130. }, $ids);
  131. }
  132. elseif (in_array($condition['field'], $lookup_keys)) {
  133. // If we don't find anything then there are no matches. No point in
  134. // listing anything.
  135. $names = [];
  136. $keys = (array) $condition['value'];
  137. $keys = array_map(function ($value) use ($condition) {
  138. return $condition['field'] . ':' . $value;
  139. }, $keys);
  140. foreach ($this->getConfigKeyStore()->getMultiple($keys) as $list) {
  141. $names = array_merge($names, $list);
  142. }
  143. }
  144. }
  145. // Save the first ID condition that is not an 'IN' or '=' for narrowing
  146. // down later.
  147. elseif (!$id_condition && $condition['field'] == $id_key) {
  148. $id_condition = $condition;
  149. }
  150. // We stop at the first restricting condition on name. In the case where
  151. // there are additional restricting conditions, results will be
  152. // eliminated when the conditions are checked on the loaded records.
  153. if ($names !== FALSE) {
  154. // If the condition has been responsible for narrowing the list of
  155. // configuration to check there is no point in checking it further.
  156. unset($conditions[$condition_key]);
  157. break;
  158. }
  159. }
  160. }
  161. // If no restrictions on IDs were found, we need to parse all records.
  162. if ($names === FALSE) {
  163. $names = $this->configFactory->listAll($prefix);
  164. }
  165. // In case we have an ID condition, try to narrow down the list of config
  166. // objects to load.
  167. if ($id_condition && !empty($names)) {
  168. $value = $id_condition['value'];
  169. $filter = NULL;
  170. switch ($id_condition['operator']) {
  171. case '<>':
  172. $filter = function ($name) use ($value, $prefix_length) {
  173. $id = substr($name, $prefix_length);
  174. return $id !== $value;
  175. };
  176. break;
  177. case 'STARTS_WITH':
  178. $filter = function ($name) use ($value, $prefix_length) {
  179. $id = substr($name, $prefix_length);
  180. return strpos($id, $value) === 0;
  181. };
  182. break;
  183. case 'CONTAINS':
  184. $filter = function ($name) use ($value, $prefix_length) {
  185. $id = substr($name, $prefix_length);
  186. return strpos($id, $value) !== FALSE;
  187. };
  188. break;
  189. case 'ENDS_WITH':
  190. $filter = function ($name) use ($value, $prefix_length) {
  191. $id = substr($name, $prefix_length);
  192. return strrpos($id, $value) === strlen($id) - strlen($value);
  193. };
  194. break;
  195. }
  196. if ($filter) {
  197. $names = array_filter($names, $filter);
  198. }
  199. }
  200. // Load the corresponding records.
  201. $records = [];
  202. foreach ($this->configFactory->loadMultiple($names) as $config) {
  203. $records[substr($config->getName(), $prefix_length)] = $config->get();
  204. }
  205. return $records;
  206. }
  207. /**
  208. * Gets the key value store used to store fast lookups.
  209. *
  210. * @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface
  211. * The key value store used to store fast lookups.
  212. */
  213. protected function getConfigKeyStore() {
  214. return $this->keyValueFactory->get(QueryFactory::CONFIG_LOOKUP_PREFIX . $this->entityTypeId);
  215. }
  216. }