Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / lib / Drupal / Core / Entity / Sql / SqlContentEntityStorage.php
index 29b8a9aa95ce1373883b7dd35c6732823f72d90d..3fdf81b7488f2d9a0e2e6fee32cf413968cb680b 100644 (file)
@@ -3,12 +3,14 @@
 namespace Drupal\Core\Entity\Sql;
 
 use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\DatabaseExceptionWrapper;
 use Drupal\Core\Database\SchemaException;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\ContentEntityStorageBase;
+use Drupal\Core\Entity\ContentEntityTypeInterface;
 use Drupal\Core\Entity\EntityBundleListenerInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
@@ -132,7 +134,8 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
       $container->get('database'),
       $container->get('entity.manager'),
       $container->get('cache.entity'),
-      $container->get('language_manager')
+      $container->get('language_manager'),
+      $container->get('entity.memory_cache')
     );
   }
 
@@ -160,9 +163,11 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
    *   The cache backend to be used.
    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
    *   The language manager.
+   * @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface|null $memory_cache
+   *   The memory cache backend to be used.
    */
-  public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager) {
-    parent::__construct($entity_type, $entity_manager, $cache);
+  public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache = NULL) {
+    parent::__construct($entity_type, $entity_manager, $cache, $memory_cache);
     $this->database = $database;
     $this->languageManager = $language_manager;
     $this->initTableLayout();
@@ -180,22 +185,21 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
     $this->dataTable = NULL;
     $this->revisionDataTable = NULL;
 
-    // @todo Remove table names from the entity type definition in
-    //   https://www.drupal.org/node/2232465.
-    $this->baseTable = $this->entityType->getBaseTable() ?: $this->entityTypeId;
+    $table_mapping = $this->getTableMapping();
+    $this->baseTable = $table_mapping->getBaseTable();
     $revisionable = $this->entityType->isRevisionable();
     if ($revisionable) {
       $this->revisionKey = $this->entityType->getKey('revision') ?: 'revision_id';
-      $this->revisionTable = $this->entityType->getRevisionTable() ?: $this->entityTypeId . '_revision';
+      $this->revisionTable = $table_mapping->getRevisionTable();
     }
     $translatable = $this->entityType->isTranslatable();
     if ($translatable) {
-      $this->dataTable = $this->entityType->getDataTable() ?: $this->entityTypeId . '_field_data';
+      $this->dataTable = $table_mapping->getDataTable();
       $this->langcodeKey = $this->entityType->getKey('langcode');
       $this->defaultLangcodeKey = $this->entityType->getKey('default_langcode');
     }
     if ($revisionable && $translatable) {
-      $this->revisionDataTable = $this->entityType->getRevisionDataTable() ?: $this->entityTypeId . '_field_revision';
+      $this->revisionDataTable = $table_mapping->getRevisionDataTable();
     }
   }
 
