EntityDefinitionUpdateManager.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. <?php
  2. namespace Drupal\Core\Entity;
  3. use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
  4. use Drupal\Core\Entity\Schema\EntityStorageSchemaInterface;
  5. use Drupal\Core\Field\BaseFieldDefinition;
  6. use Drupal\Core\Field\FieldStorageDefinitionInterface;
  7. use Drupal\Core\StringTranslation\StringTranslationTrait;
  8. /**
  9. * Manages entity definition updates.
  10. */
  11. class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInterface {
  12. use StringTranslationTrait;
  13. /**
  14. * The entity manager service.
  15. *
  16. * @var \Drupal\Core\Entity\EntityManagerInterface
  17. */
  18. protected $entityManager;
  19. /**
  20. * Constructs a new EntityDefinitionUpdateManager.
  21. *
  22. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
  23. * The entity manager.
  24. */
  25. public function __construct(EntityManagerInterface $entity_manager) {
  26. $this->entityManager = $entity_manager;
  27. }
  28. /**
  29. * {@inheritdoc}
  30. */
  31. public function needsUpdates() {
  32. return (bool) $this->getChangeList();
  33. }
  34. /**
  35. * {@inheritdoc}
  36. */
  37. public function getChangeSummary() {
  38. $summary = [];
  39. foreach ($this->getChangeList() as $entity_type_id => $change_list) {
  40. // Process entity type definition changes.
  41. if (!empty($change_list['entity_type'])) {
  42. $entity_type = $this->entityManager->getDefinition($entity_type_id);
  43. switch ($change_list['entity_type']) {
  44. case static::DEFINITION_CREATED:
  45. $summary[$entity_type_id][] = $this->t('The %entity_type entity type needs to be installed.', ['%entity_type' => $entity_type->getLabel()]);
  46. break;
  47. case static::DEFINITION_UPDATED:
  48. $summary[$entity_type_id][] = $this->t('The %entity_type entity type needs to be updated.', ['%entity_type' => $entity_type->getLabel()]);
  49. break;
  50. }
  51. }
  52. // Process field storage definition changes.
  53. if (!empty($change_list['field_storage_definitions'])) {
  54. $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
  55. $original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
  56. foreach ($change_list['field_storage_definitions'] as $field_name => $change) {
  57. switch ($change) {
  58. case static::DEFINITION_CREATED:
  59. $summary[$entity_type_id][] = $this->t('The %field_name field needs to be installed.', ['%field_name' => $storage_definitions[$field_name]->getLabel()]);
  60. break;
  61. case static::DEFINITION_UPDATED:
  62. $summary[$entity_type_id][] = $this->t('The %field_name field needs to be updated.', ['%field_name' => $storage_definitions[$field_name]->getLabel()]);
  63. break;
  64. case static::DEFINITION_DELETED:
  65. $summary[$entity_type_id][] = $this->t('The %field_name field needs to be uninstalled.', ['%field_name' => $original_storage_definitions[$field_name]->getLabel()]);
  66. break;
  67. }
  68. }
  69. }
  70. }
  71. return $summary;
  72. }
  73. /**
  74. * {@inheritdoc}
  75. */
  76. public function applyUpdates() {
  77. $complete_change_list = $this->getChangeList();
  78. if ($complete_change_list) {
  79. // self::getChangeList() only disables the cache and does not invalidate.
  80. // In case there are changes, explicitly invalidate caches.
  81. $this->entityManager->clearCachedDefinitions();
  82. }
  83. foreach ($complete_change_list as $entity_type_id => $change_list) {
  84. // Process entity type definition changes before storage definitions ones
  85. // this is necessary when you change an entity type from non-revisionable
  86. // to revisionable and at the same time add revisionable fields to the
  87. // entity type.
  88. if (!empty($change_list['entity_type'])) {
  89. $this->doEntityUpdate($change_list['entity_type'], $entity_type_id);
  90. }
  91. // Process field storage definition changes.
  92. if (!empty($change_list['field_storage_definitions'])) {
  93. $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
  94. $original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
  95. foreach ($change_list['field_storage_definitions'] as $field_name => $change) {
  96. $storage_definition = isset($storage_definitions[$field_name]) ? $storage_definitions[$field_name] : NULL;
  97. $original_storage_definition = isset($original_storage_definitions[$field_name]) ? $original_storage_definitions[$field_name] : NULL;
  98. $this->doFieldUpdate($change, $storage_definition, $original_storage_definition);
  99. }
  100. }
  101. }
  102. }
  103. /**
  104. * {@inheritdoc}
  105. */
  106. public function getEntityType($entity_type_id) {
  107. $entity_type = $this->entityManager->getLastInstalledDefinition($entity_type_id);
  108. return $entity_type ? clone $entity_type : NULL;
  109. }
  110. /**
  111. * {@inheritdoc}
  112. */
  113. public function installEntityType(EntityTypeInterface $entity_type) {
  114. $this->entityManager->clearCachedDefinitions();
  115. $this->entityManager->onEntityTypeCreate($entity_type);
  116. }
  117. /**
  118. * {@inheritdoc}
  119. */
  120. public function updateEntityType(EntityTypeInterface $entity_type) {
  121. $original = $this->getEntityType($entity_type->id());
  122. $this->entityManager->clearCachedDefinitions();
  123. $this->entityManager->onEntityTypeUpdate($entity_type, $original);
  124. }
  125. /**
  126. * {@inheritdoc}
  127. */
  128. public function uninstallEntityType(EntityTypeInterface $entity_type) {
  129. $this->entityManager->clearCachedDefinitions();
  130. $this->entityManager->onEntityTypeDelete($entity_type);
  131. }
  132. /**
  133. * {@inheritdoc}
  134. */
  135. public function installFieldStorageDefinition($name, $entity_type_id, $provider, FieldStorageDefinitionInterface $storage_definition) {
  136. // @todo Pass a mutable field definition interface when we have one. See
  137. // https://www.drupal.org/node/2346329.
  138. if ($storage_definition instanceof BaseFieldDefinition) {
  139. $storage_definition
  140. ->setName($name)
  141. ->setTargetEntityTypeId($entity_type_id)
  142. ->setProvider($provider)
  143. ->setTargetBundle(NULL);
  144. }
  145. $this->entityManager->clearCachedDefinitions();
  146. $this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
  147. }
  148. /**
  149. * {@inheritdoc}
  150. */
  151. public function getFieldStorageDefinition($name, $entity_type_id) {
  152. $storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
  153. return isset($storage_definitions[$name]) ? clone $storage_definitions[$name] : NULL;
  154. }
  155. /**
  156. * {@inheritdoc}
  157. */
  158. public function updateFieldStorageDefinition(FieldStorageDefinitionInterface $storage_definition) {
  159. $original = $this->getFieldStorageDefinition($storage_definition->getName(), $storage_definition->getTargetEntityTypeId());
  160. $this->entityManager->clearCachedDefinitions();
  161. $this->entityManager->onFieldStorageDefinitionUpdate($storage_definition, $original);
  162. }
  163. /**
  164. * {@inheritdoc}
  165. */
  166. public function uninstallFieldStorageDefinition(FieldStorageDefinitionInterface $storage_definition) {
  167. $this->entityManager->clearCachedDefinitions();
  168. $this->entityManager->onFieldStorageDefinitionDelete($storage_definition);
  169. }
  170. /**
  171. * Performs an entity type definition update.
  172. *
  173. * @param string $op
  174. * The operation to perform, either static::DEFINITION_CREATED or
  175. * static::DEFINITION_UPDATED.
  176. * @param string $entity_type_id
  177. * The entity type ID.
  178. */
  179. protected function doEntityUpdate($op, $entity_type_id) {
  180. $entity_type = $this->entityManager->getDefinition($entity_type_id);
  181. switch ($op) {
  182. case static::DEFINITION_CREATED:
  183. $this->entityManager->onEntityTypeCreate($entity_type);
  184. break;
  185. case static::DEFINITION_UPDATED:
  186. $original = $this->entityManager->getLastInstalledDefinition($entity_type_id);
  187. $this->entityManager->onEntityTypeUpdate($entity_type, $original);
  188. break;
  189. }
  190. }
  191. /**
  192. * Performs a field storage definition update.
  193. *
  194. * @param string $op
  195. * The operation to perform, possible values are static::DEFINITION_CREATED,
  196. * static::DEFINITION_UPDATED or static::DEFINITION_DELETED.
  197. * @param array|null $storage_definition
  198. * The new field storage definition.
  199. * @param array|null $original_storage_definition
  200. * The original field storage definition.
  201. */
  202. protected function doFieldUpdate($op, $storage_definition = NULL, $original_storage_definition = NULL) {
  203. switch ($op) {
  204. case static::DEFINITION_CREATED:
  205. $this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
  206. break;
  207. case static::DEFINITION_UPDATED:
  208. $this->entityManager->onFieldStorageDefinitionUpdate($storage_definition, $original_storage_definition);
  209. break;
  210. case static::DEFINITION_DELETED:
  211. $this->entityManager->onFieldStorageDefinitionDelete($original_storage_definition);
  212. break;
  213. }
  214. }
  215. /**
  216. * Gets a list of changes to entity type and field storage definitions.
  217. *
  218. * @return array
  219. * An associative array keyed by entity type id of change descriptors. Every
  220. * entry is an associative array with the following optional keys:
  221. * - entity_type: a scalar having only the DEFINITION_UPDATED value.
  222. * - field_storage_definitions: an associative array keyed by field name of
  223. * scalars having one value among:
  224. * - DEFINITION_CREATED
  225. * - DEFINITION_UPDATED
  226. * - DEFINITION_DELETED
  227. */
  228. protected function getChangeList() {
  229. $this->entityManager->useCaches(FALSE);
  230. $change_list = [];
  231. foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
  232. $original = $this->entityManager->getLastInstalledDefinition($entity_type_id);
  233. // @todo Support non-storage-schema-changing definition updates too:
  234. // https://www.drupal.org/node/2336895.
  235. if (!$original) {
  236. $change_list[$entity_type_id]['entity_type'] = static::DEFINITION_CREATED;
  237. }
  238. else {
  239. if ($this->requiresEntityStorageSchemaChanges($entity_type, $original)) {
  240. $change_list[$entity_type_id]['entity_type'] = static::DEFINITION_UPDATED;
  241. }
  242. if ($this->entityManager->getStorage($entity_type_id) instanceof DynamicallyFieldableEntityStorageInterface) {
  243. $field_changes = [];
  244. $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
  245. $original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
  246. // Detect created field storage definitions.
  247. foreach (array_diff_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
  248. $field_changes[$field_name] = static::DEFINITION_CREATED;
  249. }
  250. // Detect deleted field storage definitions.
  251. foreach (array_diff_key($original_storage_definitions, $storage_definitions) as $field_name => $original_storage_definition) {
  252. $field_changes[$field_name] = static::DEFINITION_DELETED;
  253. }
  254. // Detect updated field storage definitions.
  255. foreach (array_intersect_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
  256. // @todo Support non-storage-schema-changing definition updates too:
  257. // https://www.drupal.org/node/2336895. So long as we're checking
  258. // based on schema change requirements rather than definition
  259. // equality, skip the check if the entity type itself needs to be
  260. // updated, since that can affect the schema of all fields, so we
  261. // want to process that update first without reporting false
  262. // positives here.
  263. if (!isset($change_list[$entity_type_id]['entity_type']) && $this->requiresFieldStorageSchemaChanges($storage_definition, $original_storage_definitions[$field_name])) {
  264. $field_changes[$field_name] = static::DEFINITION_UPDATED;
  265. }
  266. }
  267. if ($field_changes) {
  268. $change_list[$entity_type_id]['field_storage_definitions'] = $field_changes;
  269. }
  270. }
  271. }
  272. }
  273. // @todo Support deleting entity definitions when we support base field
  274. // purging.
  275. // @see https://www.drupal.org/node/2907779
  276. $this->entityManager->useCaches(TRUE);
  277. return array_filter($change_list);
  278. }
  279. /**
  280. * Checks if the changes to the entity type requires storage schema changes.
  281. *
  282. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  283. * The updated entity type definition.
  284. * @param \Drupal\Core\Entity\EntityTypeInterface $original
  285. * The original entity type definition.
  286. *
  287. * @return bool
  288. * TRUE if storage schema changes are required, FALSE otherwise.
  289. */
  290. protected function requiresEntityStorageSchemaChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
  291. $storage = $this->entityManager->getStorage($entity_type->id());
  292. return ($storage instanceof EntityStorageSchemaInterface) && $storage->requiresEntityStorageSchemaChanges($entity_type, $original);
  293. }
  294. /**
  295. * Checks if the changes to the storage definition requires schema changes.
  296. *
  297. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  298. * The updated field storage definition.
  299. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
  300. * The original field storage definition.
  301. *
  302. * @return bool
  303. * TRUE if storage schema changes are required, FALSE otherwise.
  304. */
  305. protected function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
  306. $storage = $this->entityManager->getStorage($storage_definition->getTargetEntityTypeId());
  307. return ($storage instanceof DynamicallyFieldableEntityStorageSchemaInterface) && $storage->requiresFieldStorageSchemaChanges($storage_definition, $original);
  308. }
  309. }