123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508 |
- <?php
- namespace Drupal\Core\Config\Entity;
- use Drupal\Core\Cache\CacheableMetadata;
- use Drupal\Core\Config\ConfigFactoryInterface;
- use Drupal\Core\Config\ConfigImporterException;
- use Drupal\Core\Entity\EntityInterface;
- use Drupal\Core\Entity\EntityMalformedException;
- use Drupal\Core\Entity\EntityStorageBase;
- use Drupal\Core\Config\Config;
- use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException;
- use Drupal\Core\Entity\EntityTypeInterface;
- use Drupal\Component\Uuid\UuidInterface;
- use Drupal\Core\Language\LanguageManagerInterface;
- use Symfony\Component\DependencyInjection\ContainerInterface;
- /**
- * Defines the storage class for configuration entities.
- *
- * Configuration object names of configuration entities are comprised of two
- * parts, separated by a dot:
- * - config_prefix: A string denoting the owner (module/extension) of the
- * configuration object, followed by arbitrary other namespace identifiers
- * that are declared by the owning extension; e.g., 'node.type'. The
- * config_prefix does NOT contain a trailing dot. It is defined by the entity
- * type's annotation.
- * - ID: A string denoting the entity ID within the entity type namespace; e.g.,
- * 'article'. Entity IDs may contain dots/periods. The entire remaining string
- * after the config_prefix in a config name forms the entity ID. Additional or
- * custom suffixes are not possible.
- *
- * @ingroup entity_api
- */
- class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStorageInterface, ImportableEntityStorageInterface {
- /**
- * Length limit of the configuration entity ID.
- *
- * Most file systems limit a file name's length to 255 characters, so
- * ConfigBase::MAX_NAME_LENGTH restricts the full configuration object name
- * to 250 characters (leaving 5 for the file extension). The config prefix
- * is limited by ConfigEntityType::PREFIX_LENGTH to 83 characters, so this
- * leaves 166 remaining characters for the configuration entity ID, with 1
- * additional character needed for the joining dot.
- *
- * @see \Drupal\Core\Config\ConfigBase::MAX_NAME_LENGTH
- * @see \Drupal\Core\Config\Entity\ConfigEntityType::PREFIX_LENGTH
- */
- const MAX_ID_LENGTH = 166;
- /**
- * {@inheritdoc}
- */
- protected $uuidKey = 'uuid';
- /**
- * The config factory service.
- *
- * @var \Drupal\Core\Config\ConfigFactoryInterface
- */
- protected $configFactory;
- /**
- * The config storage service.
- *
- * @var \Drupal\Core\Config\StorageInterface
- */
- protected $configStorage;
- /**
- * The language manager.
- *
- * @var \Drupal\Core\Language\LanguageManagerInterface
- */
- protected $languageManager;
- /**
- * Static cache of entities, keyed first by entity ID, then by an extra key.
- *
- * The additional cache key is to maintain separate caches for different
- * states of config overrides.
- *
- * @var array
- * @see \Drupal\Core\Config\ConfigFactoryInterface::getCacheKeys().
- */
- protected $entities = [];
- /**
- * Determines if the underlying configuration is retrieved override free.
- *
- * @var bool
- */
- protected $overrideFree = FALSE;
- /**
- * Constructs a ConfigEntityStorage object.
- *
- * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
- * The entity type definition.
- * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
- * The config factory service.
- * @param \Drupal\Component\Uuid\UuidInterface $uuid_service
- * The UUID service.
- * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
- * The language manager.
- */
- public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager) {
- parent::__construct($entity_type);
- $this->configFactory = $config_factory;
- $this->uuidService = $uuid_service;
- $this->languageManager = $language_manager;
- }
- /**
- * {@inheritdoc}
- */
- public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
- return new static(
- $entity_type,
- $container->get('config.factory'),
- $container->get('uuid'),
- $container->get('language_manager')
- );
- }
- /**
- * {@inheritdoc}
- */
- public function loadRevision($revision_id) {
- return NULL;
- }
- /**
- * {@inheritdoc}
- */
- public function deleteRevision($revision_id) {
- return NULL;
- }
- /**
- * Returns the prefix used to create the configuration name.
- *
- * The prefix consists of the config prefix from the entity type plus a dot
- * for separating from the ID.
- *
- * @return string
- * The full configuration prefix, for example 'views.view.'.
- */
- protected function getPrefix() {
- return $this->entityType->getConfigPrefix() . '.';
- }
- /**
- * {@inheritdoc}
- */
- public static function getIDFromConfigName($config_name, $config_prefix) {
- return substr($config_name, strlen($config_prefix . '.'));
- }
- /**
- * {@inheritdoc}
- */
- protected function doLoadMultiple(array $ids = NULL) {
- $prefix = $this->getPrefix();
- // Get the names of the configuration entities we are going to load.
- if ($ids === NULL) {
- $names = $this->configFactory->listAll($prefix);
- }
- else {
- $names = [];
- foreach ($ids as $id) {
- // Add the prefix to the ID to serve as the configuration object name.
- $names[] = $prefix . $id;
- }
- }
- // Load all of the configuration entities.
- /** @var \Drupal\Core\Config\Config[] $configs */
- $configs = [];
- $records = [];
- foreach ($this->configFactory->loadMultiple($names) as $config) {
- $id = $config->get($this->idKey);
- $records[$id] = $this->overrideFree ? $config->getOriginal(NULL, FALSE) : $config->get();
- $configs[$id] = $config;
- }
- $entities = $this->mapFromStorageRecords($records, $configs);
- // Config entities wrap config objects, and therefore they need to inherit
- // the cacheability metadata of config objects (to ensure e.g. additional
- // cacheability metadata added by config overrides is not lost).
- foreach ($entities as $id => $entity) {
- // But rather than simply inheriting all cacheability metadata of config
- // objects, we need to make sure the self-referring cache tag that is
- // present on Config objects is not added to the Config entity. It must be
- // removed for 3 reasons:
- // 1. When renaming/duplicating a Config entity, the cache tag of the
- // original config object would remain present, which would be wrong.
- // 2. Some Config entities choose to not use the cache tag that the under-
- // lying Config object provides by default (For performance and
- // cacheability reasons it may not make sense to have a unique cache
- // tag for every Config entity. The DateFormat Config entity specifies
- // the 'rendered' cache tag for example, because A) date formats are
- // changed extremely rarely, so invalidating all render cache items is
- // fine, B) it means fewer cache tags per page.).
- // 3. Fewer cache tags is better for performance.
- $self_referring_cache_tag = ['config:' . $configs[$id]->getName()];
- $config_cacheability = CacheableMetadata::createFromObject($configs[$id]);
- $config_cacheability->setCacheTags(array_diff($config_cacheability->getCacheTags(), $self_referring_cache_tag));
- $entity->addCacheableDependency($config_cacheability);
- }
- return $entities;
- }
- /**
- * {@inheritdoc}
- */
- protected function doCreate(array $values) {
- // Set default language to current language if not provided.
- $values += [$this->langcodeKey => $this->languageManager->getCurrentLanguage()->getId()];
- $entity = new $this->entityClass($values, $this->entityTypeId);
- return $entity;
- }
- /**
- * {@inheritdoc}
- */
- protected function doDelete($entities) {
- foreach ($entities as $entity) {
- $this->configFactory->getEditable($this->getPrefix() . $entity->id())->delete();
- }
- }
- /**
- * Implements Drupal\Core\Entity\EntityStorageInterface::save().
- *
- * @throws EntityMalformedException
- * When attempting to save a configuration entity that has no ID.
- */
- public function save(EntityInterface $entity) {
- // Configuration entity IDs are strings, and '0' is a valid ID.
- $id = $entity->id();
- if ($id === NULL || $id === '') {
- throw new EntityMalformedException('The entity does not have an ID.');
- }
- // Check the configuration entity ID length.
- // @see \Drupal\Core\Config\Entity\ConfigEntityStorage::MAX_ID_LENGTH
- // @todo Consider moving this to a protected method on the parent class, and
- // abstracting it for all entity types.
- if (strlen($entity->get($this->idKey)) > self::MAX_ID_LENGTH) {
- throw new ConfigEntityIdLengthException("Configuration entity ID {$entity->get($this->idKey)} exceeds maximum allowed length of " . self::MAX_ID_LENGTH . " characters.");
- }
- return parent::save($entity);
- }
- /**
- * {@inheritdoc}
- */
- protected function doSave($id, EntityInterface $entity) {
- $is_new = $entity->isNew();
- $prefix = $this->getPrefix();
- $config_name = $prefix . $entity->id();
- if ($id !== $entity->id()) {
- // Renaming a config object needs to cater for:
- // - Storage needs to access the original object.
- // - The object needs to be renamed/copied in ConfigFactory and reloaded.
- // - All instances of the object need to be renamed.
- $this->configFactory->rename($prefix . $id, $config_name);
- }
- $config = $this->configFactory->getEditable($config_name);
- // Retrieve the desired properties and set them in config.
- $config->setData($this->mapToStorageRecord($entity));
- $config->save($entity->hasTrustedData());
- // Update the entity with the values stored in configuration. It is possible
- // that configuration schema has casted some of the values.
- if (!$entity->hasTrustedData()) {
- $data = $this->mapFromStorageRecords([$config->get()]);
- $updated_entity = current($data);
- foreach (array_keys($config->get()) as $property) {
- $value = $updated_entity->get($property);
- $entity->set($property, $value);
- }
- }
- return $is_new ? SAVED_NEW : SAVED_UPDATED;
- }
- /**
- * Maps from an entity object to the storage record.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- * The entity object.
- *
- * @return array
- * The record to store.
- */
- protected function mapToStorageRecord(EntityInterface $entity) {
- return $entity->toArray();
- }
- /**
- * {@inheritdoc}
- */
- protected function has($id, EntityInterface $entity) {
- $prefix = $this->getPrefix();
- $config = $this->configFactory->get($prefix . $id);
- return !$config->isNew();
- }
- /**
- * {@inheritdoc}
- */
- public function hasData() {
- return (bool) $this->configFactory->listAll($this->getPrefix());
- }
- /**
- * Gets entities from the static cache.
- *
- * @param array $ids
- * If not empty, return entities that match these IDs.
- *
- * @return \Drupal\Core\Entity\EntityInterface[]
- * Array of entities from the entity cache.
- */
- protected function getFromStaticCache(array $ids) {
- $entities = [];
- // Load any available entities from the internal cache.
- if ($this->entityType->isStaticallyCacheable() && !empty($this->entities)) {
- $config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys());
- foreach ($ids as $id) {
- if (!empty($this->entities[$id])) {
- if (isset($this->entities[$id][$config_overrides_key])) {
- $entities[$id] = $this->entities[$id][$config_overrides_key];
- }
- }
- }
- }
- return $entities;
- }
- /**
- * Stores entities in the static entity cache.
- *
- * @param \Drupal\Core\Entity\EntityInterface[] $entities
- * Entities to store in the cache.
- */
- protected function setStaticCache(array $entities) {
- if ($this->entityType->isStaticallyCacheable()) {
- $config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys());
- foreach ($entities as $id => $entity) {
- $this->entities[$id][$config_overrides_key] = $entity;
- }
- }
- }
- /**
- * Invokes a hook on behalf of the entity.
- *
- * @param $hook
- * One of 'presave', 'insert', 'update', 'predelete', or 'delete'.
- * @param $entity
- * The entity object.
- */
- protected function invokeHook($hook, EntityInterface $entity) {
- // Invoke the hook.
- $this->moduleHandler->invokeAll($this->entityTypeId . '_' . $hook, [$entity]);
- // Invoke the respective entity-level hook.
- $this->moduleHandler->invokeAll('entity_' . $hook, [$entity, $this->entityTypeId]);
- }
- /**
- * {@inheritdoc}
- */
- protected function getQueryServiceName() {
- return 'entity.query.config';
- }
- /**
- * {@inheritdoc}
- */
- public function importCreate($name, Config $new_config, Config $old_config) {
- $entity = $this->_doCreateFromStorageRecord($new_config->get(), TRUE);
- $entity->save();
- return TRUE;
- }
- /**
- * {@inheritdoc}
- */
- public function importUpdate($name, Config $new_config, Config $old_config) {
- $id = static::getIDFromConfigName($name, $this->entityType->getConfigPrefix());
- $entity = $this->load($id);
- if (!$entity) {
- throw new ConfigImporterException("Attempt to update non-existing entity '$id'.");
- }
- $entity->setSyncing(TRUE);
- $entity = $this->updateFromStorageRecord($entity, $new_config->get());
- $entity->save();
- return TRUE;
- }
- /**
- * {@inheritdoc}
- */
- public function importDelete($name, Config $new_config, Config $old_config) {
- $id = static::getIDFromConfigName($name, $this->entityType->getConfigPrefix());
- $entity = $this->load($id);
- $entity->setSyncing(TRUE);
- $entity->delete();
- return TRUE;
- }
- /**
- * {@inheritdoc}
- */
- public function importRename($old_name, Config $new_config, Config $old_config) {
- return $this->importUpdate($old_name, $new_config, $old_config);
- }
- /**
- * {@inheritdoc}
- */
- public function createFromStorageRecord(array $values) {
- return $this->_doCreateFromStorageRecord($values);
- }
- /**
- * Helps create a configuration entity from storage values.
- *
- * Allows the configuration entity storage to massage storage values before
- * creating an entity.
- *
- * @param array $values
- * The array of values from the configuration storage.
- * @param bool $is_syncing
- * Is the configuration entity being created as part of a config sync.
- *
- * @return \Drupal\Core\Config\ConfigEntityInterface
- * The configuration entity.
- *
- * @see \Drupal\Core\Config\Entity\ConfigEntityStorageInterface::createFromStorageRecord()
- * @see \Drupal\Core\Config\Entity\ImportableEntityStorageInterface::importCreate()
- */
- protected function _doCreateFromStorageRecord(array $values, $is_syncing = FALSE) {
- // Assign a new UUID if there is none yet.
- if ($this->uuidKey && $this->uuidService && !isset($values[$this->uuidKey])) {
- $values[$this->uuidKey] = $this->uuidService->generate();
- }
- $data = $this->mapFromStorageRecords([$values]);
- $entity = current($data);
- $entity->original = clone $entity;
- $entity->setSyncing($is_syncing);
- $entity->enforceIsNew();
- $entity->postCreate($this);
- // Modules might need to add or change the data initially held by the new
- // entity object, for instance to fill-in default values.
- $this->invokeHook('create', $entity);
- return $entity;
- }
- /**
- * {@inheritdoc}
- */
- public function updateFromStorageRecord(ConfigEntityInterface $entity, array $values) {
- $entity->original = clone $entity;
- $data = $this->mapFromStorageRecords([$values]);
- $updated_entity = current($data);
- foreach (array_keys($values) as $property) {
- $value = $updated_entity->get($property);
- $entity->set($property, $value);
- }
- return $entity;
- }
- /**
- * {@inheritdoc}
- */
- public function loadOverrideFree($id) {
- $entities = $this->loadMultipleOverrideFree([$id]);
- return isset($entities[$id]) ? $entities[$id] : NULL;
- }
- /**
- * {@inheritdoc}
- */
- public function loadMultipleOverrideFree(array $ids = NULL) {
- $this->overrideFree = TRUE;
- $entities = $this->loadMultiple($ids);
- $this->overrideFree = FALSE;
- return $entities;
- }
- }
|