@@ -283,6 +287,11 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
    */
   public function setTableMapping(TableMappingInterface $table_mapping) {
     $this->tableMapping = $table_mapping;
+
+    $this->baseTable = $table_mapping->getBaseTable();
+    $this->revisionTable = $table_mapping->getRevisionTable();
+    $this->dataTable = $table_mapping->getDataTable();
+    $this->revisionDataTable = $table_mapping->getRevisionDataTable();
   }
 
   /**
@@ -301,117 +310,41 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
    * {@inheritdoc}
    */
   public function getTableMapping(array $storage_definitions = NULL) {
-    $table_mapping = $this->tableMapping;
+    // If a new set of field storage definitions is passed, for instance when
+    // comparing old and new storage schema, we compute the table mapping
+    // without caching.
+    if ($storage_definitions) {
+      return $this->getCustomTableMapping($this->entityType, $storage_definitions);
+    }
 
     // If we are using our internal storage definitions, which is our main use
-    // case, we can statically cache the computed table mapping. If a new set
-    // of field storage definitions is passed, for instance when comparing old
-    // and new storage schema, we compute the table mapping without caching.
-    // @todo Clean-up this in https://www.drupal.org/node/2274017 so we can
-    //   easily instantiate a new table mapping whenever needed.
-    if (!isset($this->tableMapping) || $storage_definitions) {
-      $table_mapping_class = $this->temporary ? TemporaryTableMapping::class : DefaultTableMapping::class;
-      $definitions = $storage_definitions ?: $this->entityManager->getFieldStorageDefinitions($this->entityTypeId);
-      /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping|\Drupal\Core\Entity\Sql\TemporaryTableMapping $table_mapping */
-      $table_mapping = new $table_mapping_class($this->entityType, $definitions);
-
-      $shared_table_definitions = array_filter($definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) {
-        return $table_mapping->allowsSharedTableStorage($definition);
-      });
-
-      $key_fields = array_values(array_filter([$this->idKey, $this->revisionKey, $this->bundleKey, $this->uuidKey, $this->langcodeKey]));
-      $all_fields = array_keys($shared_table_definitions);
-      $revisionable_fields = array_keys(array_filter($shared_table_definitions, function (FieldStorageDefinitionInterface $definition) {
-        return $definition->isRevisionable();
-      }));
-      // Make sure the key fields come first in the list of fields.
-      $all_fields = array_merge($key_fields, array_diff($all_fields, $key_fields));
-
-      // If the entity is revisionable, gather the fields that need to be put
-      // in the revision table.
-      $revisionable = $this->entityType->isRevisionable();
-      $revision_metadata_fields = $revisionable ? array_values($this->entityType->getRevisionMetadataKeys()) : [];
-
-      $translatable = $this->entityType->isTranslatable();
-      if (!$revisionable && !$translatable) {
-        // The base layout stores all the base field values in the base table.
-        $table_mapping->setFieldNames($this->baseTable, $all_fields);
-      }
-      elseif ($revisionable && !$translatable) {
-        // The revisionable layout stores all the base field values in the base
-        // table, except for revision metadata fields. Revisionable fields
-        // denormalized in the base table but also stored in the revision table
-        // together with the entity ID and the revision ID as identifiers.
-        $table_mapping->setFieldNames($this->baseTable, array_diff($all_fields, $revision_metadata_fields));
-        $revision_key_fields = [$this->idKey, $this->revisionKey];
-        $table_mapping->setFieldNames($this->revisionTable, array_merge($revision_key_fields, $revisionable_fields));
-      }
-      elseif (!$revisionable && $translatable) {
-        // Multilingual layouts store key field values in the base table. The
-        // other base field values are stored in the data table, no matter
-        // whether they are translatable or not. The data table holds also a
-        // denormalized copy of the bundle field value to allow for more
-        // performant queries. This means that only the UUID is not stored on
-        // the data table.
-        $table_mapping
-          ->setFieldNames($this->baseTable, $key_fields)
-          ->setFieldNames($this->dataTable, array_values(array_diff($all_fields, [$this->uuidKey])));
-      }
-      elseif ($revisionable && $translatable) {
-        // The revisionable multilingual layout stores key field values in the
-        // base table, except for language, which is stored in the revision
-        // table along with revision metadata. The revision data table holds
-        // data field values for all the revisionable fields and the data table
-        // holds the data field values for all non-revisionable fields. The data
-        // field values of revisionable fields are denormalized in the data
-        // table, as well.
-        $table_mapping->setFieldNames($this->baseTable, array_values($key_fields));
-
-        // Like in the multilingual, non-revisionable case the UUID is not
-        // in the data table. Additionally, do not store revision metadata
-        // fields in the data table.
-        $data_fields = array_values(array_diff($all_fields, [$this->uuidKey], $revision_metadata_fields));
-        $table_mapping->setFieldNames($this->dataTable, $data_fields);
-
-        $revision_base_fields = array_merge([$this->idKey, $this->revisionKey, $this->langcodeKey], $revision_metadata_fields);
-        $table_mapping->setFieldNames($this->revisionTable, $revision_base_fields);
-
-        $revision_data_key_fields = [$this->idKey, $this->revisionKey, $this->langcodeKey];
-        $revision_data_fields = array_diff($revisionable_fields, $revision_metadata_fields, [$this->langcodeKey]);
-        $table_mapping->setFieldNames($this->revisionDataTable, array_merge($revision_data_key_fields, $revision_data_fields));
-      }
-
-      // Add dedicated tables.
-      $dedicated_table_definitions = array_filter($definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) {
-        return $table_mapping->requiresDedicatedTableStorage($definition);
-      });
-      $extra_columns = [
-        'bundle',
-        'deleted',
-        'entity_id',
-        'revision_id',
-        'langcode',
-        'delta',
-      ];
-      foreach ($dedicated_table_definitions as $field_name => $definition) {
-        $tables = [$table_mapping->getDedicatedDataTableName($definition)];
-        if ($revisionable && $definition->isRevisionable()) {
-          $tables[] = $table_mapping->getDedicatedRevisionTableName($definition);
-        }
-        foreach ($tables as $table_name) {
-          $table_mapping->setFieldNames($table_name, [$field_name]);
-          $table_mapping->setExtraColumns($table_name, $extra_columns);
-        }
-      }
+    // case, we can statically cache the computed table mapping.
+    if (!isset($this->tableMapping)) {
+      $storage_definitions = $this->entityManager->getFieldStorageDefinitions($this->entityTypeId);
 
-      // Cache the computed table mapping only if we are using our internal
-      // storage definitions.
-      if (!$storage_definitions) {
-        $this->tableMapping = $table_mapping;
-      }
+      $this->tableMapping = $this->getCustomTableMapping($this->entityType, $storage_definitions);
     }
 
