diff --git a/enum_field.services.yml b/enum_field.services.yml new file mode 100644 index 0000000000000000000000000000000000000000..d8f2199b9746e4c5d180136d57c1c0b17f96c9e0 --- /dev/null +++ b/enum_field.services.yml @@ -0,0 +1,8 @@ +services: + enum_field.migration: + class: Drupal\enum_field\Migration + arguments: + - '@config.factory' + - '@entity_field.manager' + - '@entity.last_installed_schema.repository' + Drupal\enum_field\Migration: '@enum_field.migration' diff --git a/src/Migration.php b/src/Migration.php new file mode 100644 index 0000000000000000000000000000000000000000..bbe0fce7a183c6c1e3fb33a175ded9331a7f6045 --- /dev/null +++ b/src/Migration.php @@ -0,0 +1,154 @@ +<?php + +namespace Drupal\enum_field; + +use Drupal\Core\Config\Config; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Entity\EntityFieldManagerInterface; +use Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface; +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; + +/** + * Helper methods to migrate between list and enum field types. + */ +class Migration { + + protected const FIELD_TYPE_MAP = [ + 'enum_integer' => 'list_integer', + 'enum_string' => 'list_string', + ]; + + /** + * Constructor. + * + * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory + * The config factory service. + * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager + * The entity field manager service. + * @param \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $entityLastInstalledSchemaRepository + * The entity last installed schema repository service. + */ + public function __construct( + protected ConfigFactoryInterface $configFactory, + protected EntityFieldManagerInterface $entityFieldManager, + protected EntityLastInstalledSchemaRepositoryInterface $entityLastInstalledSchemaRepository, + ) { + } + + /** + * Migrates an enum field to a list field. + * + * @param string $entityTypeId + * The entity type ID. + * @param string $fieldName + * The field name. + * + * @throws \Exception + * Thrown when the field is not a list field. + */ + public function migrateEnumField(string $entityTypeId, string $fieldName): void { + $fieldStorageConfig = $this->configFactory->getEditable("field.storage.$entityTypeId.$fieldName"); + $sourceFieldType = $fieldStorageConfig->get('type'); + $targetFieldType = static::FIELD_TYPE_MAP[$sourceFieldType] ?? NULL; + + if ($sourceFieldType === $targetFieldType) { + // Already migrated + return; + } + + if ($targetFieldType === NULL) { + throw new \Exception('Not an enum field'); + } + + $this->doMigrateField( + entityTypeId: $entityTypeId, + fieldName: $fieldName, + fieldStorageConfig: $fieldStorageConfig, + sourceFieldType: $sourceFieldType, + targetFieldType: $targetFieldType, + targetModule: 'options', + ); + } + + /** + * Migrates a list field to an enum field. + * + * @param string $entityTypeId + * The entity type ID. + * @param string $fieldName + * The field name. + * + * @throws \Exception + * Thrown when the field is not a list field. + */ + public function migrateListField(string $entityTypeId, string $fieldName): void { + $fieldStorageConfig = $this->configFactory->getEditable("field.storage.$entityTypeId.$fieldName"); + $sourceFieldType = $fieldStorageConfig->get('type'); + $targetFieldType = array_reverse(static::FIELD_TYPE_MAP)[$sourceFieldType] ?? NULL; + + if ($sourceFieldType === $targetFieldType) { + // Already migrated + return; + } + + if ($targetFieldType === NULL) { + throw new \Exception('Not a list field'); + } + + $this->doMigrateField( + entityTypeId: $entityTypeId, + fieldName: $fieldName, + fieldStorageConfig: $fieldStorageConfig, + sourceFieldType: $sourceFieldType, + targetFieldType: $targetFieldType, + targetModule: 'enum_field', + ); + } + + /** + * Migrates a field. + * + * @param string $entityTypeId + * The entity type ID. + * @param string $fieldName + * The field name. + * @param \Drupal\Core\Config\Config $fieldStorageConfig + * The field storage config. + * @param string $sourceFieldType + * The source field type. + * @param string $targetFieldType + * The target field type. + * @param string $targetModule + * The target module. + */ + protected function doMigrateField(string $entityTypeId, string $fieldName, Config $fieldStorageConfig, string $sourceFieldType, string $targetFieldType, string $targetModule): void { + // Update the field storage. + /** @var \Drupal\field\Entity\FieldStorageConfig[] $schemaDefinitions */ + $schemaDefinitions = $this->entityLastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($entityTypeId); + $schemaDefinitions[$fieldName]->set('type', $targetFieldType); + $this->entityLastInstalledSchemaRepository->setLastInstalledFieldStorageDefinitions($entityTypeId, $schemaDefinitions); + + $this->entityFieldManager->clearCachedFieldDefinitions(); + + $fieldStorageConfig->set('type', $targetFieldType); + $fieldStorageConfig->set('module', $targetModule); + $fieldStorageConfig->save(TRUE); + + FieldStorageConfig::loadByName($entityTypeId, $fieldName)->calculateDependencies()->save(); + + // Update the field instances. + $fieldMap = $this->entityFieldManager->getFieldMapByFieldType($sourceFieldType)[$entityTypeId][$fieldName]; + + foreach ($fieldMap['bundles'] as $bundle) { + $fieldConfig = $this->configFactory->getEditable("field.field.$entityTypeId.$bundle.$fieldName"); + $fieldConfig->set('field_type', $targetFieldType); + $fieldConfig->save(); + + /** @var \Drupal\field\FieldConfigInterface $fieldConfig */ + $fieldConfig = FieldConfig::loadByName($entityTypeId, $bundle, $fieldName); + $fieldConfig->calculateDependencies()->save(); + } + } + +}