1<?php 2 3namespace Drupal\Core\Config\Entity\Query; 4 5use Drupal\Core\Config\Config; 6use Drupal\Core\Config\ConfigCrudEvent; 7use Drupal\Core\Config\ConfigEvents; 8use Drupal\Core\Config\ConfigFactoryInterface; 9use Drupal\Core\Config\ConfigManagerInterface; 10use Drupal\Core\Config\Entity\ConfigEntityTypeInterface; 11use Drupal\Core\Entity\EntityTypeInterface; 12use Drupal\Core\Entity\Query\QueryBase; 13use Drupal\Core\Entity\Query\QueryException; 14use Drupal\Core\Entity\Query\QueryFactoryInterface; 15use Drupal\Core\KeyValueStore\KeyValueFactoryInterface; 16use Symfony\Component\EventDispatcher\EventSubscriberInterface; 17 18/** 19 * Provides a factory for creating entity query objects for the config backend. 20 */ 21class QueryFactory implements QueryFactoryInterface, EventSubscriberInterface { 22 23 /** 24 * The prefix for the key value collection for fast lookups. 25 */ 26 const CONFIG_LOOKUP_PREFIX = 'config.entity.key_store.'; 27 28 /** 29 * The config factory used by the config entity query. 30 * 31 * @var \Drupal\Core\Config\ConfigFactoryInterface 32 */ 33 protected $configFactory; 34 35 /** 36 * The namespace of this class, the parent class etc. 37 * 38 * @var array 39 */ 40 protected $namespaces; 41 42 /** 43 * Constructs a QueryFactory object. 44 * 45 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory 46 * The config storage used by the config entity query. 47 * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value 48 * The key value factory. 49 * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager 50 * The configuration manager. 51 */ 52 public function __construct(ConfigFactoryInterface $config_factory, KeyValueFactoryInterface $key_value, ConfigManagerInterface $config_manager) { 53 $this->configFactory = $config_factory; 54 $this->keyValueFactory = $key_value; 55 $this->configManager = $config_manager; 56 $this->namespaces = QueryBase::getNamespaces($this); 57 } 58 59 /** 60 * {@inheritdoc} 61 */ 62 public function get(EntityTypeInterface $entity_type, $conjunction) { 63 return new Query($entity_type, $conjunction, $this->configFactory, $this->keyValueFactory, $this->namespaces); 64 } 65 66 /** 67 * {@inheritdoc} 68 */ 69 public function getAggregate(EntityTypeInterface $entity_type, $conjunction) { 70 throw new QueryException('Aggregation over configuration entities is not supported'); 71 } 72 73 /** 74 * Gets the key value store used to store fast lookups. 75 * 76 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type 77 * The entity type. 78 * 79 * @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface 80 * The key value store used to store fast lookups. 81 */ 82 protected function getConfigKeyStore(EntityTypeInterface $entity_type) { 83 return $this->keyValueFactory->get(static::CONFIG_LOOKUP_PREFIX . $entity_type->id()); 84 } 85 86 /** 87 * Updates or adds lookup data. 88 * 89 * @param \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type 90 * The entity type. 91 * @param \Drupal\Core\Config\Config $config 92 * The configuration object that is being saved. 93 */ 94 protected function updateConfigKeyStore(ConfigEntityTypeInterface $entity_type, Config $config) { 95 $config_key_store = $this->getConfigKeyStore($entity_type); 96 foreach ($entity_type->getLookupKeys() as $lookup_key) { 97 foreach ($this->getKeys($config, $lookup_key, 'get', $entity_type) as $key) { 98 $values = $config_key_store->get($key, []); 99 if (!in_array($config->getName(), $values, TRUE)) { 100 $values[] = $config->getName(); 101 $config_key_store->set($key, $values); 102 } 103 } 104 } 105 } 106 107 /** 108 * Deletes lookup data. 109 * 110 * @param \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type 111 * The entity type. 112 * @param \Drupal\Core\Config\Config $config 113 * The configuration object that is being deleted. 114 */ 115 protected function deleteConfigKeyStore(ConfigEntityTypeInterface $entity_type, Config $config) { 116 $config_key_store = $this->getConfigKeyStore($entity_type); 117 foreach ($entity_type->getLookupKeys() as $lookup_key) { 118 foreach ($this->getKeys($config, $lookup_key, 'getOriginal', $entity_type) as $key) { 119 $values = $config_key_store->get($key, []); 120 $pos = array_search($config->getName(), $values, TRUE); 121 if ($pos !== FALSE) { 122 unset($values[$pos]); 123 } 124 if (empty($values)) { 125 $config_key_store->delete($key); 126 } 127 else { 128 $config_key_store->set($key, $values); 129 } 130 } 131 } 132 } 133 134 /** 135 * Creates lookup keys for configuration data. 136 * 137 * @param \Drupal\Core\Config\Config $config 138 * The configuration object. 139 * @param string $key 140 * The configuration key to look for. 141 * @param string $get_method 142 * Which method on the config object to call to get the value. Either 'get' 143 * or 'getOriginal'. 144 * @param \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type 145 * The configuration entity type. 146 * 147 * @return array 148 * An array of lookup keys concatenated to the configuration values. 149 * 150 * @throws \Drupal\Core\Config\Entity\Query\InvalidLookupKeyException 151 * The provided $key cannot end with a wildcard. This makes no sense since 152 * you cannot do fast lookups against this. 153 */ 154 protected function getKeys(Config $config, $key, $get_method, ConfigEntityTypeInterface $entity_type) { 155 if (substr($key, -1) == '*') { 156 throw new InvalidLookupKeyException(strtr('%entity_type lookup key %key ends with a wildcard this can not be used as a lookup', ['%entity_type' => $entity_type->id(), '%key' => $key])); 157 } 158 $parts = explode('.*', $key); 159 // Remove leading dots. 160 array_walk($parts, function (&$value) { 161 $value = trim($value, '.'); 162 }); 163 164 $values = (array) $this->getValues($config, $parts[0], $get_method, $parts); 165 166 $output = []; 167 // Flatten the array to a single dimension and add the key to all the 168 // values. 169 array_walk_recursive($values, function ($current) use (&$output, $key) { 170 if (is_scalar($current)) { 171 $current = $key . ':' . $current; 172 } 173 $output[] = $current; 174 }); 175 return $output; 176 } 177 178 /** 179 * Finds all the values for a configuration key in a configuration object. 180 * 181 * @param \Drupal\Core\Config\Config $config 182 * The configuration object. 183 * @param string $key 184 * The current key being checked. 185 * @param string $get_method 186 * Which method on the config object to call to get the value. 187 * @param array $parts 188 * All the parts of a configuration key we are checking. 189 * @param int $start 190 * Which position of $parts we are processing. Defaults to 0. 191 * 192 * @return array|null 193 * The array of configuration values the match the provided key. NULL if 194 * the configuration object does not have a value that corresponds to the 195 * key. 196 */ 197 protected function getValues(Config $config, $key, $get_method, array $parts, $start = 0) { 198 $value = $config->$get_method($key); 199 if (is_array($value)) { 200 $new_value = []; 201 $start++; 202 if (!isset($parts[$start])) { 203 // The configuration object does not have a value that corresponds to 204 // the key. 205 return NULL; 206 } 207 foreach (array_keys($value) as $key_bit) { 208 $new_key = $key . '.' . $key_bit; 209 if (!empty($parts[$start])) { 210 $new_key .= '.' . $parts[$start]; 211 } 212 $new_value[] = $this->getValues($config, $new_key, $get_method, $parts, $start); 213 } 214 $value = $new_value; 215 } 216 return $value; 217 } 218 219 /** 220 * Updates configuration entity in the key store. 221 * 222 * @param \Drupal\Core\Config\ConfigCrudEvent $event 223 * The configuration event. 224 */ 225 public function onConfigSave(ConfigCrudEvent $event) { 226 $saved_config = $event->getConfig(); 227 $entity_type_id = $this->configManager->getEntityTypeIdByName($saved_config->getName()); 228 if ($entity_type_id) { 229 $entity_type = $this->configManager->getEntityTypeManager()->getDefinition($entity_type_id); 230 $this->updateConfigKeyStore($entity_type, $saved_config); 231 } 232 } 233 234 /** 235 * Removes configuration entity from key store. 236 * 237 * @param \Drupal\Core\Config\ConfigCrudEvent $event 238 * The configuration event. 239 */ 240 public function onConfigDelete(ConfigCrudEvent $event) { 241 $saved_config = $event->getConfig(); 242 $entity_type_id = $this->configManager->getEntityTypeIdByName($saved_config->getName()); 243 if ($entity_type_id) { 244 $entity_type = $this->configManager->getEntityTypeManager()->getDefinition($entity_type_id); 245 $this->deleteConfigKeyStore($entity_type, $saved_config); 246 } 247 } 248 249 /** 250 * {@inheritdoc} 251 */ 252 public static function getSubscribedEvents() { 253 $events[ConfigEvents::SAVE][] = ['onConfigSave', 128]; 254 $events[ConfigEvents::DELETE][] = ['onConfigDelete', 128]; 255 return $events; 256 } 257 258} 259