-    return $table_mapping;
+    return $this->tableMapping;
+  }
+
+  /**
+   * Gets a table mapping for the specified entity type and storage definitions.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
+   *   An entity type definition.
+   * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $storage_definitions
+   *   An array of field storage definitions to be used to compute the table
+   *   mapping.
+   *
+   * @return \Drupal\Core\Entity\Sql\TableMappingInterface
+   *   A table mapping object for the entity's tables.
+   *
+   * @internal
+   */
+  public function getCustomTableMapping(ContentEntityTypeInterface $entity_type, array $storage_definitions) {
+    $table_mapping_class = $this->temporary ? TemporaryTableMapping::class : DefaultTableMapping::class;
+    return $table_mapping_class::create($entity_type, $storage_definitions);
   }
 
   /**
@@ -482,25 +415,46 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
       return [];
     }
 
+    // Get the names of the fields that are stored in the base table and, if
+    // applicable, the revision table. Other entity data will be loaded in
+    // loadFromSharedTables() and loadFromDedicatedTables().
+    $field_names = $this->tableMapping->getFieldNames($this->baseTable);
+    if ($this->revisionTable) {
+      $field_names = array_unique(array_merge($field_names, $this->tableMapping->getFieldNames($this->revisionTable)));
+    }
+
     $values = [];
     foreach ($records as $id => $record) {
       $values[$id] = [];
       // Skip the item delta and item value levels (if possible) but let the
       // field assign the value as suiting. This avoids unnecessary array
       // hierarchies and saves memory here.
-      foreach ($record as $name => $value) {
-        // Handle columns named [field_name]__[column_name] (e.g for field types
-        // that store several properties).
-        if ($field_name = strstr($name, '__', TRUE)) {
-          $property_name = substr($name, strpos($name, '__') + 2);
-          $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT][$property_name] = $value;
+      foreach ($field_names as $field_name) {
+        $field_columns = $this->tableMapping->getColumnNames($field_name);
+        // Handle field types that store several properties.
+        if (count($field_columns) > 1) {
+          foreach ($field_columns as $property_name => $column_name) {
+            if (property_exists($record, $column_name)) {
+              $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT][$property_name] = $record->{$column_name};
+              unset($record->{$column_name});
+            }
+          }
         }
+        // Handle field types that store only one property.
         else {
-          // Handle columns named directly after the field (e.g if the field
-          // type only stores one property).
-          $values[$id][$name][LanguageInterface::LANGCODE_DEFAULT] = $value;
+          $column_name = reset($field_columns);
+          if (property_exists($record, $column_name)) {
+            $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT] = $record->{$column_name};
+            unset($record->{$column_name});
+          }
         }
       }
+
+      // Handle additional record entries that are not provided by an entity
+      // field, such as 'isDefaultRevision'.
+      foreach ($record as $name => $value) {
+        $values[$id][$name][LanguageInterface::LANGCODE_DEFAULT] = $value;
+      }
     }
 
     // Initialize translations array.
@@ -564,7 +518,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
           // Some fields can have more then one columns in the data table so
           // column names are needed.
           foreach ($data_fields as $data_field) {
-            // \Drupal\Core\Entity\Sql\TableMappingInterface:: getColumNames()
+            // \Drupal\Core\Entity\Sql\TableMappingInterface::getColumnNames()
             // returns an array keyed by property names so remove the keys
             // before array_merge() to avoid losing data with fields having the
             // same columns i.e. value.
@@ -706,7 +660,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
    *   A SelectQuery object for loading the entity.
    */
   protected function buildQuery($ids, $revision_ids = FALSE) {
-    $query = $this->database->select($this->entityType->getBaseTable(), 'base');
+    $query = $this->database->select($this->baseTable, 'base');
 
     $query->addTag($this->entityTypeId . '_load_multiple');
 
@@ -784,7 +738,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
   protected function doDeleteFieldItems($entities) {
     $ids = array_keys($entities);
 
-    $this->database->delete($this->entityType->getBaseTable())
+    $this->database->delete($this->baseTable)
       ->condition($this->idKey, $ids, 'IN')
       ->execute();
 
@@ -1105,7 +1059,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
         $record->{$this->revisionKey} = $insert_id;
       }
       if ($entity->isDefaultRevision()) {
-        $this->database->update($this->entityType->getBaseTable())
+        $this->database->update($this->baseTable)
           ->fields([$this->revisionKey => $record->{$this->revisionKey}])
           ->condition($this->idKey, $record->{$this->idKey})
           ->execute();
@@ -1462,14 +1416,6 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
    * {@inheritdoc}
    */
   public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {
-    // If we are adding a field stored in a shared table we need to recompute
-    // the table mapping.
-    // @todo This does not belong here. Remove it once we are able to generate a
-    //   fresh table mapping in the schema handler. See
-    //   https://www.drupal.org/node/2274017.
-    if ($this->getTableMapping()->allowsSharedTableStorage($storage_definition)) {
-      $this->tableMapping = NULL;
-    }
     $this->wrapSchemaException(function () use ($storage_definition) {
       $this->getStorageSchema()->onFieldStorageDefinitionCreate($storage_definition);
     });
@@ -1694,16 +1640,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
     elseif ($table_mapping->allowsSharedTableStorage($storage_definition)) {
       // Ascertain the table this field is mapped too.
       $field_name = $storage_definition->getName();
-      try {
-        $table_name = $table_mapping->getFieldTableName($field_name);
-      }
-      catch (SqlContentEntityStorageException $e) {
-        // This may happen when changing field storage schema, since we are not
-        // able to use a table mapping matching the passed storage definition.
-        // @todo Revisit this once we are able to instantiate the table mapping
-        //   properly. See https://www.drupal.org/node/2274017.
-        $table_name = $this->dataTable ?: $this->baseTable;
-      }
+      $table_name = $table_mapping->getFieldTableName($field_name);
       $query = $this->database->select($table_name, 't');
       $or = $query->orConditionGroup();
       foreach (array_keys($storage_definition->getColumns()) as $property_name) {