View source
<?php
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\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Utility\Error;
use Symfony\Component\DependencyInjection\ContainerInterface;
class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEntityStorageInterface, DynamicallyFieldableEntityStorageSchemaInterface, EntityBundleListenerInterface {
protected $fieldStorageDefinitions;
protected $tableMapping;
protected $revisionKey = FALSE;
protected $langcodeKey = FALSE;
protected $defaultLangcodeKey = FALSE;
protected $baseTable;
protected $revisionTable;
protected $dataTable;
protected $revisionDataTable;
protected $database;
protected $storageSchema;
protected $languageManager;
protected $entityTypeManager;
protected $temporary = FALSE;
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static($entity_type, $container
->get('database'), $container
->get('entity_field.manager'), $container
->get('cache.entity'), $container
->get('language_manager'), $container
->get('entity.memory_cache'), $container
->get('entity_type.bundle.info'), $container
->get('entity_type.manager'));
}
public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityFieldManagerInterface $entity_field_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($entity_type, $entity_field_manager, $cache, $memory_cache, $entity_type_bundle_info);
$this->database = $database;
$this->languageManager = $language_manager;
$this->entityTypeManager = $entity_type_manager;
$this->entityType = $this->entityTypeManager
->getActiveDefinition($entity_type
->id());
$this->fieldStorageDefinitions = $this->entityFieldManager
->getActiveFieldStorageDefinitions($entity_type
->id());
$this
->initTableLayout();
}
protected function initTableLayout() {
$this->tableMapping = NULL;
$this->revisionKey = NULL;
$this->revisionTable = NULL;
$this->dataTable = NULL;
$this->revisionDataTable = NULL;
$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 = $table_mapping
->getRevisionTable();
}
$translatable = $this->entityType
->isTranslatable();
if ($translatable) {
$this->dataTable = $table_mapping
->getDataTable();
$this->langcodeKey = $this->entityType
->getKey('langcode');
$this->defaultLangcodeKey = $this->entityType
->getKey('default_langcode');
}
if ($revisionable && $translatable) {
$this->revisionDataTable = $table_mapping
->getRevisionDataTable();
}
}
public function getBaseTable() {
return $this->baseTable;
}
public function getRevisionTable() {
return $this->revisionTable;
}
public function getDataTable() {
return $this->dataTable;
}
public function getRevisionDataTable() {
return $this->revisionDataTable;
}
protected function getStorageSchema() {
if (!isset($this->storageSchema)) {
$class = $this->entityType
->getHandlerClass('storage_schema') ?: 'Drupal\\Core\\Entity\\Sql\\SqlContentEntityStorageSchema';
$this->storageSchema = new $class($this->entityTypeManager, $this->entityType, $this, $this->database, $this->entityFieldManager);
}
return $this->storageSchema;
}
public function setEntityType(EntityTypeInterface $entity_type) {
if ($this->entityType
->id() == $entity_type
->id()) {
$this->entityType = $entity_type;
$this
->initTableLayout();
}
else {
throw new EntityStorageException("Unsupported entity type {$entity_type->id()}");
}
}
public function setFieldStorageDefinitions(array $field_storage_definitions) {
foreach ($field_storage_definitions as $field_storage_definition) {
if ($field_storage_definition
->getTargetEntityTypeId() !== $this->entityType
->id()) {
throw new EntityStorageException("Unsupported entity type {$field_storage_definition->getTargetEntityTypeId()}");
}
}
$this->fieldStorageDefinitions = $field_storage_definitions;
}
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();
}
public function setTemporary($temporary) {
$this->temporary = $temporary;
}
public function getTableMapping(array $storage_definitions = NULL) {
if ($storage_definitions) {
return $this
->getCustomTableMapping($this->entityType, $storage_definitions);
}
if (!isset($this->tableMapping)) {
$this->tableMapping = $this
->getCustomTableMapping($this->entityType, $this->fieldStorageDefinitions);
}
return $this->tableMapping;
}
public function getCustomTableMapping(ContentEntityTypeInterface $entity_type, array $storage_definitions, $prefix = '') {
$prefix = $prefix ?: ($this->temporary ? 'tmp_' : '');
return DefaultTableMapping::create($entity_type, $storage_definitions, $prefix);
}
protected function doLoadMultiple(array $ids = NULL) {
$entities_from_cache = $this
->getFromPersistentCache($ids);
if ($entities_from_storage = $this
->getFromStorage($ids)) {
$this
->invokeStorageLoadHook($entities_from_storage);
$this
->setPersistentCache($entities_from_storage);
}
return $entities_from_cache + $entities_from_storage;
}
protected function getFromStorage(array $ids = NULL) {
$entities = [];
if (!empty($ids)) {
$ids = $this
->cleanIds($ids);
}
if ($ids === NULL || $ids) {
$query_result = $this
->buildQuery($ids)
->execute();
$records = $query_result
->fetchAllAssoc($this->idKey);
if ($records) {
$entities = $this
->mapFromStorageRecords($records);
}
}
return $entities;
}
protected function mapFromStorageRecords(array $records, $load_from_revision = FALSE) {
if (!$records) {
return [];
}
$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] = [];
foreach ($field_names as $field_name) {
$field_columns = $this->tableMapping
->getColumnNames($field_name);
if (count($field_columns) > 1) {
$definition_columns = $this->fieldStorageDefinitions[$field_name]
->getColumns();
foreach ($field_columns as $property_name => $column_name) {
if (property_exists($record, $column_name)) {
$values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT][$property_name] = !empty($definition_columns[$property_name]['serialize']) ? unserialize($record->{$column_name}) : $record->{$column_name};
unset($record->{$column_name});
}
}
}
else {
$column_name = reset($field_columns);
if (property_exists($record, $column_name)) {
$columns = $this->fieldStorageDefinitions[$field_name]
->getColumns();
$column = reset($columns);
$values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT] = !empty($column['serialize']) ? unserialize($record->{$column_name}) : $record->{$column_name};
unset($record->{$column_name});
}
}
}
foreach ($record as $name => $value) {
$values[$id][$name][LanguageInterface::LANGCODE_DEFAULT] = $value;
}
}
$translations = array_fill_keys(array_keys($values), []);
$this
->loadFromSharedTables($values, $translations, $load_from_revision);
$this
->loadFromDedicatedTables($values, $load_from_revision);
$entities = [];
foreach ($values as $id => $entity_values) {
$bundle = $this->bundleKey ? $entity_values[$this->bundleKey][LanguageInterface::LANGCODE_DEFAULT] : NULL;
$entity_class = $this
->getEntityClass($bundle);
$entities[$id] = new $entity_class($entity_values, $this->entityTypeId, $bundle, array_keys($translations[$id]));
}
return $entities;
}
protected function loadFromSharedTables(array &$values, array &$translations, $load_from_revision) {
$record_key = !$load_from_revision ? $this->idKey : $this->revisionKey;
if ($this->dataTable) {
$table = $this->revisionDataTable ?: $this->dataTable;
$alias = $this->revisionDataTable ? 'revision' : 'data';
$query = $this->database
->select($table, $alias, [
'fetch' => \PDO::FETCH_ASSOC,
])
->fields($alias)
->condition($alias . '.' . $record_key, array_keys($values), 'IN')
->orderBy($alias . '.' . $record_key);
$table_mapping = $this
->getTableMapping();
if ($this->revisionDataTable) {
$base_fields = array_diff($table_mapping
->getFieldNames($this->baseTable), [
$this->langcodeKey,
]);
$revisioned_fields = array_diff($table_mapping
->getFieldNames($this->revisionDataTable), $base_fields);
$data_fields = array_diff($table_mapping
->getFieldNames($this->dataTable), $revisioned_fields, $base_fields);
$all_fields = $revisioned_fields;
if ($data_fields) {
$all_fields = array_merge($revisioned_fields, $data_fields);
$query
->leftJoin($this->dataTable, 'data', "([revision].[{$this->idKey}] = [data].[{$this->idKey}] AND [revision].[{$this->langcodeKey}] = [data].[{$this->langcodeKey}])");
$column_names = [];
foreach ($data_fields as $data_field) {
$column_names[] = array_values($table_mapping
->getColumnNames($data_field));
}
$column_names = array_merge(...$column_names);
$query
->fields('data', $column_names);
}
$revision_ids = [];
foreach ($values as $entity_values) {
$revision_ids[] = $entity_values[$this->revisionKey][LanguageInterface::LANGCODE_DEFAULT];
}
$query
->condition('revision.' . $this->revisionKey, $revision_ids, 'IN');
}
else {
$all_fields = $table_mapping
->getFieldNames($this->dataTable);
}
$result = $query
->execute();
foreach ($result as $row) {
$id = $row[$record_key];
$langcode = empty($row[$this->defaultLangcodeKey]) ? $row[$this->langcodeKey] : LanguageInterface::LANGCODE_DEFAULT;
$translations[$id][$langcode] = TRUE;
foreach ($all_fields as $field_name) {
$storage_definition = $this->fieldStorageDefinitions[$field_name];
$definition_columns = $storage_definition
->getColumns();
$columns = $table_mapping
->getColumnNames($field_name);
if (count($columns) == 1) {
$column_name = reset($columns);
$column_attributes = $definition_columns[key($columns)];
$values[$id][$field_name][$langcode] = !empty($column_attributes['serialize']) ? unserialize($row[$column_name]) : $row[$column_name];
}
else {
foreach ($columns as $property_name => $column_name) {
$column_attributes = $definition_columns[$property_name];
$values[$id][$field_name][$langcode][$property_name] = !empty($column_attributes['serialize']) ? unserialize($row[$column_name]) : $row[$column_name];
}
}
}
}
}
}
protected function doLoadMultipleRevisionsFieldItems($revision_ids) {
$revisions = [];
$revision_ids = $this
->cleanIds($revision_ids, 'revision');
if (!empty($revision_ids)) {
$query_result = $this
->buildQuery(NULL, $revision_ids)
->execute();
$records = $query_result
->fetchAllAssoc($this->revisionKey);
if ($records) {
$revisions = $this
->mapFromStorageRecords($records, TRUE);
}
}
return $revisions;
}
protected function doDeleteRevisionFieldItems(ContentEntityInterface $revision) {
$this->database
->delete($this->revisionTable)
->condition($this->revisionKey, $revision
->getRevisionId())
->execute();
if ($this->revisionDataTable) {
$this->database
->delete($this->revisionDataTable)
->condition($this->revisionKey, $revision
->getRevisionId())
->execute();
}
$this
->deleteRevisionFromDedicatedTables($revision);
}
protected function buildPropertyQuery(QueryInterface $entity_query, array $values) {
if ($this->dataTable) {
if (!array_key_exists($this->defaultLangcodeKey, $values)) {
$values[$this->defaultLangcodeKey] = 1;
}
elseif ($values[$this->defaultLangcodeKey] === NULL) {
unset($values[$this->defaultLangcodeKey]);
}
}
parent::buildPropertyQuery($entity_query, $values);
}
protected function buildQuery($ids, $revision_ids = FALSE) {
$query = $this->database
->select($this->baseTable, 'base');
$query
->addTag($this->entityTypeId . '_load_multiple');
if ($revision_ids) {
$query
->join($this->revisionTable, 'revision', "[revision].[{$this->idKey}] = [base].[{$this->idKey}] AND [revision].[{$this->revisionKey}] IN (:revisionIds[])", [
':revisionIds[]' => $revision_ids,
]);
}
elseif ($this->revisionTable) {
$query
->join($this->revisionTable, 'revision', "[revision].[{$this->revisionKey}] = [base].[{$this->revisionKey}]");
}
$table_mapping = $this
->getTableMapping();
$entity_fields = $table_mapping
->getAllColumns($this->baseTable);
if ($this->revisionTable) {
$entity_revision_fields = $table_mapping
->getAllColumns($this->revisionTable);
$entity_revision_fields = array_combine($entity_revision_fields, $entity_revision_fields);
unset($entity_revision_fields[$this->idKey]);
$entity_field_keys = array_flip($entity_fields);
foreach ($entity_revision_fields as $name) {
if (isset($entity_field_keys[$name])) {
unset($entity_fields[$entity_field_keys[$name]]);
}
}
$query
->fields('revision', $entity_revision_fields);
$query
->addExpression('CASE [base].[' . $this->revisionKey . '] WHEN [revision].[' . $this->revisionKey . '] THEN 1 ELSE 0 END', 'isDefaultRevision');
}
$query
->fields('base', $entity_fields);
if ($ids) {
$query
->condition("base.{$this->idKey}", $ids, 'IN');
}
return $query;
}
public function delete(array $entities) {
if (!$entities) {
return;
}
try {
$transaction = $this->database
->startTransaction();
parent::delete($entities);
\Drupal::service('database.replica_kill_switch')
->trigger();
} catch (\Exception $e) {
if (isset($transaction)) {
$transaction
->rollBack();
}
Error::logException(\Drupal::logger($this->entityTypeId), $e);
throw new EntityStorageException($e
->getMessage(), $e
->getCode(), $e);
}
}
protected function doDeleteFieldItems($entities) {
$ids = array_keys($entities);
$this->database
->delete($this->baseTable)
->condition($this->idKey, $ids, 'IN')
->execute();
if ($this->revisionTable) {
$this->database
->delete($this->revisionTable)
->condition($this->idKey, $ids, 'IN')
->execute();
}
if ($this->dataTable) {
$this->database
->delete($this->dataTable)
->condition($this->idKey, $ids, 'IN')
->execute();
}
if ($this->revisionDataTable) {
$this->database
->delete($this->revisionDataTable)
->condition($this->idKey, $ids, 'IN')
->execute();
}
foreach ($entities as $entity) {
$this
->deleteFromDedicatedTables($entity);
}
}
public function save(EntityInterface $entity) {
try {
$transaction = $this->database
->startTransaction();
$return = parent::save($entity);
\Drupal::service('database.replica_kill_switch')
->trigger();
return $return;
} catch (\Exception $e) {
if (isset($transaction)) {
$transaction
->rollBack();
}
Error::logException(\Drupal::logger($this->entityTypeId), $e);
throw new EntityStorageException($e
->getMessage(), $e
->getCode(), $e);
}
}
public function restore(EntityInterface $entity) {
try {
$transaction = $this->database
->startTransaction();
if ($entity
->isDefaultRevision()) {
$record = $this
->mapToStorageRecord($entity
->getUntranslated(), $this->baseTable);
$this->database
->insert($this->baseTable)
->fields((array) $record)
->execute();
if ($this->dataTable) {
$this
->saveToSharedTables($entity);
}
}
if ($this->revisionTable) {
$record = $this
->mapToStorageRecord($entity
->getUntranslated(), $this->revisionTable);
$this->database
->insert($this->revisionTable)
->fields((array) $record)
->execute();
if ($this->revisionDataTable) {
$this
->saveToSharedTables($entity, $this->revisionDataTable);
}
}
$this
->saveToDedicatedTables($entity, FALSE, []);
\Drupal::service('database.replica_kill_switch')
->trigger();
} catch (\Exception $e) {
if (isset($transaction)) {
$transaction
->rollBack();
}
Error::logException(\Drupal::logger($this->entityTypeId), $e);
throw new EntityStorageException($e
->getMessage(), $e
->getCode(), $e);
}
}
protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []) {
$full_save = empty($names);
$update = !$full_save || !$entity
->isNew();
if ($full_save) {
$shared_table_fields = TRUE;
$dedicated_table_fields = TRUE;
}
else {
$table_mapping = $this
->getTableMapping();
$shared_table_fields = FALSE;
$dedicated_table_fields = [];
foreach ($names as $name) {
$storage_definition = $this->fieldStorageDefinitions[$name];
if ($table_mapping
->allowsSharedTableStorage($storage_definition)) {
$shared_table_fields = TRUE;
}
elseif ($table_mapping
->requiresDedicatedTableStorage($storage_definition)) {
$dedicated_table_fields[] = $name;
}
}
}
if ($shared_table_fields) {
$record = $this
->mapToStorageRecord($entity
->getUntranslated(), $this->baseTable);
if ($update) {
$default_revision = $entity
->isDefaultRevision();
if ($default_revision) {
$id = $record->{$this->idKey};
unset($record->{$this->idKey});
$this->database
->update($this->baseTable)
->fields((array) $record)
->condition($this->idKey, $id)
->execute();
}
if ($this->revisionTable) {
if ($full_save) {
$entity->{$this->revisionKey} = $this
->saveRevision($entity);
}
else {
$record = $this
->mapToStorageRecord($entity
->getUntranslated(), $this->revisionTable);
unset($record->{$this->revisionKey});
$entity
->preSaveRevision($this, $record);
$this->database
->update($this->revisionTable)
->fields((array) $record)
->condition($this->revisionKey, $entity
->getRevisionId())
->execute();
}
}
if ($default_revision && $this->dataTable) {
$this
->saveToSharedTables($entity);
}
if ($this->revisionDataTable) {
$new_revision = $full_save && $entity
->isNewRevision();
$this
->saveToSharedTables($entity, $this->revisionDataTable, $new_revision);
}
}
else {
$insert_id = $this->database
->insert($this->baseTable, [
'return' => Database::RETURN_INSERT_ID,
])
->fields((array) $record)
->execute();
if (!isset($record->{$this->idKey})) {
$record->{$this->idKey} = $insert_id;
}
$entity->{$this->idKey} = (string) $record->{$this->idKey};
if ($this->revisionTable) {
$record->{$this->revisionKey} = $this
->saveRevision($entity);
}
if ($this->dataTable) {
$this
->saveToSharedTables($entity);
}
if ($this->revisionDataTable) {
$this
->saveToSharedTables($entity, $this->revisionDataTable);
}
}
}
if ($dedicated_table_fields) {
$names = is_array($dedicated_table_fields) ? $dedicated_table_fields : [];
$this
->saveToDedicatedTables($entity, $update, $names);
}
}
protected function has($id, EntityInterface $entity) {
return !$entity
->isNew();
}
protected function saveToSharedTables(ContentEntityInterface $entity, $table_name = NULL, $new_revision = NULL) {
if (!isset($table_name)) {
$table_name = $this->dataTable;
}
if (!isset($new_revision)) {
$new_revision = $entity
->isNewRevision();
}
$revision = $table_name != $this->dataTable;
if (!$revision || !$new_revision) {
$key = $revision ? $this->revisionKey : $this->idKey;
$value = $revision ? $entity
->getRevisionId() : $entity
->id();
$this->database
->delete($table_name)
->condition($key, $value)
->execute();
}
$query = $this->database
->insert($table_name);
foreach ($entity
->getTranslationLanguages() as $langcode => $language) {
$translation = $entity
->getTranslation($langcode);
$record = $this
->mapToDataStorageRecord($translation, $table_name);
$values = (array) $record;
$query
->fields(array_keys($values))
->values($values);
}
$query
->execute();
}
protected function mapToStorageRecord(ContentEntityInterface $entity, $table_name = NULL) {
if (!isset($table_name)) {
$table_name = $this->baseTable;
}
$record = new \stdClass();
$table_mapping = $this
->getTableMapping();
foreach ($table_mapping
->getFieldNames($table_name) as $field_name) {
if (empty($this->fieldStorageDefinitions[$field_name])) {
throw new EntityStorageException("Table mapping contains invalid field {$field_name}.");
}
$definition = $this->fieldStorageDefinitions[$field_name];
$columns = $table_mapping
->getColumnNames($field_name);
foreach ($columns as $column_name => $schema_name) {
if (!$definition
->getMainPropertyName() && count($columns) == 1) {
$value = ($item = $entity->{$field_name}
->first()) ? $item
->getValue() : [];
}
else {
$value = $entity->{$field_name}->{$column_name} ?? NULL;
}
if (!empty($definition
->getSchema()['columns'][$column_name]['serialize'])) {
$value = serialize($value);
}
$value = SqlContentEntityStorageSchema::castValue($definition
->getSchema()['columns'][$column_name], $value);
$empty_serial = empty($value) && $this
->isColumnSerial($table_name, $schema_name);
$user_zero = $this->entityTypeId === 'user' && $value === 0;
if (!$empty_serial || $user_zero) {
$record->{$schema_name} = $value;
}
}
}
return $record;
}
protected function isColumnSerial($table_name, $schema_name) {
$result = FALSE;
switch ($table_name) {
case $this->baseTable:
$result = $schema_name == $this->idKey;
break;
case $this->revisionTable:
$result = $schema_name == $this->revisionKey;
break;
}
return $result;
}
protected function mapToDataStorageRecord(EntityInterface $entity, $table_name = NULL) {
if (!isset($table_name)) {
$table_name = $this->dataTable;
}
$record = $this
->mapToStorageRecord($entity, $table_name);
return $record;
}
protected function saveRevision(ContentEntityInterface $entity) {
$record = $this
->mapToStorageRecord($entity
->getUntranslated(), $this->revisionTable);
$entity
->preSaveRevision($this, $record);
if ($entity
->isNewRevision()) {
$insert_id = $this->database
->insert($this->revisionTable, [
'return' => Database::RETURN_INSERT_ID,
])
->fields((array) $record)
->execute();
if (!isset($record->{$this->revisionKey})) {
$record->{$this->revisionKey} = $insert_id;
}
if ($entity
->isDefaultRevision()) {
$this->database
->update($this->baseTable)
->fields([
$this->revisionKey => $record->{$this->revisionKey},
])
->condition($this->idKey, $record->{$this->idKey})
->execute();
}
$entity->{$this->revisionKey}->value = $record->{$this->revisionKey};
}
else {
unset($record->{$this->revisionKey});
$this->database
->update($this->revisionTable)
->fields((array) $record)
->condition($this->revisionKey, $entity
->getRevisionId())
->execute();
}
return $entity
->getRevisionId();
}
protected function getQueryServiceName() {
return 'entity.query.sql';
}
protected function loadFromDedicatedTables(array &$values, $load_from_revision) {
if (empty($values)) {
return;
}
$bundles = [];
$ids = [];
$default_langcodes = [];
foreach ($values as $key => $entity_values) {
$bundles[$this->bundleKey ? $entity_values[$this->bundleKey][LanguageInterface::LANGCODE_DEFAULT] : $this->entityTypeId] = TRUE;
$ids[] = !$load_from_revision ? $key : $entity_values[$this->revisionKey][LanguageInterface::LANGCODE_DEFAULT];
if ($this->langcodeKey && isset($entity_values[$this->langcodeKey][LanguageInterface::LANGCODE_DEFAULT])) {
$default_langcodes[$key] = $entity_values[$this->langcodeKey][LanguageInterface::LANGCODE_DEFAULT];
}
}
$storage_definitions = [];
$definitions = [];
$table_mapping = $this
->getTableMapping();
foreach ($bundles as $bundle => $v) {
$definitions[$bundle] = $this->entityFieldManager
->getFieldDefinitions($this->entityTypeId, $bundle);
foreach ($definitions[$bundle] as $field_name => $field_definition) {
$storage_definition = $field_definition
->getFieldStorageDefinition();
if ($table_mapping
->requiresDedicatedTableStorage($storage_definition)) {
$storage_definitions[$field_name] = $storage_definition;
}
}
}
$langcodes = array_keys($this->languageManager
->getLanguages(LanguageInterface::STATE_ALL));
foreach ($storage_definitions as $field_name => $storage_definition) {
$table = !$load_from_revision ? $table_mapping
->getDedicatedDataTableName($storage_definition) : $table_mapping
->getDedicatedRevisionTableName($storage_definition);
$results = $this->database
->select($table, 't')
->fields('t')
->condition(!$load_from_revision ? 'entity_id' : 'revision_id', $ids, 'IN')
->condition('deleted', 0)
->condition('langcode', $langcodes, 'IN')
->orderBy('delta')
->execute();
foreach ($results as $row) {
$bundle = $row->bundle;
$value_key = !$load_from_revision ? $row->entity_id : $row->revision_id;
$langcode = LanguageInterface::LANGCODE_DEFAULT;
if ($this->langcodeKey && isset($default_langcodes[$value_key]) && $row->langcode != $default_langcodes[$value_key]) {
$langcode = $row->langcode;
}
if (!isset($values[$value_key][$field_name][$langcode])) {
$values[$value_key][$field_name][$langcode] = [];
}
if ($langcode == LanguageInterface::LANGCODE_DEFAULT || $definitions[$bundle][$field_name]
->isTranslatable()) {
if ($storage_definition
->getCardinality() == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || count($values[$value_key][$field_name][$langcode]) < $storage_definition
->getCardinality()) {
$item = [];
foreach ($storage_definition
->getColumns() as $column => $attributes) {
$column_name = $table_mapping
->getFieldColumnName($storage_definition, $column);
$item[$column] = !empty($attributes['serialize']) ? unserialize($row->{$column_name}) : $row->{$column_name};
}
$values[$value_key][$field_name][$langcode][] = $item;
}
}
}
}
}
protected function saveToDedicatedTables(ContentEntityInterface $entity, $update = TRUE, $names = []) {
$vid = $entity
->getRevisionId();
$id = $entity
->id();
$bundle = $entity
->bundle();
$entity_type = $entity
->getEntityTypeId();
$default_langcode = $entity
->getUntranslated()
->language()
->getId();
$translation_langcodes = array_keys($entity
->getTranslationLanguages());
$table_mapping = $this
->getTableMapping();
if (!isset($vid)) {
$vid = $id;
}
$original = !empty($entity->original) ? $entity->original : NULL;
if ($original && !$entity
->isNewRevision() && !$entity
->isDefaultRevision()) {
$original = $this
->loadRevision($entity
->getLoadedRevisionId());
}
$definitions = $this->entityFieldManager
->getFieldDefinitions($entity_type, $bundle);
if ($names) {
$definitions = array_intersect_key($definitions, array_flip($names));
}
foreach ($definitions as $field_name => $field_definition) {
$storage_definition = $field_definition
->getFieldStorageDefinition();
if (!$table_mapping
->requiresDedicatedTableStorage($storage_definition)) {
continue;
}
if (!$entity
->isNewRevision() && $original && !$this
->hasFieldValueChanged($field_definition, $entity, $original)) {
continue;
}
$table_name = $table_mapping
->getDedicatedDataTableName($storage_definition);
$revision_name = $table_mapping
->getDedicatedRevisionTableName($storage_definition);
if ($update) {
if ($entity
->isDefaultRevision()) {
$this->database
->delete($table_name)
->condition('entity_id', $id)
->execute();
}
if ($this->entityType
->isRevisionable()) {
$this->database
->delete($revision_name)
->condition('entity_id', $id)
->condition('revision_id', $vid)
->execute();
}
}
$do_insert = FALSE;
$columns = [
'entity_id',
'revision_id',
'bundle',
'delta',
'langcode',
];
foreach ($storage_definition
->getColumns() as $column => $attributes) {
$columns[] = $table_mapping
->getFieldColumnName($storage_definition, $column);
}
$query = $this->database
->insert($table_name)
->fields($columns);
if ($this->entityType
->isRevisionable()) {
$revision_query = $this->database
->insert($revision_name)
->fields($columns);
}
$langcodes = $field_definition
->isTranslatable() ? $translation_langcodes : [
$default_langcode,
];
foreach ($langcodes as $langcode) {
$delta_count = 0;
$items = $entity
->getTranslation($langcode)
->get($field_name);
$items
->filterEmptyItems();
foreach ($items as $delta => $item) {
$do_insert = TRUE;
$record = [
'entity_id' => $id,
'revision_id' => $vid,
'bundle' => $bundle,
'delta' => $delta,
'langcode' => $langcode,
];
foreach ($storage_definition
->getColumns() as $column => $attributes) {
$column_name = $table_mapping
->getFieldColumnName($storage_definition, $column);
$value = $item->{$column};
if (!empty($attributes['serialize'])) {
$value = serialize($value);
}
$record[$column_name] = SqlContentEntityStorageSchema::castValue($attributes, $value);
}
$query
->values($record);
if ($this->entityType
->isRevisionable()) {
$revision_query
->values($record);
}
if ($storage_definition
->getCardinality() != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && ++$delta_count == $storage_definition
->getCardinality()) {
break;
}
}
}
if ($do_insert) {
if ($entity
->isDefaultRevision()) {
$query
->execute();
}
if ($this->entityType
->isRevisionable()) {
$revision_query
->execute();
}
}
}
}
protected function deleteFromDedicatedTables(ContentEntityInterface $entity) {
$table_mapping = $this
->getTableMapping();
foreach ($this->fieldStorageDefinitions as $storage_definition) {
if (!$table_mapping
->requiresDedicatedTableStorage($storage_definition)) {
continue;
}
$table_name = $table_mapping
->getDedicatedDataTableName($storage_definition);
$revision_name = $table_mapping
->getDedicatedRevisionTableName($storage_definition);
$this->database
->delete($table_name)
->condition('entity_id', $entity
->id())
->execute();
if ($this->entityType
->isRevisionable()) {
$this->database
->delete($revision_name)
->condition('entity_id', $entity
->id())
->execute();
}
}
}
protected function deleteRevisionFromDedicatedTables(ContentEntityInterface $entity) {
$vid = $entity
->getRevisionId();
if (isset($vid)) {
$table_mapping = $this
->getTableMapping();
foreach ($this->fieldStorageDefinitions as $storage_definition) {
if (!$table_mapping
->requiresDedicatedTableStorage($storage_definition)) {
continue;
}
$revision_name = $table_mapping
->getDedicatedRevisionTableName($storage_definition);
$this->database
->delete($revision_name)
->condition('entity_id', $entity
->id())
->condition('revision_id', $vid)
->execute();
}
}
}
public function requiresEntityStorageSchemaChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
return $this
->getStorageSchema()
->requiresEntityStorageSchemaChanges($entity_type, $original);
}
public function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
return $this
->getStorageSchema()
->requiresFieldStorageSchemaChanges($storage_definition, $original);
}
public function requiresEntityDataMigration(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
return $this
->getStorageSchema()
->requiresEntityDataMigration($entity_type, $original);
}
public function requiresFieldDataMigration(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
return $this
->getStorageSchema()
->requiresFieldDataMigration($storage_definition, $original);
}
public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
$this
->wrapSchemaException(function () use ($entity_type) {
$this
->getStorageSchema()
->onEntityTypeCreate($entity_type);
});
}
public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
$this->entityType = $entity_type;
$this
->initTableLayout();
$this
->wrapSchemaException(function () use ($entity_type, $original) {
$this
->getStorageSchema()
->onEntityTypeUpdate($entity_type, $original);
});
}
public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
$this
->wrapSchemaException(function () use ($entity_type) {
$this
->getStorageSchema()
->onEntityTypeDelete($entity_type);
});
}
public function onFieldableEntityTypeCreate(EntityTypeInterface $entity_type, array $field_storage_definitions) {
$this
->wrapSchemaException(function () use ($entity_type, $field_storage_definitions) {
$this
->getStorageSchema()
->onFieldableEntityTypeCreate($entity_type, $field_storage_definitions);
});
}
public function onFieldableEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original, array $field_storage_definitions, array $original_field_storage_definitions, array &$sandbox = NULL) {
$this
->wrapSchemaException(function () use ($entity_type, $original, $field_storage_definitions, $original_field_storage_definitions, &$sandbox) {
$this
->getStorageSchema()
->onFieldableEntityTypeUpdate($entity_type, $original, $field_storage_definitions, $original_field_storage_definitions, $sandbox);
});
}
public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {
$this
->wrapSchemaException(function () use ($storage_definition) {
$this
->getStorageSchema()
->onFieldStorageDefinitionCreate($storage_definition);
$this->fieldStorageDefinitions[$storage_definition
->getName()] = $storage_definition;
$this->tableMapping = NULL;
});
}
public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
$this
->wrapSchemaException(function () use ($storage_definition, $original) {
$this
->getStorageSchema()
->onFieldStorageDefinitionUpdate($storage_definition, $original);
$this->fieldStorageDefinitions[$storage_definition
->getName()] = $storage_definition;
$this->tableMapping = NULL;
});
}
public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) {
$table_mapping = $this
->getTableMapping();
if ($table_mapping
->requiresDedicatedTableStorage($storage_definition)) {
$table = $table_mapping
->getDedicatedDataTableName($storage_definition);
$revision_table = $table_mapping
->getDedicatedRevisionTableName($storage_definition);
$this->database
->update($table)
->fields([
'deleted' => 1,
])
->execute();
if ($this->entityType
->isRevisionable()) {
$this->database
->update($revision_table)
->fields([
'deleted' => 1,
])
->execute();
}
}
$this
->wrapSchemaException(function () use ($storage_definition) {
$this
->getStorageSchema()
->onFieldStorageDefinitionDelete($storage_definition);
unset($this->fieldStorageDefinitions[$storage_definition
->getName()]);
$this->tableMapping = NULL;
});
}
protected function wrapSchemaException(callable $callback) {
$message = 'Exception thrown while performing a schema update.';
try {
$callback();
} catch (SchemaException $e) {
$message .= ' ' . $e
->getMessage();
throw new EntityStorageException($message, 0, $e);
} catch (DatabaseExceptionWrapper $e) {
$message .= ' ' . $e
->getMessage();
throw new EntityStorageException($message, 0, $e);
}
}
public function onFieldDefinitionDelete(FieldDefinitionInterface $field_definition) {
$table_mapping = $this
->getTableMapping();
$storage_definition = $field_definition
->getFieldStorageDefinition();
if ($table_mapping
->requiresDedicatedTableStorage($storage_definition)) {
$table_name = $table_mapping
->getDedicatedDataTableName($storage_definition);
$revision_name = $table_mapping
->getDedicatedRevisionTableName($storage_definition);
$this->database
->update($table_name)
->fields([
'deleted' => 1,
])
->condition('bundle', $field_definition
->getTargetBundle())
->execute();
if ($this->entityType
->isRevisionable()) {
$this->database
->update($revision_name)
->fields([
'deleted' => 1,
])
->condition('bundle', $field_definition
->getTargetBundle())
->execute();
}
}
}
public function onBundleCreate($bundle, $entity_type_id) {
}
public function onBundleDelete($bundle, $entity_type_id) {
}
protected function readFieldItemsToPurge(FieldDefinitionInterface $field_definition, $batch_size) {
$storage_definition = $field_definition
->getFieldStorageDefinition();
$table_mapping = $this
->getTableMapping();
$table_name = $table_mapping
->getDedicatedDataTableName($storage_definition, $storage_definition
->isDeleted());
$entity_query = $this->database
->select($table_name, 't', [
'fetch' => \PDO::FETCH_ASSOC,
]);
$or = $entity_query
->orConditionGroup();
foreach ($storage_definition
->getColumns() as $column_name => $data) {
$or
->isNotNull($table_mapping
->getFieldColumnName($storage_definition, $column_name));
}
$entity_query
->distinct(TRUE)
->fields('t', [
'entity_id',
])
->condition('bundle', $field_definition
->getTargetBundle())
->range(0, $batch_size);
$column_map = [];
foreach ($storage_definition
->getColumns() as $column_name => $data) {
$column_map[$table_mapping
->getFieldColumnName($storage_definition, $column_name)] = $column_name;
}
$entities = [];
$items_by_entity = [];
foreach ($entity_query
->execute() as $row) {
$item_query = $this->database
->select($table_name, 't', [
'fetch' => \PDO::FETCH_ASSOC,
])
->fields('t')
->condition('entity_id', $row['entity_id'])
->condition('deleted', 1)
->orderBy('delta');
foreach ($item_query
->execute() as $item_row) {
if (!isset($entities[$item_row['revision_id']])) {
$item_row['entity_type'] = $this->entityTypeId;
$entities[$item_row['revision_id']] = _field_create_entity_from_ids((object) $item_row);
}
$item = [];
foreach ($column_map as $db_column => $field_column) {
$item[$field_column] = $item_row[$db_column];
}
$items_by_entity[$item_row['revision_id']][] = $item;
}
}
foreach ($items_by_entity as $revision_id => $values) {
$entity_adapter = $entities[$revision_id]
->getTypedData();
$items_by_entity[$revision_id] = \Drupal::typedDataManager()
->create($field_definition, $values, $field_definition
->getName(), $entity_adapter);
}
return $items_by_entity;
}
protected function purgeFieldItems(ContentEntityInterface $entity, FieldDefinitionInterface $field_definition) {
$storage_definition = $field_definition
->getFieldStorageDefinition();
$is_deleted = $storage_definition
->isDeleted();
$table_mapping = $this
->getTableMapping();
$table_name = $table_mapping
->getDedicatedDataTableName($storage_definition, $is_deleted);
$revision_name = $table_mapping
->getDedicatedRevisionTableName($storage_definition, $is_deleted);
$revision_id = $this->entityType
->isRevisionable() ? $entity
->getRevisionId() : $entity
->id();
$this->database
->delete($table_name)
->condition('revision_id', $revision_id)
->condition('deleted', 1)
->execute();
if ($this->entityType
->isRevisionable()) {
$this->database
->delete($revision_name)
->condition('revision_id', $revision_id)
->condition('deleted', 1)
->execute();
}
}
public function finalizePurge(FieldStorageDefinitionInterface $storage_definition) {
$this
->getStorageSchema()
->finalizePurge($storage_definition);
}
public function countFieldData($storage_definition, $as_bool = FALSE) {
$storage_definitions = $this->fieldStorageDefinitions;
$storage_definitions[$storage_definition
->getName()] = $storage_definition;
$table_mapping = $this
->getTableMapping($storage_definitions);
if ($table_mapping
->requiresDedicatedTableStorage($storage_definition)) {
$is_deleted = $storage_definition
->isDeleted();
if ($this->entityType
->isRevisionable()) {
$table_name = $table_mapping
->getDedicatedRevisionTableName($storage_definition, $is_deleted);
}
else {
$table_name = $table_mapping
->getDedicatedDataTableName($storage_definition, $is_deleted);
}
$query = $this->database
->select($table_name, 't');
$or = $query
->orConditionGroup();
foreach ($storage_definition
->getColumns() as $column_name => $data) {
$or
->isNotNull($table_mapping
->getFieldColumnName($storage_definition, $column_name));
}
$query
->condition($or);
if (!$as_bool) {
$query
->fields('t', [
'entity_id',
])
->distinct(TRUE);
}
}
elseif ($table_mapping
->allowsSharedTableStorage($storage_definition)) {
$field_name = $storage_definition
->getName();
$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) {
$or
->isNotNull($table_mapping
->getFieldColumnName($storage_definition, $property_name));
}
$query
->condition($or);
if (!$as_bool) {
$query
->fields('t', [
$this->idKey,
])
->distinct(TRUE);
}
}
$count = 0;
if (isset($query)) {
if ($as_bool) {
$query
->range(0, 1)
->addExpression('1');
}
else {
$query = $query
->countQuery();
}
$count = $query
->execute()
->fetchField();
}
return $as_bool ? (bool) $count : (int) $count;
}
}