Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
76.47% |
13 / 17 |
CRAP | |
80.00% |
60 / 75 |
DefaultTableMapping | |
0.00% |
0 / 1 |
|
77.78% |
14 / 18 |
59.49 | |
80.00% |
60 / 75 |
__construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
getTableNames | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getAllColumns | |
0.00% |
0 / 1 |
5.05 | |
87.50% |
7 / 8 |
|||
getFieldNames | |
100.00% |
1 / 1 |
2 | |
100.00% |
3 / 3 |
|||
getFieldTableName | |
100.00% |
1 / 1 |
6 | |
100.00% |
18 / 18 |
|||
getColumnNames | |
100.00% |
1 / 1 |
4 | |
100.00% |
6 / 6 |
|||
getFieldColumnName | |
100.00% |
1 / 1 |
5 | |
100.00% |
7 / 7 |
|||
setFieldNames | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
getExtraColumns | |
100.00% |
1 / 1 |
2 | |
100.00% |
3 / 3 |
|||
setExtraColumns | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
allowsSharedTableStorage | |
100.00% |
1 / 1 |
3 | |
100.00% |
1 / 1 |
|||
requiresDedicatedTableStorage | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
getDedicatedTableNames | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
anonymous function | |
100.00% |
1 / 1 |
1 | |
100.00% |
0 / 0 |
|||
getReservedColumns | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getDedicatedDataTableName | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 3 |
|||
getDedicatedRevisionTableName | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 3 |
|||
generateFieldTableName | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 8 |
<?php | |
/** | |
* @file | |
* Contains \Drupal\Core\Entity\Sql\DefaultTableMapping. | |
*/ | |
namespace Drupal\Core\Entity\Sql; | |
use Drupal\Core\Entity\ContentEntityTypeInterface; | |
use Drupal\Core\Field\FieldStorageDefinitionInterface; | |
/** | |
* Defines a default table mapping class. | |
*/ | |
class DefaultTableMapping implements TableMappingInterface { | |
/** | |
* The entity type definition. | |
* | |
* @var \Drupal\Core\Entity\ContentEntityTypeInterface | |
*/ | |
protected $entityType; | |
/** | |
* The field storage definitions of this mapping. | |
* | |
* @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] | |
*/ | |
protected $fieldStorageDefinitions = array(); | |
/** | |
* A list of field names per table. | |
* | |
* This corresponds to the return value of | |
* TableMappingInterface::getFieldNames() except that this variable is | |
* additionally keyed by table name. | |
* | |
* @var array[] | |
*/ | |
protected $fieldNames = array(); | |
/** | |
* A list of database columns which store denormalized data per table. | |
* | |
* This corresponds to the return value of | |
* TableMappingInterface::getExtraColumns() except that this variable is | |
* additionally keyed by table name. | |
* | |
* @var array[] | |
*/ | |
protected $extraColumns = array(); | |
/** | |
* A mapping of column names per field name. | |
* | |
* This corresponds to the return value of | |
* TableMappingInterface::getColumnNames() except that this variable is | |
* additionally keyed by field name. | |
* | |
* This data is derived from static::$storageDefinitions, but is stored | |
* separately to avoid repeated processing. | |
* | |
* @var array[] | |
*/ | |
protected $columnMapping = array(); | |
/** | |
* A list of all database columns per table. | |
* | |
* This corresponds to the return value of | |
* TableMappingInterface::getAllColumns() except that this variable is | |
* additionally keyed by table name. | |
* | |
* This data is derived from static::$storageDefinitions, static::$fieldNames, | |
* and static::$extraColumns, but is stored separately to avoid repeated | |
* processing. | |
* | |
* @var array[] | |
*/ | |
protected $allColumns = array(); | |
/** | |
* Constructs a DefaultTableMapping. | |
* | |
* @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type | |
* The entity type definition. | |
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $storage_definitions | |
* A list of field storage definitions that should be available for the | |
* field columns of this table mapping. | |
*/ | |
public function __construct(ContentEntityTypeInterface $entity_type, array $storage_definitions) { | |
$this->entityType = $entity_type; | |
$this->fieldStorageDefinitions = $storage_definitions; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getTableNames() { | |
return array_unique(array_merge(array_keys($this->fieldNames), array_keys($this->extraColumns))); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getAllColumns($table_name) { | |
if (!isset($this->allColumns[$table_name])) { | |
$this->allColumns[$table_name] = array(); | |
foreach ($this->getFieldNames($table_name) as $field_name) { | |
$this->allColumns[$table_name] = array_merge($this->allColumns[$table_name], array_values($this->getColumnNames($field_name))); | |
} | |
// There is just one field for each dedicated storage table, thus | |
// $field_name can only refer to it. | |
if (isset($field_name) && $this->requiresDedicatedTableStorage($this->fieldStorageDefinitions[$field_name])) { | |
// Unlike in shared storage tables, in dedicated ones field columns are | |
// positioned last. | |
$this->allColumns[$table_name] = array_merge($this->getExtraColumns($table_name), $this->allColumns[$table_name]); | |
} | |
else { | |
$this->allColumns[$table_name] = array_merge($this->allColumns[$table_name], $this->getExtraColumns($table_name)); | |
} | |
} | |
return $this->allColumns[$table_name]; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getFieldNames($table_name) { | |
if (isset($this->fieldNames[$table_name])) { | |
return $this->fieldNames[$table_name]; | |
} | |
return array(); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getFieldTableName($field_name) { | |
$result = NULL; | |
if (isset($this->fieldStorageDefinitions[$field_name])) { | |
// Since a field may be stored in more than one table, we inspect tables | |
// in order of relevance: the data table if present is the main place | |
// where field data is stored, otherwise the base table is responsible for | |
// storing field data. Revision metadata is an exception as it's stored | |
// only in the revision table. | |
// @todo The table mapping itself should know about entity tables. See | |
// https://www.drupal.org/node/2274017. | |
/** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */ | |
$storage = \Drupal::entityManager()->getStorage($this->entityType->id()); | |
$table_names = array( | |
$storage->getDataTable(), | |
$storage->getBaseTable(), | |
$storage->getRevisionTable(), | |
); | |
// Collect field columns. | |
$field_columns = array(); | |
$storage_definition = $this->fieldStorageDefinitions[$field_name]; | |
foreach (array_keys($storage_definition->getColumns()) as $property_name) { | |
$field_columns[] = $this->getFieldColumnName($storage_definition, $property_name); | |
} | |
foreach (array_filter($table_names) as $table_name) { | |
$columns = $this->getAllColumns($table_name); | |
// We assume finding one field column belonging to the mapping is enough | |
// to identify the field table. | |
if (array_intersect($columns, $field_columns)) { | |
$result = $table_name; | |
break; | |
} | |
} | |
} | |
if (!isset($result)) { | |
throw new SqlContentEntityStorageException("Table information not available for the '$field_name' field."); | |
} | |
return $result; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getColumnNames($field_name) { | |
if (!isset($this->columnMapping[$field_name])) { | |
$this->columnMapping[$field_name] = array(); | |
if (isset($this->fieldStorageDefinitions[$field_name])) { | |
foreach (array_keys($this->fieldStorageDefinitions[$field_name]->getColumns()) as $property_name) { | |
$this->columnMapping[$field_name][$property_name] = $this->getFieldColumnName($this->fieldStorageDefinitions[$field_name], $property_name); | |
} | |
} | |
} | |
return $this->columnMapping[$field_name]; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getFieldColumnName(FieldStorageDefinitionInterface $storage_definition, $property_name) { | |
$field_name = $storage_definition->getName(); | |
if ($this->allowsSharedTableStorage($storage_definition)) { | |
$column_name = count($storage_definition->getColumns()) == 1 ? $field_name : $field_name . '__' . $property_name; | |
} | |
elseif ($this->requiresDedicatedTableStorage($storage_definition)) { | |
$column_name = !in_array($property_name, $this->getReservedColumns()) ? $field_name . '_' . $property_name : $property_name; | |
} | |
else { | |
throw new SqlContentEntityStorageException("Column information not available for the '$field_name' field."); | |
} | |
return $column_name; | |
} | |
/** | |
* Adds field columns for a table to the table mapping. | |
* | |
* @param string $table_name | |
* The name of the table to add the field column for. | |
* @param string[] $field_names | |
* A list of field names to add the columns for. | |
* | |
* @return $this | |
*/ | |
public function setFieldNames($table_name, array $field_names) { | |
$this->fieldNames[$table_name] = $field_names; | |
// Force the re-computation of the column list. | |
unset($this->allColumns[$table_name]); | |
return $this; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getExtraColumns($table_name) { | |
if (isset($this->extraColumns[$table_name])) { | |
return $this->extraColumns[$table_name]; | |
} | |
return array(); | |
} | |
/** | |
* Adds a extra columns for a table to the table mapping. | |
* | |
* @param string $table_name | |
* The name of table to add the extra columns for. | |
* @param string[] $column_names | |
* The list of column names. | |
* | |
* @return $this | |
*/ | |
public function setExtraColumns($table_name, array $column_names) { | |
$this->extraColumns[$table_name] = $column_names; | |
// Force the re-computation of the column list. | |
unset($this->allColumns[$table_name]); | |
return $this; | |
} | |
/** | |
* Checks whether the given field can be stored in a shared table. | |
* | |
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition | |
* The field storage definition. | |
* | |
* @return bool | |
* TRUE if the field can be stored in a dedicated table, FALSE otherwise. | |
*/ | |
public function allowsSharedTableStorage(FieldStorageDefinitionInterface $storage_definition) { | |
return !$storage_definition->hasCustomStorage() && $storage_definition->isBaseField() && !$storage_definition->isMultiple(); | |
} | |
/** | |
* Checks whether the given field has to be stored in a dedicated table. | |
* | |
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition | |
* The field storage definition. | |
* | |
* @return bool | |
* TRUE if the field can be stored in a dedicated table, FALSE otherwise. | |
*/ | |
public function requiresDedicatedTableStorage(FieldStorageDefinitionInterface $storage_definition) { | |
return !$storage_definition->hasCustomStorage() && !$this->allowsSharedTableStorage($storage_definition); | |
} | |
/** | |
* Gets a list of dedicated table names for this mapping. | |
* | |
* @return string[] | |
* An array of table names. | |
*/ | |
public function getDedicatedTableNames() { | |
$table_mapping = $this; | |
$definitions = array_filter($this->fieldStorageDefinitions, function($definition) use ($table_mapping) { return $table_mapping->requiresDedicatedTableStorage($definition); }); | |
$data_tables = array_map(function($definition) use ($table_mapping) { return $table_mapping->getDedicatedDataTableName($definition); }, $definitions); | |
$revision_tables = array_map(function($definition) use ($table_mapping) { return $table_mapping->getDedicatedRevisionTableName($definition); }, $definitions); | |
$dedicated_tables = array_merge(array_values($data_tables), array_values($revision_tables)); | |
return $dedicated_tables; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getReservedColumns() { | |
return array('deleted'); | |
} | |
/** | |
* Generates a table name for a field data table. | |
* | |
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition | |
* The field storage definition. | |
* @param bool $is_deleted | |
* (optional) Whether the table name holding the values of a deleted field | |
* should be returned. | |
* | |
* @return string | |
* A string containing the generated name for the database table. | |
*/ | |
public function getDedicatedDataTableName(FieldStorageDefinitionInterface $storage_definition, $is_deleted = FALSE) { | |
if ($is_deleted) { | |
// When a field is a deleted, the table is renamed to | |
// {field_deleted_data_FIELD_UUID}. To make sure we don't end up with | |
// table names longer than 64 characters, we hash the unique storage | |
// identifier and return the first 10 characters so we end up with a short | |
// unique ID. | |
return "field_deleted_data_" . substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10); | |
} | |
else { | |
return $this->generateFieldTableName($storage_definition, FALSE); | |
} | |
} | |
/** | |
* Generates a table name for a field revision archive table. | |
* | |
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition | |
* The field storage definition. | |
* @param bool $is_deleted | |
* (optional) Whether the table name holding the values of a deleted field | |
* should be returned. | |
* | |
* @return string | |
* A string containing the generated name for the database table. | |
*/ | |
public function getDedicatedRevisionTableName(FieldStorageDefinitionInterface $storage_definition, $is_deleted = FALSE) { | |
if ($is_deleted) { | |
// When a field is a deleted, the table is renamed to | |
// {field_deleted_revision_FIELD_UUID}. To make sure we don't end up with | |
// table names longer than 64 characters, we hash the unique storage | |
// identifier and return the first 10 characters so we end up with a short | |
// unique ID. | |
return "field_deleted_revision_" . substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10); | |
} | |
else { | |
return $this->generateFieldTableName($storage_definition, TRUE); | |
} | |
} | |
/** | |
* Generates a safe and unambiguous field table name. | |
* | |
* The method accounts for a maximum table name length of 64 characters, and | |
* takes care of disambiguation. | |
* | |
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition | |
* The field storage definition. | |
* @param bool $revision | |
* TRUE for revision table, FALSE otherwise. | |
* | |
* @return string | |
* The final table name. | |
*/ | |
protected function generateFieldTableName(FieldStorageDefinitionInterface $storage_definition, $revision) { | |
$separator = $revision ? '_revision__' : '__'; | |
$table_name = $storage_definition->getTargetEntityTypeId() . $separator . $storage_definition->getName(); | |
// Limit the string to 48 characters, keeping a 16 characters margin for db | |
// prefixes. | |
if (strlen($table_name) > 48) { | |
// Use a shorter separator, a truncated entity_type, and a hash of the | |
// field UUID. | |
$separator = $revision ? '_r__' : '__'; | |
// Truncate to the same length for the current and revision tables. | |
$entity_type = substr($storage_definition->getTargetEntityTypeId(), 0, 34); | |
$field_hash = substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10); | |
$table_name = $entity_type . $separator . $field_hash; | |
} | |
return $table_name; | |
} | |
} |