ConfigEntityStorage.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. <?php
  2. namespace Drupal\Core\Config\Entity;
  3. use Drupal\Core\Cache\CacheableMetadata;
  4. use Drupal\Core\Config\ConfigFactoryInterface;
  5. use Drupal\Core\Config\ConfigImporterException;
  6. use Drupal\Core\Entity\EntityInterface;
  7. use Drupal\Core\Entity\EntityMalformedException;
  8. use Drupal\Core\Entity\EntityStorageBase;
  9. use Drupal\Core\Config\Config;
  10. use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException;
  11. use Drupal\Core\Entity\EntityTypeInterface;
  12. use Drupal\Component\Uuid\UuidInterface;
  13. use Drupal\Core\Language\LanguageManagerInterface;
  14. use Symfony\Component\DependencyInjection\ContainerInterface;
  15. /**
  16. * Defines the storage class for configuration entities.
  17. *
  18. * Configuration object names of configuration entities are comprised of two
  19. * parts, separated by a dot:
  20. * - config_prefix: A string denoting the owner (module/extension) of the
  21. * configuration object, followed by arbitrary other namespace identifiers
  22. * that are declared by the owning extension; e.g., 'node.type'. The
  23. * config_prefix does NOT contain a trailing dot. It is defined by the entity
  24. * type's annotation.
  25. * - ID: A string denoting the entity ID within the entity type namespace; e.g.,
  26. * 'article'. Entity IDs may contain dots/periods. The entire remaining string
  27. * after the config_prefix in a config name forms the entity ID. Additional or
  28. * custom suffixes are not possible.
  29. *
  30. * @ingroup entity_api
  31. */
  32. class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStorageInterface, ImportableEntityStorageInterface {
  33. /**
  34. * Length limit of the configuration entity ID.
  35. *
  36. * Most file systems limit a file name's length to 255 characters, so
  37. * ConfigBase::MAX_NAME_LENGTH restricts the full configuration object name
  38. * to 250 characters (leaving 5 for the file extension). The config prefix
  39. * is limited by ConfigEntityType::PREFIX_LENGTH to 83 characters, so this
  40. * leaves 166 remaining characters for the configuration entity ID, with 1
  41. * additional character needed for the joining dot.
  42. *
  43. * @see \Drupal\Core\Config\ConfigBase::MAX_NAME_LENGTH
  44. * @see \Drupal\Core\Config\Entity\ConfigEntityType::PREFIX_LENGTH
  45. */
  46. const MAX_ID_LENGTH = 166;
  47. /**
  48. * {@inheritdoc}
  49. */
  50. protected $uuidKey = 'uuid';
  51. /**
  52. * The config factory service.
  53. *
  54. * @var \Drupal\Core\Config\ConfigFactoryInterface
  55. */
  56. protected $configFactory;
  57. /**
  58. * The config storage service.
  59. *
  60. * @var \Drupal\Core\Config\StorageInterface
  61. */
  62. protected $configStorage;
  63. /**
  64. * The language manager.
  65. *
  66. * @var \Drupal\Core\Language\LanguageManagerInterface
  67. */
  68. protected $languageManager;
  69. /**
  70. * Static cache of entities, keyed first by entity ID, then by an extra key.
  71. *
  72. * The additional cache key is to maintain separate caches for different
  73. * states of config overrides.
  74. *
  75. * @var array
  76. * @see \Drupal\Core\Config\ConfigFactoryInterface::getCacheKeys().
  77. */
  78. protected $entities = [];
  79. /**
  80. * Determines if the underlying configuration is retrieved override free.
  81. *
  82. * @var bool
  83. */
  84. protected $overrideFree = FALSE;
  85. /**
  86. * Constructs a ConfigEntityStorage object.
  87. *
  88. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  89. * The entity type definition.
  90. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
  91. * The config factory service.
  92. * @param \Drupal\Component\Uuid\UuidInterface $uuid_service
  93. * The UUID service.
  94. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
  95. * The language manager.
  96. */
  97. public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager) {
  98. parent::__construct($entity_type);
  99. $this->configFactory = $config_factory;
  100. $this->uuidService = $uuid_service;
  101. $this->languageManager = $language_manager;
  102. }
  103. /**
  104. * {@inheritdoc}
  105. */
  106. public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
  107. return new static(
  108. $entity_type,
  109. $container->get('config.factory'),
  110. $container->get('uuid'),
  111. $container->get('language_manager')
  112. );
  113. }
  114. /**
  115. * {@inheritdoc}
  116. */
  117. public function loadRevision($revision_id) {
  118. return NULL;
  119. }
  120. /**
  121. * {@inheritdoc}
  122. */
  123. public function deleteRevision($revision_id) {
  124. return NULL;
  125. }
  126. /**
  127. * Returns the prefix used to create the configuration name.
  128. *
  129. * The prefix consists of the config prefix from the entity type plus a dot
  130. * for separating from the ID.
  131. *
  132. * @return string
  133. * The full configuration prefix, for example 'views.view.'.
  134. */
  135. protected function getPrefix() {
  136. return $this->entityType->getConfigPrefix() . '.';
  137. }
  138. /**
  139. * {@inheritdoc}
  140. */
  141. public static function getIDFromConfigName($config_name, $config_prefix) {
  142. return substr($config_name, strlen($config_prefix . '.'));
  143. }
  144. /**
  145. * {@inheritdoc}
  146. */
  147. protected function doLoadMultiple(array $ids = NULL) {
  148. $prefix = $this->getPrefix();
  149. // Get the names of the configuration entities we are going to load.
  150. if ($ids === NULL) {
  151. $names = $this->configFactory->listAll($prefix);
  152. }
  153. else {
  154. $names = [];
  155. foreach ($ids as $id) {
  156. // Add the prefix to the ID to serve as the configuration object name.
  157. $names[] = $prefix . $id;
  158. }
  159. }
  160. // Load all of the configuration entities.
  161. /** @var \Drupal\Core\Config\Config[] $configs */
  162. $configs = [];
  163. $records = [];
  164. foreach ($this->configFactory->loadMultiple($names) as $config) {
  165. $id = $config->get($this->idKey);
  166. $records[$id] = $this->overrideFree ? $config->getOriginal(NULL, FALSE) : $config->get();
  167. $configs[$id] = $config;
  168. }
  169. $entities = $this->mapFromStorageRecords($records, $configs);
  170. // Config entities wrap config objects, and therefore they need to inherit
  171. // the cacheability metadata of config objects (to ensure e.g. additional
  172. // cacheability metadata added by config overrides is not lost).
  173. foreach ($entities as $id => $entity) {
  174. // But rather than simply inheriting all cacheability metadata of config
  175. // objects, we need to make sure the self-referring cache tag that is
  176. // present on Config objects is not added to the Config entity. It must be
  177. // removed for 3 reasons:
  178. // 1. When renaming/duplicating a Config entity, the cache tag of the
  179. // original config object would remain present, which would be wrong.
  180. // 2. Some Config entities choose to not use the cache tag that the under-
  181. // lying Config object provides by default (For performance and
  182. // cacheability reasons it may not make sense to have a unique cache
  183. // tag for every Config entity. The DateFormat Config entity specifies
  184. // the 'rendered' cache tag for example, because A) date formats are
  185. // changed extremely rarely, so invalidating all render cache items is
  186. // fine, B) it means fewer cache tags per page.).
  187. // 3. Fewer cache tags is better for performance.
  188. $self_referring_cache_tag = ['config:' . $configs[$id]->getName()];
  189. $config_cacheability = CacheableMetadata::createFromObject($configs[$id]);
  190. $config_cacheability->setCacheTags(array_diff($config_cacheability->getCacheTags(), $self_referring_cache_tag));
  191. $entity->addCacheableDependency($config_cacheability);
  192. }
  193. return $entities;
  194. }
  195. /**
  196. * {@inheritdoc}
  197. */
  198. protected function doCreate(array $values) {
  199. // Set default language to current language if not provided.
  200. $values += [$this->langcodeKey => $this->languageManager->getCurrentLanguage()->getId()];
  201. $entity = new $this->entityClass($values, $this->entityTypeId);
  202. return $entity;
  203. }
  204. /**
  205. * {@inheritdoc}
  206. */
  207. protected function doDelete($entities) {
  208. foreach ($entities as $entity) {
  209. $this->configFactory->getEditable($this->getPrefix() . $entity->id())->delete();
  210. }
  211. }
  212. /**
  213. * Implements Drupal\Core\Entity\EntityStorageInterface::save().
  214. *
  215. * @throws EntityMalformedException
  216. * When attempting to save a configuration entity that has no ID.
  217. */
  218. public function save(EntityInterface $entity) {
  219. // Configuration entity IDs are strings, and '0' is a valid ID.
  220. $id = $entity->id();
  221. if ($id === NULL || $id === '') {
  222. throw new EntityMalformedException('The entity does not have an ID.');
  223. }
  224. // Check the configuration entity ID length.
  225. // @see \Drupal\Core\Config\Entity\ConfigEntityStorage::MAX_ID_LENGTH
  226. // @todo Consider moving this to a protected method on the parent class, and
  227. // abstracting it for all entity types.
  228. if (strlen($entity->get($this->idKey)) > self::MAX_ID_LENGTH) {
  229. throw new ConfigEntityIdLengthException("Configuration entity ID {$entity->get($this->idKey)} exceeds maximum allowed length of " . self::MAX_ID_LENGTH . " characters.");
  230. }
  231. return parent::save($entity);
  232. }
  233. /**
  234. * {@inheritdoc}
  235. */
  236. protected function doSave($id, EntityInterface $entity) {
  237. $is_new = $entity->isNew();
  238. $prefix = $this->getPrefix();
  239. $config_name = $prefix . $entity->id();
  240. if ($id !== $entity->id()) {
  241. // Renaming a config object needs to cater for:
  242. // - Storage needs to access the original object.
  243. // - The object needs to be renamed/copied in ConfigFactory and reloaded.
  244. // - All instances of the object need to be renamed.
  245. $this->configFactory->rename($prefix . $id, $config_name);
  246. }
  247. $config = $this->configFactory->getEditable($config_name);
  248. // Retrieve the desired properties and set them in config.
  249. $config->setData($this->mapToStorageRecord($entity));
  250. $config->save($entity->hasTrustedData());
  251. // Update the entity with the values stored in configuration. It is possible
  252. // that configuration schema has casted some of the values.
  253. if (!$entity->hasTrustedData()) {
  254. $data = $this->mapFromStorageRecords([$config->get()]);
  255. $updated_entity = current($data);
  256. foreach (array_keys($config->get()) as $property) {
  257. $value = $updated_entity->get($property);
  258. $entity->set($property, $value);
  259. }
  260. }
  261. return $is_new ? SAVED_NEW : SAVED_UPDATED;
  262. }
  263. /**
  264. * Maps from an entity object to the storage record.
  265. *
  266. * @param \Drupal\Core\Entity\EntityInterface $entity
  267. * The entity object.
  268. *
  269. * @return array
  270. * The record to store.
  271. */
  272. protected function mapToStorageRecord(EntityInterface $entity) {
  273. return $entity->toArray();
  274. }
  275. /**
  276. * {@inheritdoc}
  277. */
  278. protected function has($id, EntityInterface $entity) {
  279. $prefix = $this->getPrefix();
  280. $config = $this->configFactory->get($prefix . $id);
  281. return !$config->isNew();
  282. }
  283. /**
  284. * {@inheritdoc}
  285. */
  286. public function hasData() {
  287. return (bool) $this->configFactory->listAll($this->getPrefix());
  288. }
  289. /**
  290. * Gets entities from the static cache.
  291. *
  292. * @param array $ids
  293. * If not empty, return entities that match these IDs.
  294. *
  295. * @return \Drupal\Core\Entity\EntityInterface[]
  296. * Array of entities from the entity cache.
  297. */
  298. protected function getFromStaticCache(array $ids) {
  299. $entities = [];
  300. // Load any available entities from the internal cache.
  301. if ($this->entityType->isStaticallyCacheable() && !empty($this->entities)) {
  302. $config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys());
  303. foreach ($ids as $id) {
  304. if (!empty($this->entities[$id])) {
  305. if (isset($this->entities[$id][$config_overrides_key])) {
  306. $entities[$id] = $this->entities[$id][$config_overrides_key];
  307. }
  308. }
  309. }
  310. }
  311. return $entities;
  312. }
  313. /**
  314. * Stores entities in the static entity cache.
  315. *
  316. * @param \Drupal\Core\Entity\EntityInterface[] $entities
  317. * Entities to store in the cache.
  318. */
  319. protected function setStaticCache(array $entities) {
  320. if ($this->entityType->isStaticallyCacheable()) {
  321. $config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys());
  322. foreach ($entities as $id => $entity) {
  323. $this->entities[$id][$config_overrides_key] = $entity;
  324. }
  325. }
  326. }
  327. /**
  328. * Invokes a hook on behalf of the entity.
  329. *
  330. * @param $hook
  331. * One of 'presave', 'insert', 'update', 'predelete', or 'delete'.
  332. * @param $entity
  333. * The entity object.
  334. */
  335. protected function invokeHook($hook, EntityInterface $entity) {
  336. // Invoke the hook.
  337. $this->moduleHandler->invokeAll($this->entityTypeId . '_' . $hook, [$entity]);
  338. // Invoke the respective entity-level hook.
  339. $this->moduleHandler->invokeAll('entity_' . $hook, [$entity, $this->entityTypeId]);
  340. }
  341. /**
  342. * {@inheritdoc}
  343. */
  344. protected function getQueryServiceName() {
  345. return 'entity.query.config';
  346. }
  347. /**
  348. * {@inheritdoc}
  349. */
  350. public function importCreate($name, Config $new_config, Config $old_config) {
  351. $entity = $this->_doCreateFromStorageRecord($new_config->get(), TRUE);
  352. $entity->save();
  353. return TRUE;
  354. }
  355. /**
  356. * {@inheritdoc}
  357. */
  358. public function importUpdate($name, Config $new_config, Config $old_config) {
  359. $id = static::getIDFromConfigName($name, $this->entityType->getConfigPrefix());
  360. $entity = $this->load($id);
  361. if (!$entity) {
  362. throw new ConfigImporterException("Attempt to update non-existing entity '$id'.");
  363. }
  364. $entity->setSyncing(TRUE);
  365. $entity = $this->updateFromStorageRecord($entity, $new_config->get());
  366. $entity->save();
  367. return TRUE;
  368. }
  369. /**
  370. * {@inheritdoc}
  371. */
  372. public function importDelete($name, Config $new_config, Config $old_config) {
  373. $id = static::getIDFromConfigName($name, $this->entityType->getConfigPrefix());
  374. $entity = $this->load($id);
  375. $entity->setSyncing(TRUE);
  376. $entity->delete();
  377. return TRUE;
  378. }
  379. /**
  380. * {@inheritdoc}
  381. */
  382. public function importRename($old_name, Config $new_config, Config $old_config) {
  383. return $this->importUpdate($old_name, $new_config, $old_config);
  384. }
  385. /**
  386. * {@inheritdoc}
  387. */
  388. public function createFromStorageRecord(array $values) {
  389. return $this->_doCreateFromStorageRecord($values);
  390. }
  391. /**
  392. * Helps create a configuration entity from storage values.
  393. *
  394. * Allows the configuration entity storage to massage storage values before
  395. * creating an entity.
  396. *
  397. * @param array $values
  398. * The array of values from the configuration storage.
  399. * @param bool $is_syncing
  400. * Is the configuration entity being created as part of a config sync.
  401. *
  402. * @return \Drupal\Core\Config\ConfigEntityInterface
  403. * The configuration entity.
  404. *
  405. * @see \Drupal\Core\Config\Entity\ConfigEntityStorageInterface::createFromStorageRecord()
  406. * @see \Drupal\Core\Config\Entity\ImportableEntityStorageInterface::importCreate()
  407. */
  408. protected function _doCreateFromStorageRecord(array $values, $is_syncing = FALSE) {
  409. // Assign a new UUID if there is none yet.
  410. if ($this->uuidKey && $this->uuidService && !isset($values[$this->uuidKey])) {
  411. $values[$this->uuidKey] = $this->uuidService->generate();
  412. }
  413. $data = $this->mapFromStorageRecords([$values]);
  414. $entity = current($data);
  415. $entity->original = clone $entity;
  416. $entity->setSyncing($is_syncing);
  417. $entity->enforceIsNew();
  418. $entity->postCreate($this);
  419. // Modules might need to add or change the data initially held by the new
  420. // entity object, for instance to fill-in default values.
  421. $this->invokeHook('create', $entity);
  422. return $entity;
  423. }
  424. /**
  425. * {@inheritdoc}
  426. */
  427. public function updateFromStorageRecord(ConfigEntityInterface $entity, array $values) {
  428. $entity->original = clone $entity;
  429. $data = $this->mapFromStorageRecords([$values]);
  430. $updated_entity = current($data);
  431. foreach (array_keys($values) as $property) {
  432. $value = $updated_entity->get($property);
  433. $entity->set($property, $value);
  434. }
  435. return $entity;
  436. }
  437. /**
  438. * {@inheritdoc}
  439. */
  440. public function loadOverrideFree($id) {
  441. $entities = $this->loadMultipleOverrideFree([$id]);
  442. return isset($entities[$id]) ? $entities[$id] : NULL;
  443. }
  444. /**
  445. * {@inheritdoc}
  446. */
  447. public function loadMultipleOverrideFree(array $ids = NULL) {
  448. $this->overrideFree = TRUE;
  449. $entities = $this->loadMultiple($ids);
  450. $this->overrideFree = FALSE;
  451. return $entities;
  452. }
  453. }