Version 1
[yaffs-website] / web / core / lib / Drupal / Core / Config / Entity / ConfigEntityStorage.php
diff --git a/web/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php b/web/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
new file mode 100644 (file)
index 0000000..11b66fc
--- /dev/null
@@ -0,0 +1,501 @@
+<?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();
+  }
+
+  /**
+   * 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 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;
+  }
+
+}