EntityDefinitionUpdateManager.php 15 KB

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