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 * Constructs a new EntityDefinitionUpdateManager.
27 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
30 public function __construct(EntityManagerInterface $entity_manager) {
31 $this->entityManager = $entity_manager;
37 public function needsUpdates() {
38 return (bool) $this->getChangeList();
44 public function getChangeSummary() {
47 foreach ($this->getChangeList() as $entity_type_id => $change_list) {
48 // Process entity type definition changes.
49 if (!empty($change_list['entity_type'])) {
50 $entity_type = $this->entityManager->getDefinition($entity_type_id);
52 switch ($change_list['entity_type']) {
53 case static::DEFINITION_CREATED:
54 $summary[$entity_type_id][] = $this->t('The %entity_type entity type needs to be installed.', ['%entity_type' => $entity_type->getLabel()]);
57 case static::DEFINITION_UPDATED:
58 $summary[$entity_type_id][] = $this->t('The %entity_type entity type needs to be updated.', ['%entity_type' => $entity_type->getLabel()]);
63 // Process field storage definition changes.
64 if (!empty($change_list['field_storage_definitions'])) {
65 $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
66 $original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
68 foreach ($change_list['field_storage_definitions'] as $field_name => $change) {
70 case static::DEFINITION_CREATED:
71 $summary[$entity_type_id][] = $this->t('The %field_name field needs to be installed.', ['%field_name' => $storage_definitions[$field_name]->getLabel()]);
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()]);
78 case static::DEFINITION_DELETED:
79 $summary[$entity_type_id][] = $this->t('The %field_name field needs to be uninstalled.', ['%field_name' => $original_storage_definitions[$field_name]->getLabel()]);
92 public function applyUpdates() {
93 $complete_change_list = $this->getChangeList();
94 if ($complete_change_list) {
95 // self::getChangeList() only disables the cache and does not invalidate.
96 // In case there are changes, explicitly invalidate caches.
97 $this->entityManager->clearCachedDefinitions();
99 foreach ($complete_change_list as $entity_type_id => $change_list) {
100 // Process entity type definition changes before storage definitions ones
101 // this is necessary when you change an entity type from non-revisionable
102 // to revisionable and at the same time add revisionable fields to the
104 if (!empty($change_list['entity_type'])) {
105 $this->doEntityUpdate($change_list['entity_type'], $entity_type_id);
108 // Process field storage definition changes.
109 if (!empty($change_list['field_storage_definitions'])) {
110 $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
111 $original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
113 foreach ($change_list['field_storage_definitions'] as $field_name => $change) {
114 $storage_definition = isset($storage_definitions[$field_name]) ? $storage_definitions[$field_name] : NULL;
115 $original_storage_definition = isset($original_storage_definitions[$field_name]) ? $original_storage_definitions[$field_name] : NULL;
116 $this->doFieldUpdate($change, $storage_definition, $original_storage_definition);
125 public function getEntityType($entity_type_id) {
126 $entity_type = $this->entityManager->getLastInstalledDefinition($entity_type_id);
127 return $entity_type ? clone $entity_type : NULL;
133 public function installEntityType(EntityTypeInterface $entity_type) {
134 $this->entityManager->clearCachedDefinitions();
135 $this->entityManager->onEntityTypeCreate($entity_type);
141 public function updateEntityType(EntityTypeInterface $entity_type) {
142 $original = $this->getEntityType($entity_type->id());
143 $this->entityManager->clearCachedDefinitions();
144 $this->entityManager->onEntityTypeUpdate($entity_type, $original);
150 public function uninstallEntityType(EntityTypeInterface $entity_type) {
151 $this->entityManager->clearCachedDefinitions();
152 $this->entityManager->onEntityTypeDelete($entity_type);
158 public function installFieldStorageDefinition($name, $entity_type_id, $provider, FieldStorageDefinitionInterface $storage_definition) {
159 // @todo Pass a mutable field definition interface when we have one. See
160 // https://www.drupal.org/node/2346329.
161 if ($storage_definition instanceof BaseFieldDefinition) {
164 ->setTargetEntityTypeId($entity_type_id)
165 ->setProvider($provider)
166 ->setTargetBundle(NULL);
168 $this->entityManager->clearCachedDefinitions();
169 $this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
175 public function getFieldStorageDefinition($name, $entity_type_id) {
176 $storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
177 return isset($storage_definitions[$name]) ? clone $storage_definitions[$name] : NULL;
183 public function updateFieldStorageDefinition(FieldStorageDefinitionInterface $storage_definition) {
184 $original = $this->getFieldStorageDefinition($storage_definition->getName(), $storage_definition->getTargetEntityTypeId());
185 $this->entityManager->clearCachedDefinitions();
186 $this->entityManager->onFieldStorageDefinitionUpdate($storage_definition, $original);
192 public function uninstallFieldStorageDefinition(FieldStorageDefinitionInterface $storage_definition) {
193 $this->entityManager->clearCachedDefinitions();
194 $this->entityManager->onFieldStorageDefinitionDelete($storage_definition);
198 * Performs an entity type definition update.
201 * The operation to perform, either static::DEFINITION_CREATED or
202 * static::DEFINITION_UPDATED.
203 * @param string $entity_type_id
204 * The entity type ID.
206 protected function doEntityUpdate($op, $entity_type_id) {
207 $entity_type = $this->entityManager->getDefinition($entity_type_id);
209 case static::DEFINITION_CREATED:
210 $this->entityManager->onEntityTypeCreate($entity_type);
213 case static::DEFINITION_UPDATED:
214 $original = $this->entityManager->getLastInstalledDefinition($entity_type_id);
215 $this->entityManager->onEntityTypeUpdate($entity_type, $original);
221 * Performs a field storage definition update.
224 * The operation to perform, possible values are static::DEFINITION_CREATED,
225 * static::DEFINITION_UPDATED or static::DEFINITION_DELETED.
226 * @param array|null $storage_definition
227 * The new field storage definition.
228 * @param array|null $original_storage_definition
229 * The original field storage definition.
231 protected function doFieldUpdate($op, $storage_definition = NULL, $original_storage_definition = NULL) {
233 case static::DEFINITION_CREATED:
234 $this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
237 case static::DEFINITION_UPDATED:
238 $this->entityManager->onFieldStorageDefinitionUpdate($storage_definition, $original_storage_definition);
241 case static::DEFINITION_DELETED:
242 $this->entityManager->onFieldStorageDefinitionDelete($original_storage_definition);
248 * Gets a list of changes to entity type and field storage definitions.
251 * An associative array keyed by entity type id of change descriptors. Every
252 * entry is an associative array with the following optional keys:
253 * - entity_type: a scalar having only the DEFINITION_UPDATED value.
254 * - field_storage_definitions: an associative array keyed by field name of
255 * scalars having one value among:
256 * - DEFINITION_CREATED
257 * - DEFINITION_UPDATED
258 * - DEFINITION_DELETED
260 protected function getChangeList() {
261 $this->entityManager->useCaches(FALSE);
264 foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
265 $original = $this->entityManager->getLastInstalledDefinition($entity_type_id);
267 // @todo Support non-storage-schema-changing definition updates too:
268 // https://www.drupal.org/node/2336895.
270 $change_list[$entity_type_id]['entity_type'] = static::DEFINITION_CREATED;
273 if ($this->requiresEntityStorageSchemaChanges($entity_type, $original)) {
274 $change_list[$entity_type_id]['entity_type'] = static::DEFINITION_UPDATED;
277 if ($this->entityManager->getStorage($entity_type_id) instanceof DynamicallyFieldableEntityStorageInterface) {
279 $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
280 $original_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id);
282 // Detect created field storage definitions.
283 foreach (array_diff_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
284 $field_changes[$field_name] = static::DEFINITION_CREATED;
287 // Detect deleted field storage definitions.
288 foreach (array_diff_key($original_storage_definitions, $storage_definitions) as $field_name => $original_storage_definition) {
289 $field_changes[$field_name] = static::DEFINITION_DELETED;
292 // Detect updated field storage definitions.
293 foreach (array_intersect_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) {
294 // @todo Support non-storage-schema-changing definition updates too:
295 // https://www.drupal.org/node/2336895. So long as we're checking
296 // based on schema change requirements rather than definition
297 // equality, skip the check if the entity type itself needs to be
298 // updated, since that can affect the schema of all fields, so we
299 // want to process that update first without reporting false
301 if (!isset($change_list[$entity_type_id]['entity_type']) && $this->requiresFieldStorageSchemaChanges($storage_definition, $original_storage_definitions[$field_name])) {
302 $field_changes[$field_name] = static::DEFINITION_UPDATED;
306 if ($field_changes) {
307 $change_list[$entity_type_id]['field_storage_definitions'] = $field_changes;
313 // @todo Support deleting entity definitions when we support base field
314 // purging. See https://www.drupal.org/node/2282119.
316 $this->entityManager->useCaches(TRUE);
318 return array_filter($change_list);
322 * Checks if the changes to the entity type requires storage schema changes.
324 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
325 * The updated entity type definition.
326 * @param \Drupal\Core\Entity\EntityTypeInterface $original
327 * The original entity type definition.
330 * TRUE if storage schema changes are required, FALSE otherwise.
332 protected function requiresEntityStorageSchemaChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
333 $storage = $this->entityManager->getStorage($entity_type->id());
334 return ($storage instanceof EntityStorageSchemaInterface) && $storage->requiresEntityStorageSchemaChanges($entity_type, $original);
338 * Checks if the changes to the storage definition requires schema changes.
340 * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
341 * The updated field storage definition.
342 * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
343 * The original field storage definition.
346 * TRUE if storage schema changes are required, FALSE otherwise.
348 protected function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
349 $storage = $this->entityManager->getStorage($storage_definition->getTargetEntityTypeId());
350 return ($storage instanceof DynamicallyFieldableEntityStorageSchemaInterface) && $storage->requiresFieldStorageSchemaChanges($storage_definition, $original);