PageRenderTime 40ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php

https://gitlab.com/guillaumev/alkarama
PHP | 396 lines | 157 code | 36 blank | 203 comment | 28 complexity | ae93452bcf8a68c5254fdff8134ba3b0 MD5 | raw file
  1. <?php
  2. namespace Drupal\Core\Entity\Sql;
  3. use Drupal\Core\Entity\ContentEntityTypeInterface;
  4. use Drupal\Core\Field\FieldStorageDefinitionInterface;
  5. /**
  6. * Defines a default table mapping class.
  7. */
  8. class DefaultTableMapping implements TableMappingInterface {
  9. /**
  10. * The entity type definition.
  11. *
  12. * @var \Drupal\Core\Entity\ContentEntityTypeInterface
  13. */
  14. protected $entityType;
  15. /**
  16. * The field storage definitions of this mapping.
  17. *
  18. * @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]
  19. */
  20. protected $fieldStorageDefinitions = array();
  21. /**
  22. * A list of field names per table.
  23. *
  24. * This corresponds to the return value of
  25. * TableMappingInterface::getFieldNames() except that this variable is
  26. * additionally keyed by table name.
  27. *
  28. * @var array[]
  29. */
  30. protected $fieldNames = array();
  31. /**
  32. * A list of database columns which store denormalized data per table.
  33. *
  34. * This corresponds to the return value of
  35. * TableMappingInterface::getExtraColumns() except that this variable is
  36. * additionally keyed by table name.
  37. *
  38. * @var array[]
  39. */
  40. protected $extraColumns = array();
  41. /**
  42. * A mapping of column names per field name.
  43. *
  44. * This corresponds to the return value of
  45. * TableMappingInterface::getColumnNames() except that this variable is
  46. * additionally keyed by field name.
  47. *
  48. * This data is derived from static::$storageDefinitions, but is stored
  49. * separately to avoid repeated processing.
  50. *
  51. * @var array[]
  52. */
  53. protected $columnMapping = array();
  54. /**
  55. * A list of all database columns per table.
  56. *
  57. * This corresponds to the return value of
  58. * TableMappingInterface::getAllColumns() except that this variable is
  59. * additionally keyed by table name.
  60. *
  61. * This data is derived from static::$storageDefinitions, static::$fieldNames,
  62. * and static::$extraColumns, but is stored separately to avoid repeated
  63. * processing.
  64. *
  65. * @var array[]
  66. */
  67. protected $allColumns = array();
  68. /**
  69. * Constructs a DefaultTableMapping.
  70. *
  71. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
  72. * The entity type definition.
  73. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $storage_definitions
  74. * A list of field storage definitions that should be available for the
  75. * field columns of this table mapping.
  76. */
  77. public function __construct(ContentEntityTypeInterface $entity_type, array $storage_definitions) {
  78. $this->entityType = $entity_type;
  79. $this->fieldStorageDefinitions = $storage_definitions;
  80. }
  81. /**
  82. * {@inheritdoc}
  83. */
  84. public function getTableNames() {
  85. return array_unique(array_merge(array_keys($this->fieldNames), array_keys($this->extraColumns)));
  86. }
  87. /**
  88. * {@inheritdoc}
  89. */
  90. public function getAllColumns($table_name) {
  91. if (!isset($this->allColumns[$table_name])) {
  92. $this->allColumns[$table_name] = array();
  93. foreach ($this->getFieldNames($table_name) as $field_name) {
  94. $this->allColumns[$table_name] = array_merge($this->allColumns[$table_name], array_values($this->getColumnNames($field_name)));
  95. }
  96. // There is just one field for each dedicated storage table, thus
  97. // $field_name can only refer to it.
  98. if (isset($field_name) && $this->requiresDedicatedTableStorage($this->fieldStorageDefinitions[$field_name])) {
  99. // Unlike in shared storage tables, in dedicated ones field columns are
  100. // positioned last.
  101. $this->allColumns[$table_name] = array_merge($this->getExtraColumns($table_name), $this->allColumns[$table_name]);
  102. }
  103. else {
  104. $this->allColumns[$table_name] = array_merge($this->allColumns[$table_name], $this->getExtraColumns($table_name));
  105. }
  106. }
  107. return $this->allColumns[$table_name];
  108. }
  109. /**
  110. * {@inheritdoc}
  111. */
  112. public function getFieldNames($table_name) {
  113. if (isset($this->fieldNames[$table_name])) {
  114. return $this->fieldNames[$table_name];
  115. }
  116. return array();
  117. }
  118. /**
  119. * {@inheritdoc}
  120. */
  121. public function getFieldTableName($field_name) {
  122. $result = NULL;
  123. if (isset($this->fieldStorageDefinitions[$field_name])) {
  124. // Since a field may be stored in more than one table, we inspect tables
  125. // in order of relevance: the data table if present is the main place
  126. // where field data is stored, otherwise the base table is responsible for
  127. // storing field data. Revision metadata is an exception as it's stored
  128. // only in the revision table.
  129. // @todo The table mapping itself should know about entity tables. See
  130. // https://www.drupal.org/node/2274017.
  131. /** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */
  132. $storage = \Drupal::entityManager()->getStorage($this->entityType->id());
  133. $storage_definition = $this->fieldStorageDefinitions[$field_name];
  134. $table_names = array(
  135. $storage->getDataTable(),
  136. $storage->getBaseTable(),
  137. $storage->getRevisionTable(),
  138. $this->getDedicatedDataTableName($storage_definition),
  139. );
  140. // Collect field columns.
  141. $field_columns = array();
  142. foreach (array_keys($storage_definition->getColumns()) as $property_name) {
  143. $field_columns[] = $this->getFieldColumnName($storage_definition, $property_name);
  144. }
  145. foreach (array_filter($table_names) as $table_name) {
  146. $columns = $this->getAllColumns($table_name);
  147. // We assume finding one field column belonging to the mapping is enough
  148. // to identify the field table.
  149. if (array_intersect($columns, $field_columns)) {
  150. $result = $table_name;
  151. break;
  152. }
  153. }
  154. }
  155. if (!isset($result)) {
  156. throw new SqlContentEntityStorageException("Table information not available for the '$field_name' field.");
  157. }
  158. return $result;
  159. }
  160. /**
  161. * {@inheritdoc}
  162. */
  163. public function getColumnNames($field_name) {
  164. if (!isset($this->columnMapping[$field_name])) {
  165. $this->columnMapping[$field_name] = array();
  166. if (isset($this->fieldStorageDefinitions[$field_name]) && !$this->fieldStorageDefinitions[$field_name]->hasCustomStorage()) {
  167. foreach (array_keys($this->fieldStorageDefinitions[$field_name]->getColumns()) as $property_name) {
  168. $this->columnMapping[$field_name][$property_name] = $this->getFieldColumnName($this->fieldStorageDefinitions[$field_name], $property_name);
  169. }
  170. }
  171. }
  172. return $this->columnMapping[$field_name];
  173. }
  174. /**
  175. * {@inheritdoc}
  176. */
  177. public function getFieldColumnName(FieldStorageDefinitionInterface $storage_definition, $property_name) {
  178. $field_name = $storage_definition->getName();
  179. if ($this->allowsSharedTableStorage($storage_definition)) {
  180. $column_name = count($storage_definition->getColumns()) == 1 ? $field_name : $field_name . '__' . $property_name;
  181. }
  182. elseif ($this->requiresDedicatedTableStorage($storage_definition)) {
  183. if ($property_name == TableMappingInterface::DELTA) {
  184. $column_name = 'delta';
  185. }
  186. else {
  187. $column_name = !in_array($property_name, $this->getReservedColumns()) ? $field_name . '_' . $property_name : $property_name;
  188. }
  189. }
  190. else {
  191. throw new SqlContentEntityStorageException("Column information not available for the '$field_name' field.");
  192. }
  193. return $column_name;
  194. }
  195. /**
  196. * Adds field columns for a table to the table mapping.
  197. *
  198. * @param string $table_name
  199. * The name of the table to add the field column for.
  200. * @param string[] $field_names
  201. * A list of field names to add the columns for.
  202. *
  203. * @return $this
  204. */
  205. public function setFieldNames($table_name, array $field_names) {
  206. $this->fieldNames[$table_name] = $field_names;
  207. // Force the re-computation of the column list.
  208. unset($this->allColumns[$table_name]);
  209. return $this;
  210. }
  211. /**
  212. * {@inheritdoc}
  213. */
  214. public function getExtraColumns($table_name) {
  215. if (isset($this->extraColumns[$table_name])) {
  216. return $this->extraColumns[$table_name];
  217. }
  218. return array();
  219. }
  220. /**
  221. * Adds a extra columns for a table to the table mapping.
  222. *
  223. * @param string $table_name
  224. * The name of table to add the extra columns for.
  225. * @param string[] $column_names
  226. * The list of column names.
  227. *
  228. * @return $this
  229. */
  230. public function setExtraColumns($table_name, array $column_names) {
  231. $this->extraColumns[$table_name] = $column_names;
  232. // Force the re-computation of the column list.
  233. unset($this->allColumns[$table_name]);
  234. return $this;
  235. }
  236. /**
  237. * Checks whether the given field can be stored in a shared table.
  238. *
  239. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  240. * The field storage definition.
  241. *
  242. * @return bool
  243. * TRUE if the field can be stored in a dedicated table, FALSE otherwise.
  244. */
  245. public function allowsSharedTableStorage(FieldStorageDefinitionInterface $storage_definition) {
  246. return !$storage_definition->hasCustomStorage() && $storage_definition->isBaseField() && !$storage_definition->isMultiple();
  247. }
  248. /**
  249. * Checks whether the given field has to be stored in a dedicated table.
  250. *
  251. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  252. * The field storage definition.
  253. *
  254. * @return bool
  255. * TRUE if the field can be stored in a dedicated table, FALSE otherwise.
  256. */
  257. public function requiresDedicatedTableStorage(FieldStorageDefinitionInterface $storage_definition) {
  258. return !$storage_definition->hasCustomStorage() && !$this->allowsSharedTableStorage($storage_definition);
  259. }
  260. /**
  261. * Gets a list of dedicated table names for this mapping.
  262. *
  263. * @return string[]
  264. * An array of table names.
  265. */
  266. public function getDedicatedTableNames() {
  267. $table_mapping = $this;
  268. $definitions = array_filter($this->fieldStorageDefinitions, function($definition) use ($table_mapping) { return $table_mapping->requiresDedicatedTableStorage($definition); });
  269. $data_tables = array_map(function($definition) use ($table_mapping) { return $table_mapping->getDedicatedDataTableName($definition); }, $definitions);
  270. $revision_tables = array_map(function($definition) use ($table_mapping) { return $table_mapping->getDedicatedRevisionTableName($definition); }, $definitions);
  271. $dedicated_tables = array_merge(array_values($data_tables), array_values($revision_tables));
  272. return $dedicated_tables;
  273. }
  274. /**
  275. * {@inheritdoc}
  276. */
  277. public function getReservedColumns() {
  278. return array('deleted');
  279. }
  280. /**
  281. * Generates a table name for a field data table.
  282. *
  283. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  284. * The field storage definition.
  285. * @param bool $is_deleted
  286. * (optional) Whether the table name holding the values of a deleted field
  287. * should be returned.
  288. *
  289. * @return string
  290. * A string containing the generated name for the database table.
  291. */
  292. public function getDedicatedDataTableName(FieldStorageDefinitionInterface $storage_definition, $is_deleted = FALSE) {
  293. if ($is_deleted) {
  294. // When a field is a deleted, the table is renamed to
  295. // {field_deleted_data_FIELD_UUID}. To make sure we don't end up with
  296. // table names longer than 64 characters, we hash the unique storage
  297. // identifier and return the first 10 characters so we end up with a short
  298. // unique ID.
  299. return "field_deleted_data_" . substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
  300. }
  301. else {
  302. return $this->generateFieldTableName($storage_definition, FALSE);
  303. }
  304. }
  305. /**
  306. * Generates a table name for a field revision archive table.
  307. *
  308. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  309. * The field storage definition.
  310. * @param bool $is_deleted
  311. * (optional) Whether the table name holding the values of a deleted field
  312. * should be returned.
  313. *
  314. * @return string
  315. * A string containing the generated name for the database table.
  316. */
  317. public function getDedicatedRevisionTableName(FieldStorageDefinitionInterface $storage_definition, $is_deleted = FALSE) {
  318. if ($is_deleted) {
  319. // When a field is a deleted, the table is renamed to
  320. // {field_deleted_revision_FIELD_UUID}. To make sure we don't end up with
  321. // table names longer than 64 characters, we hash the unique storage
  322. // identifier and return the first 10 characters so we end up with a short
  323. // unique ID.
  324. return "field_deleted_revision_" . substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
  325. }
  326. else {
  327. return $this->generateFieldTableName($storage_definition, TRUE);
  328. }
  329. }
  330. /**
  331. * Generates a safe and unambiguous field table name.
  332. *
  333. * The method accounts for a maximum table name length of 64 characters, and
  334. * takes care of disambiguation.
  335. *
  336. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  337. * The field storage definition.
  338. * @param bool $revision
  339. * TRUE for revision table, FALSE otherwise.
  340. *
  341. * @return string
  342. * The final table name.
  343. */
  344. protected function generateFieldTableName(FieldStorageDefinitionInterface $storage_definition, $revision) {
  345. $separator = $revision ? '_revision__' : '__';
  346. $table_name = $storage_definition->getTargetEntityTypeId() . $separator . $storage_definition->getName();
  347. // Limit the string to 48 characters, keeping a 16 characters margin for db
  348. // prefixes.
  349. if (strlen($table_name) > 48) {
  350. // Use a shorter separator, a truncated entity_type, and a hash of the
  351. // field UUID.
  352. $separator = $revision ? '_r__' : '__';
  353. // Truncate to the same length for the current and revision tables.
  354. $entity_type = substr($storage_definition->getTargetEntityTypeId(), 0, 34);
  355. $field_hash = substr(hash('sha256', $storage_definition->getUniqueStorageIdentifier()), 0, 10);
  356. $table_name = $entity_type . $separator . $field_hash;
  357. }
  358. return $table_name;
  359. }
  360. }