Skip to content
Snippets Groups Projects
Commit 15e3a5a8 authored by catch's avatar catch
Browse files

Issue #3056816 by amateescu, larowlan, plach, Sam152: Installing a new field...

Issue #3056816 by amateescu, larowlan, plach, Sam152: Installing a new field storage definition during a fieldable entity type update is not possible
parent 5cd26f24
No related branches found
No related tags found
No related merge requests found
Showing
with 209 additions and 39 deletions
......@@ -194,7 +194,6 @@ public function getEntityTypes() {
* {@inheritdoc}
*/
public function installEntityType(EntityTypeInterface $entity_type) {
$this->clearCachedDefinitions();
$this->entityTypeListener->onEntityTypeCreate($entity_type);
}
......@@ -203,7 +202,6 @@ public function installEntityType(EntityTypeInterface $entity_type) {
*/
public function updateEntityType(EntityTypeInterface $entity_type) {
$original = $this->getEntityType($entity_type->id());
$this->clearCachedDefinitions();
$this->entityTypeListener->onEntityTypeUpdate($entity_type, $original);
}
......@@ -211,7 +209,6 @@ public function updateEntityType(EntityTypeInterface $entity_type) {
* {@inheritdoc}
*/
public function uninstallEntityType(EntityTypeInterface $entity_type) {
$this->clearCachedDefinitions();
$this->entityTypeListener->onEntityTypeDelete($entity_type);
}
......@@ -227,7 +224,6 @@ public function updateFieldableEntityType(EntityTypeInterface $entity_type, arra
$original_field_storage_definitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entity_type->id());
$this->entityTypeListener->onFieldableEntityTypeUpdate($entity_type, $original, $field_storage_definitions, $original_field_storage_definitions, $sandbox);
$this->clearCachedDefinitions();
}
/**
......@@ -243,7 +239,6 @@ public function installFieldStorageDefinition($name, $entity_type_id, $provider,
->setProvider($provider)
->setTargetBundle(NULL);
}
$this->clearCachedDefinitions();
$this->fieldStorageDefinitionListener->onFieldStorageDefinitionCreate($storage_definition);
}
......@@ -260,7 +255,6 @@ public function getFieldStorageDefinition($name, $entity_type_id) {
*/
public function updateFieldStorageDefinition(FieldStorageDefinitionInterface $storage_definition) {
$original = $this->getFieldStorageDefinition($storage_definition->getName(), $storage_definition->getTargetEntityTypeId());
$this->clearCachedDefinitions();
$this->fieldStorageDefinitionListener->onFieldStorageDefinitionUpdate($storage_definition, $original);
}
......@@ -268,7 +262,6 @@ public function updateFieldStorageDefinition(FieldStorageDefinitionInterface $st
* {@inheritdoc}
*/
public function uninstallFieldStorageDefinition(FieldStorageDefinitionInterface $storage_definition) {
$this->clearCachedDefinitions();
$this->fieldStorageDefinitionListener->onFieldStorageDefinitionDelete($storage_definition);
}
......@@ -387,12 +380,4 @@ protected function requiresEntityDataMigration(EntityTypeInterface $entity_type,
return ($storage instanceof EntityStorageSchemaInterface) && $storage->requiresEntityDataMigration($entity_type, $original);
}
/**
* Clears necessary caches to apply entity/field definition updates.
*/
protected function clearCachedDefinitions() {
$this->entityTypeManager->clearCachedDefinitions();
$this->entityFieldManager->clearCachedFieldDefinitions();
}
}
......@@ -71,12 +71,13 @@ public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
$storage->onEntityTypeCreate($entity_type);
}
$this->eventDispatcher->dispatch(EntityTypeEvents::CREATE, new EntityTypeEvent($entity_type));
$this->entityLastInstalledSchemaRepository->setLastInstalledDefinition($entity_type);
if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) {
$this->entityLastInstalledSchemaRepository->setLastInstalledFieldStorageDefinitions($entity_type_id, $this->entityFieldManager->getFieldStorageDefinitions($entity_type_id));
}
$this->clearCachedDefinitions();
$this->eventDispatcher->dispatch(EntityTypeEvents::CREATE, new EntityTypeEvent($entity_type));
}
/**
......@@ -94,9 +95,10 @@ public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeI
$storage->onEntityTypeUpdate($entity_type, $original);
}
$this->eventDispatcher->dispatch(EntityTypeEvents::UPDATE, new EntityTypeEvent($entity_type, $original));
$this->entityLastInstalledSchemaRepository->setLastInstalledDefinition($entity_type);
$this->clearCachedDefinitions();
$this->eventDispatcher->dispatch(EntityTypeEvents::UPDATE, new EntityTypeEvent($entity_type, $original));
}
/**
......@@ -116,9 +118,10 @@ public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
$storage->onEntityTypeDelete($entity_type);
}
$this->eventDispatcher->dispatch(EntityTypeEvents::DELETE, new EntityTypeEvent($entity_type));
$this->entityLastInstalledSchemaRepository->deleteLastInstalledDefinition($entity_type_id);
$this->clearCachedDefinitions();
$this->eventDispatcher->dispatch(EntityTypeEvents::DELETE, new EntityTypeEvent($entity_type));
}
/**
......@@ -135,13 +138,22 @@ public function onFieldableEntityTypeUpdate(EntityTypeInterface $entity_type, En
}
if ($sandbox === NULL || (isset($sandbox['#finished']) && $sandbox['#finished'] == 1)) {
$this->eventDispatcher->dispatch(EntityTypeEvents::UPDATE, new EntityTypeEvent($entity_type, $original));
$this->entityLastInstalledSchemaRepository->setLastInstalledDefinition($entity_type);
if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) {
$this->entityLastInstalledSchemaRepository->setLastInstalledFieldStorageDefinitions($entity_type_id, $field_storage_definitions);
}
$this->clearCachedDefinitions();
$this->eventDispatcher->dispatch(EntityTypeEvents::UPDATE, new EntityTypeEvent($entity_type, $original));
}
}
/**
* Clears necessary caches before dispatching entity/field definition events.
*/
protected function clearCachedDefinitions() {
$this->entityTypeManager->clearCachedDefinitions();
$this->entityFieldManager->clearCachedFieldDefinitions();
}
}
......@@ -85,10 +85,10 @@ public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $
$storage->onFieldStorageDefinitionCreate($storage_definition);
}
$this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::CREATE, new FieldStorageDefinitionEvent($storage_definition));
$this->entityLastInstalledSchemaRepository->setLastInstalledFieldStorageDefinition($storage_definition);
$this->entityFieldManager->clearCachedFieldDefinitions();
$this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::CREATE, new FieldStorageDefinitionEvent($storage_definition));
}
/**
......@@ -104,10 +104,10 @@ public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $
$storage->onFieldStorageDefinitionUpdate($storage_definition, $original);
}
$this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::UPDATE, new FieldStorageDefinitionEvent($storage_definition, $original));
$this->entityLastInstalledSchemaRepository->setLastInstalledFieldStorageDefinition($storage_definition);
$this->entityFieldManager->clearCachedFieldDefinitions();
$this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::UPDATE, new FieldStorageDefinitionEvent($storage_definition, $original));
}
/**
......@@ -133,10 +133,10 @@ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $
$storage->onFieldStorageDefinitionDelete($storage_definition);
}
$this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::DELETE, new FieldStorageDefinitionEvent($storage_definition));
$this->entityLastInstalledSchemaRepository->deleteLastInstalledFieldStorageDefinition($storage_definition);
$this->entityFieldManager->clearCachedFieldDefinitions();
$this->eventDispatcher->dispatch(FieldStorageDefinitionEvents::DELETE, new FieldStorageDefinitionEvent($storage_definition));
}
}
services:
entity_test.definition.subscriber:
class: Drupal\entity_test\EntityTestDefinitionSubscriber
arguments: ['@state']
arguments: ['@state', '@entity.last_installed_schema.repository']
tags:
- { name: event_subscriber }
cache_context.entity_test_view_grants:
......
......@@ -2,6 +2,7 @@
namespace Drupal\entity_test;
use Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface;
use Drupal\Core\Entity\EntityTypeEvents;
use Drupal\Core\Entity\EntityTypeEventSubscriberTrait;
use Drupal\Core\Entity\EntityTypeInterface;
......@@ -28,6 +29,13 @@ class EntityTestDefinitionSubscriber implements EventSubscriberInterface, Entity
*/
protected $state;
/**
* The last installed schema repository.
*
* @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface
*/
protected $entityLastInstalledSchemaRepository;
/**
* Flag determining whether events should be tracked.
*
......@@ -38,8 +46,9 @@ class EntityTestDefinitionSubscriber implements EventSubscriberInterface, Entity
/**
* {@inheritdoc}
*/
public function __construct(StateInterface $state) {
public function __construct(StateInterface $state, EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_repository) {
$this->state = $state;
$this->entityLastInstalledSchemaRepository = $entity_last_installed_schema_repository;
}
/**
......@@ -53,6 +62,9 @@ public static function getSubscribedEvents() {
* {@inheritdoc}
*/
public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
if ($this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type->id())) {
$this->storeDefinitionUpdate(EntityTypeEvents::CREATE);
}
$this->storeEvent(EntityTypeEvents::CREATE);
}
......@@ -60,6 +72,11 @@ public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
* {@inheritdoc}
*/
public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
$last_installed_definition = $this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type->id());
if ((string) $last_installed_definition->getLabel() === 'Updated entity test rev') {
$this->storeDefinitionUpdate(EntityTypeEvents::UPDATE);
}
$this->storeEvent(EntityTypeEvents::UPDATE);
}
......@@ -74,6 +91,9 @@ public function onFieldableEntityTypeUpdate(EntityTypeInterface $entity_type, En
* {@inheritdoc}
*/
public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
if (!$this->entityLastInstalledSchemaRepository->getLastInstalledDefinition($entity_type->id())) {
$this->storeDefinitionUpdate(EntityTypeEvents::DELETE);
}
$this->storeEvent(EntityTypeEvents::DELETE);
}
......@@ -81,6 +101,9 @@ public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
* {@inheritdoc}
*/
public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {
if (isset($this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($storage_definition->getTargetEntityTypeId())[$storage_definition->getName()])) {
$this->storeDefinitionUpdate(FieldStorageDefinitionEvents::CREATE);
}
$this->storeEvent(FieldStorageDefinitionEvents::CREATE);
}
......@@ -88,6 +111,10 @@ public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $
* {@inheritdoc}
*/
public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
$last_installed_definition = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($storage_definition->getTargetEntityTypeId())[$storage_definition->getName()];
if ((string) $last_installed_definition->getLabel() === 'Updated field storage test') {
$this->storeDefinitionUpdate(FieldStorageDefinitionEvents::UPDATE);
}
$this->storeEvent(FieldStorageDefinitionEvents::UPDATE);
}
......@@ -95,6 +122,9 @@ public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $
* {@inheritdoc}
*/
public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) {
if (!isset($this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($storage_definition->getTargetEntityTypeId())[$storage_definition->getName()])) {
$this->storeDefinitionUpdate(FieldStorageDefinitionEvents::DELETE);
}
$this->storeEvent(FieldStorageDefinitionEvents::DELETE);
}
......@@ -130,4 +160,30 @@ protected function storeEvent($event_name) {
}
}
/**
* Checks whether the installed definitions were updated before the event.
*
* @param string $event_name
* The event name.
*
* @return bool
* TRUE if the last installed entity type of field storage definitions have
* been updated before the was fired, FALSE otherwise.
*/
public function hasDefinitionBeenUpdated($event_name) {
return (bool) $this->state->get($event_name . '_updated_definition');
}
/**
* Stores the installed definition state for the specified event.
*
* @param string $event_name
* The event name.
*/
protected function storeDefinitionUpdate($event_name) {
if ($this->trackEvents) {
$this->state->set($event_name . '_updated_definition', TRUE);
}
}
}
services:
entity_test_update.entity_schema_listener:
class: Drupal\entity_test_update\EventSubscriber\EntitySchemaSubscriber
arguments: ['@entity.definition_update_manager', '@state']
tags:
- { name: 'event_subscriber' }
<?php
namespace Drupal\entity_test_update\EventSubscriber;
use Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface;
use Drupal\Core\Entity\EntityTypeEventSubscriberTrait;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeListenerInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\State\StateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Defines a class for listening to entity schema changes.
*/
class EntitySchemaSubscriber implements EntityTypeListenerInterface, EventSubscriberInterface {
use EntityTypeEventSubscriberTrait;
/**
* The entity definition update manager.
*
* @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
*/
protected $entityDefinitionUpdateManager;
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* Constructs a new EntitySchemaSubscriber.
*
* @param \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface $entityDefinitionUpdateManager
* The entity definition update manager.
* @param \Drupal\Core\State\StateInterface $state
* The state service.
*/
public function __construct(EntityDefinitionUpdateManagerInterface $entityDefinitionUpdateManager, StateInterface $state) {
$this->entityDefinitionUpdateManager = $entityDefinitionUpdateManager;
$this->state = $state;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return static::getEntityTypeEvents();
}
/**
* {@inheritdoc}
*/
public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
// Only add the new base field when a test needs it.
if (!$this->state->get('entity_test_update.install_new_base_field_during_update', FALSE)) {
return;
}
// Add a new base field when the entity type is updated.
$definitions = $this->state->get('entity_test_update.additional_base_field_definitions', []);
$definitions['new_base_field'] = BaseFieldDefinition::create('string')
->setName('new_base_field')
->setLabel(new TranslatableMarkup('A new base field'));
$this->state->set('entity_test_update.additional_base_field_definitions', $definitions);
$this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test_update', $definitions['new_base_field']);
}
}
......@@ -14,6 +14,8 @@
use Drupal\Core\Field\FieldException;
use Drupal\Core\Field\FieldStorageDefinitionEvents;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\entity_test\FieldStorageDefinition;
use Drupal\entity_test_update\Entity\EntityTestUpdate;
use Drupal\Tests\system\Functional\Entity\Traits\EntityDefinitionTestTrait;
......@@ -835,28 +837,47 @@ public function testDefinitionEvents() {
$event_subscriber->enableEventTracking();
// Test field storage definition events.
$storage_definition = current(\Drupal::service('entity_field.manager')->getFieldStorageDefinitions('entity_test_rev'));
$this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete was not dispatched yet.');
\Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionDelete($storage_definition);
$this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete event successfully dispatched.');
$storage_definition = FieldStorageDefinition::create('string')
->setName('field_storage_test')
->setLabel(new TranslatableMarkup('Field storage test'))
->setTargetEntityTypeId('entity_test_rev');
$this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create was not dispatched yet.');
\Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionCreate($storage_definition);
$this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create event successfully dispatched.');
$this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(FieldStorageDefinitionEvents::CREATE), 'Last installed field storage definition was created before the event was fired.');
$updated_storage_definition = clone $storage_definition;
$updated_storage_definition->setLabel(new TranslatableMarkup('Updated field storage test'));
$this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update was not dispatched yet.');
\Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionUpdate($storage_definition, $storage_definition);
\Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionUpdate($updated_storage_definition, $storage_definition);
$this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update event successfully dispatched.');
$this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(FieldStorageDefinitionEvents::UPDATE), 'Last installed field storage definition was updated before the event was fired.');
$this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete was not dispatched yet.');
\Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionDelete($storage_definition);
$this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete event successfully dispatched.');
$this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(FieldStorageDefinitionEvents::DELETE), 'Last installed field storage definition was deleted before the event was fired.');
// Test entity type events.
$entity_type = $this->entityTypeManager->getDefinition('entity_test_rev');
$this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::CREATE), 'Entity type create was not dispatched yet.');
\Drupal::service('entity_type.listener')->onEntityTypeCreate($entity_type);
$this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::CREATE), 'Entity type create event successfully dispatched.');
$this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(EntityTypeEvents::CREATE), 'Last installed entity type definition was created before the event was fired.');
$updated_entity_type = clone $entity_type;
$updated_entity_type->set('label', new TranslatableMarkup('Updated entity test rev'));
$this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update was not dispatched yet.');
\Drupal::service('entity_type.listener')->onEntityTypeUpdate($entity_type, $entity_type);
\Drupal::service('entity_type.listener')->onEntityTypeUpdate($updated_entity_type, $entity_type);
$this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update event successfully dispatched.');
$this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(EntityTypeEvents::UPDATE), 'Last installed entity type definition was updated before the event was fired.');
$this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete was not dispatched yet.');
\Drupal::service('entity_type.listener')->onEntityTypeDelete($entity_type);
$this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete event successfully dispatched.');
$this->assertTrue($event_subscriber->hasDefinitionBeenUpdated(EntityTypeEvents::DELETE), 'Last installed entity type definition was deleted before the event was fired.');
}
/**
......
......@@ -151,13 +151,17 @@ public function testFieldableEntityTypeUpdates($initial_rev, $initial_mul, $new_
$this->assertEntityData($initial_rev, $initial_mul);
}
// Enable the creation of a new base field during a fieldable entity type
// update.
$this->state->set('entity_test_update.install_new_base_field_during_update', TRUE);
// Simulate a batch run since we are converting the entities one by one.
$sandbox = [];
do {
$this->entityDefinitionUpdateManager->updateFieldableEntityType($updated_entity_type, $updated_field_storage_definitions, $sandbox);
} while ($sandbox['#finished'] != 1);
$this->assertEntityTypeSchema($new_rev, $new_mul);
$this->assertEntityTypeSchema($new_rev, $new_mul, TRUE);
$this->assertEntityData($initial_rev, $initial_mul);
$change_list = $this->entityDefinitionUpdateManager->getChangeList();
......@@ -427,8 +431,20 @@ protected function assertEntityData($revisionable, $translatable) {
* Whether the entity type is revisionable or not.
* @param bool $translatable
* Whether the entity type is translatable or not.
* @param bool $new_base_field
* (optional) Whether a new base field was added as part of the update.
* Defaults to FALSE.
*/
protected function assertEntityTypeSchema($revisionable, $translatable) {
protected function assertEntityTypeSchema($revisionable, $translatable, $new_base_field = FALSE) {
// Check whether the 'new_base_field' field has been installed correctly.
$field_storage_definition = $this->entityDefinitionUpdateManager->getFieldStorageDefinition('new_base_field', $this->entityTypeId);
if ($new_base_field) {
$this->assertNotNull($field_storage_definition);
}
else {
$this->assertNull($field_storage_definition);
}
if ($revisionable && $translatable) {
$this->assertRevisionableAndTranslatable();
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment