3 namespace Drupal\Core\Entity;
5 use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
6 use Drupal\Core\Entity\Schema\EntityStorageSchemaInterface;
7 use Drupal\Core\Field\BaseFieldDefinition;
8 use Drupal\Core\Field\FieldStorageDefinitionInterface;
9 use Drupal\Core\StringTranslation\StringTranslationTrait;
12 * Manages entity definition updates.
14 class EntityDefinitionUpdateManager implements EntityDefinitionUpdateManagerInterface {
15 use StringTranslationTrait;
18 * The entity manager service.
20 * @var \Drupal\Core\Entity\EntityManagerInterface
22 protected $entityManager;
25 * The last installed schema repository.
27 * @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface
29 protected $entityLastInstalledSchemaRepository;
32 * Constructs a new EntityDefinitionUpdateManager.
34 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
36 * @param \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_repository
37 * The last installed schema repository service.
39 public function __construct(EntityManagerInterface $entity_manager, EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_repository = NULL) {
40 $this->entityManager = $entity_manager;
42 if (!isset($entity_last_installed_schema_repository)) {
43 @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);
44 $entity_last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');
46 $this->entityLastInstalledSchemaRepository = $entity_last_installed_schema_repository;
52 public function needsUpdates() {
53 return (bool) $this->getChangeList();
59 public function getChangeSummary() {
62 foreach ($this->getChangeList() as $entity_type_id => $change_list) {
63 // Process entity type definition changes.
64 if (!empty($change_list['entity_type'])) {
65 $entity_type = $this->entityManager->getDefinition($entity_type_id);
67 switch ($change_list['entity_type']) {
68 case static::DEFINITION_CREATED:
69 $summary[$entity_type_id][] = $this->t('The %entity_type entity type needs to be installed.', ['%entity_type' => $entity_type->getLabel()]);
72 case static::DEFINITION_UPDATED:
73 $summary[$entity_type_id][] = $this->t('The %entity_type entity type needs to be updated.', ['%entity_type' => $entity_type->getLabel()]);
78 // Process field storage definition changes.
79 if (!empty($change_list['field_storage_definitions'])) {
80 $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
81 $original_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id);
83 foreach ($change_list['field_storage_definitions'] as $field_name => $change) {
85 case static::DEFINITION_CREATED:
86 $summary[$entity_type_id][] = $this->t('The %field_name field needs to be installed.', ['%field_name' => $storage_definitions[$field_name]->getLabel()]);
89 case static::DEFINITION_UPDATED:
90 $summary[$entity_type_id][] = $this->t('The %field_name field needs to be updated.', ['%field_name' => $storage_definitions[$field_name]->getLabel()]);
93 case static::DEFINITION_DELETED:
94 $summary[$entity_type_id][] = $this->t('The %field_name field needs to be uninstalled.', ['%field_name' => $original_storage_definitions[$field_name]->getLabel()]);
107 public function applyUpdates() {
108 $complete_change_list = $this->getChangeList();
109 if ($complete_change_list) {
110 // self::getChangeList() only disables the cache and does not invalidate.
111 // In case there are changes, explicitly invalidate caches.
112 $this->entityManager->clearCachedDefinitions();
114 foreach ($complete_change_list as $entity_type_id => $change_list) {
115 // Process entity type definition changes before storage definitions ones
116 // this is necessary when you change an entity type from non-revisionable
117 // to revisionable and at the same time add revisionable fields to the
119 if (!empty($change_list['entity_type'])) {
120 $this->doEntityUpdate($change_list['entity_type'], $entity_type_id);
123 // Process field storage definition changes.
124 if (!empty($change_list['field_storage_definitions'])) {
125 $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
126 $original_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id);
128 foreach ($change_list['field_storage_definitions'] as $field_name => $change) {
129 $storage_definition = isset($storage_definitions[$field_name]) ? $storage_definitions[$field_name] : NULL;
130 $original_storage_definition = isset($original_storage_definitions[$field_name]) ? $original_storage_definitions[$field_name] : NULL;
131 $this->doFieldUpdate($change, $storage_definition, $original_storage_definition);
140 public function getEntityType($entity_type_id) {
141 $entity_type = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type_id);
142 return $entity_type ? clone $entity_type : NULL;
148 public function getEntityTypes() {
149 return $this->entityLastInstalledSchemaRepository->getLastInstalledDefinitions();
155 public function installEntityType(EntityTypeInterface $entity_type) {
156 $this->entityManager->clearCachedDefinitions();
157 $this->entityManager->onEntityTypeCreate($entity_type);
163 public function updateEntityType(EntityTypeInterface $entity_type) {
164 $original = $this->getEntityType($entity_type->id());
165 $this->entityManager->clearCachedDefinitions();
166 $this->entityManager->onEntityTypeUpdate($entity_type, $original);
172 public function uninstallEntityType(EntityTypeInterface $entity_type) {
173 $this->entityManager->clearCachedDefinitions();
174 $this->entityManager->onEntityTypeDelete($entity_type);
180 public function installFieldStorageDefinition($name, $entity_type_id, $provider, FieldStorageDefinitionInterface $storage_definition) {
181 // @todo Pass a mutable field definition interface when we have one. See
182 // https://www.drupal.org/node/2346329.
183 if ($storage_definition instanceof BaseFieldDefinition) {
186 ->setTargetEntityTypeId($entity_type_id)
187 ->setProvider($provider)
188 ->setTargetBundle(NULL);
190 $this->entityManager->clearCachedDefinitions();
191 $this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
197 public function getFieldStorageDefinition($name, $entity_type_id) {
198 $storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id);
199 return isset($storage_definitions[$name]) ? clone $storage_definitions[$name] : NULL;
205 public function updateFieldStorageDefinition(FieldStorageDefinitionInterface $storage_definition) {
206 $original = $this->getFieldStorageDefinition($storage_definition->getName(), $storage_definition->getTargetEntityTypeId());
207 $this->entityManager->clearCachedDefinitions();
208 $this->entityManager->onFieldStorageDefinitionUpdate($storage_definition, $original);
214 public function uninstallFieldStorageDefinition(FieldStorageDefinitionInterface $storage_definition) {
215 $this->entityManager->clearCachedDefinitions();
216 $this->entityManager->onFieldStorageDefinitionDelete($storage_definition);
220 * Performs an entity type definition update.
223 * The operation to perform, either static::DEFINITION_CREATED or
224 * static::DEFINITION_UPDATED.
225 * @param string $entity_type_id
226 * The entity type ID.
228 protected function doEntityUpdate($op, $entity_type_id) {
229 $entity_type = $this->entityManager->getDefinition($entity_type_id);
231 case static::DEFINITION_CREATED:
232 $this->entityManager->onEntityTypeCreate($entity_type);
235 case static::DEFINITION_UPDATED:
236 $original = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type_id);
237 $this->entityManager->onEntityTypeUpdate($entity_type, $original);
243 * Performs a field storage definition update.
246 * The operation to perform, possible values are static::DEFINITION_CREATED,
247 * static::DEFINITION_UPDATED or static::DEFINITION_DELETED.
248 * @param array|null $storage_definition
249 * The new field storage definition.
250 * @param array|null $original_storage_definition
251 * The original field storage definition.
253 protected function doFieldUpdate($op, $storage_definition = NULL, $original_storage_definition = NULL) {
255 case static::DEFINITION_CREATED:
256 $this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
259 case static::DEFINITION_UPDATED:
260 $this->entityManager->onFieldStorageDefinitionUpdate($storage_definition, $original_storage_definition);
263 case static::DEFINITION_DELETED:
264 $this->entityManager->onFieldStorageDefinitionDelete($original_storage_definition);
270 * Gets a list of changes to entity type and field storage definitions.
273 * An associative array keyed by entity type id of change descriptors. Every
274 * entry is an associative array with the following optional keys:
275 * - entity_type: a scalar having only the DEFINITION_UPDATED value.
276 * - field_storage_definitions: an associative array keyed by field name of
277 * scalars having one value among:
278 * - DEFINITION_CREATED
279 * - DEFINITION_UPDATED
280 * - DEFINITION_DELETED
282 protected function getChangeList() {
283 $this->entityManager->useCaches(FALSE);
286 foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
287 $original = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type_id);
289 // @todo Support non-storage-schema-changing definition updates too:
290 // https://www.drupal.org/node/2336895.
292 $change_list[$entity_type_id]['entity_type'] = static::DEFINITION_CREATED;
295 if ($this->requiresEntityStorageSchemaChanges($entity_type, $original)) {
296 $change_list[$entity_type_id]['entity_type'] = static::DEFINITION_UPDATED;
299 if ($this->entityManager->getStorage($entity_type_id) instanceof DynamicallyFieldableEntityStorageInterface) {
301 $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
302 $original_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type_id);
304 // Detect created field storage definitions.
305 foreach (array_diff_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
306 $field_changes[$field_name] = static::DEFINITION_CREATED;
309 // Detect deleted field storage definitions.
310 foreach (array_diff_key($original_storage_definitions, $storage_definitions) as $field_name => $original_storage_definition) {
311 $field_changes[$field_name] = static::DEFINITION_DELETED;
314 // Detect updated field storage definitions.
315 foreach (array_intersect_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
316 // @todo Support non-storage-schema-changing definition updates too:
317 // https://www.drupal.org/node/2336895. So long as we're checking
318 // based on schema change requirements rather than definition
319 // equality, skip the check if the entity type itself needs to be
320 // updated, since that can affect the schema of all fields, so we
321 // want to process that update first without reporting false
323 if (!isset($change_list[$entity_type_id]['entity_type']) && $this->requiresFieldStorageSchemaChanges($storage_definition, $original_storage_definitions[$field_name])) {
324 $field_changes[$field_name] = static::DEFINITION_UPDATED;
328 if ($field_changes) {
329 $change_list[$entity_type_id]['field_storage_definitions'] = $field_changes;
335 // @todo Support deleting entity definitions when we support base field
337 // @see https://www.drupal.org/node/2907779
339 $this->entityManager->useCaches(TRUE);
341 return array_filter($change_list);
345 * Checks if the changes to the entity type requires storage schema changes.
347 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
348 * The updated entity type definition.
349 * @param \Drupal\Core\Entity\EntityTypeInterface $original
350 * The original entity type definition.
353 * TRUE if storage schema changes are required, FALSE otherwise.
355 protected function requiresEntityStorageSchemaChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
356 $storage = $this->entityManager->getStorage($entity_type->id());
357 return ($storage instanceof EntityStorageSchemaInterface) && $storage->requiresEntityStorageSchemaChanges($entity_type, $original);
361 * Checks if the changes to the storage definition requires schema changes.
363 * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
364 * The updated field storage definition.
365 * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
366 * The original field storage definition.
369 * TRUE if storage schema changes are required, FALSE otherwise.
371 protected function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
372 $storage = $this->entityManager->getStorage($storage_definition->getTargetEntityTypeId());
373 return ($storage instanceof DynamicallyFieldableEntityStorageSchemaInterface) && $storage->requiresFieldStorageSchemaChanges($storage_definition, $original);