diff -u b/src/Entity/MediaBundle.php b/src/Entity/MediaBundle.php --- b/src/Entity/MediaBundle.php +++ b/src/Entity/MediaBundle.php @@ -250,18 +250,19 @@ public function preSave(EntityStorageInterface $storage) { parent::preSave($storage); - // If the type plugin uses a source field, we'll need to store its name in - // our type configuration before saving. We'd need to double-save if we did - // this in postSave(). - $type_plugin = $this->getType(); - if ($type_plugin instanceof SourceFieldInterface) { - $storage = $type_plugin->getSourceField($this)->getFieldStorageDefinition(); + // If the handler uses a source field, we'll need to store its name before + // saving. We'd need to double-save if we did this in postSave(). + $handler = $this->getType(); + if ($handler instanceof SourceFieldInterface) { + $storage = $handler->getSourceField($this)->getFieldStorageDefinition(); // If the field storage is a new (unsaved) config entity, save it. if ($storage instanceof FieldStorageConfigInterface && $storage->isNew()) { $storage->save(); } - // Store the field name. - $configuration = $type_plugin->getConfiguration(); + // Store the field name. We always want to update this value because the + // field name may have changed, or a new field may have been created, + // depending on the user's actions or the handler's behavior. + $configuration = $handler->getConfiguration(); $configuration['source_field'] = $storage->getName(); $this->setTypeConfiguration($configuration); } @@ -273,12 +274,12 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { parent::postSave($storage, $update); - // If the type plugin is using a source field, we may need to save it if - // it's new. The field storage is guaranteed to exist already because - // preSave() took care of that. - $type_plugin = $this->getType(); - if ($type_plugin instanceof SourceFieldInterface) { - $field = $type_plugin->getSourceField($this); + // If the handler is using a source field, we may need to save it if it's + // new. The field storage is guaranteed to exist already because preSave() + // took care of that. + $handler = $this->getType(); + if ($handler instanceof SourceFieldInterface) { + $field = $handler->getSourceField($this); // If the field is new, save it and add it to this bundle's view and form // displays. diff -u b/src/MediaTypeBase.php b/src/MediaTypeBase.php --- b/src/MediaTypeBase.php +++ b/src/MediaTypeBase.php @@ -158,6 +158,7 @@ '#empty_option' => $this->t('- Create -'), '#empty_value' => NULL, '#options' => $options, + '#description' => $this->t('The field on media items of this type that will store the source information.'), ]; } return $form; @@ -184,9 +185,13 @@ * {@inheritdoc} */ public function getSourceField(MediaBundleInterface $bundle) { + // If we don't know the name of the source field, we definitely need to + // create it. if (empty($this->configuration['source_field'])) { return $this->createSourceField($bundle); } + // Even if we do know the name of the source field, there is no guarantee + // that it already exists. So check for the field and create it if needed. $field = $this->configuration['source_field']; $fields = $this->entityFieldManager->getFieldDefinitions('media', $bundle->id()); return isset($fields[$field]) ? $fields[$field] : $this->createSourceField($bundle); @@ -199,9 +204,13 @@ * The field storage definition. Will be unsaved if new. */ protected function getSourceFieldStorage() { + // If we don't know the name of the source field, we definitely need to + // create its storage. if (empty($this->configuration['source_field'])) { return $this->createSourceFieldStorage(); } + // Even if we do know the name of the source field, we cannot guarantee that + // its storage exists. So check for the storage and create it if needed. $field = $this->configuration['source_field']; $fields = $this->entityFieldManager->getFieldStorageDefinitions('media'); return isset($fields[$field]) ? $fields[$field] : $this->createSourceFieldStorage(); @@ -228,12 +237,17 @@ abstract protected function createSourceField(MediaBundleInterface $bundle); /** - * Determine a free field name to use as the default field. + * Determine the name of the source field. * * @return string - * An appropriate field name that was determined to be available. + * The source field name. If one is already stored in configuration, it is + * returned. Otherwise, a new, unused one is generated. */ protected function getSourceFieldName() { + if ($this->configuration['source_field']) { + return $this->configuration['source_field']; + } + $base_id = 'field_media_' . $this->getPluginId(); $tries = 0; $storage = $this->entityTypeManager->getStorage('field_storage_config'); diff -u b/tests/modules/media_entity_test_type/src/Plugin/MediaEntity/Type/TestType.php b/tests/modules/media_entity_test_type/src/Plugin/MediaEntity/Type/TestType.php --- b/tests/modules/media_entity_test_type/src/Plugin/MediaEntity/Type/TestType.php +++ b/tests/modules/media_entity_test_type/src/Plugin/MediaEntity/Type/TestType.php @@ -48,8 +48,6 @@ '#default_value' => empty($this->configuration['test_config_value']) ? NULL : $this->configuration['test_config_value'], ]; - $form['source_field']['#description'] = $this->t('Field on media entity that stores the source information.'); - return $form; } diff -u b/tests/src/Kernel/BasicCreationTest.php b/tests/src/Kernel/BasicCreationTest.php --- b/tests/src/Kernel/BasicCreationTest.php +++ b/tests/src/Kernel/BasicCreationTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\media_entity\Kernel; +use Drupal\field\Entity\FieldConfig; use Drupal\KernelTests\KernelTestBase; use Drupal\media_entity\Entity\Media; use Drupal\media_entity\Entity\MediaBundle; @@ -114,2 +115,49 @@ + /** + * Tests creating and updating bundles programmatically. + */ + public function testProgrammaticBundleManipulation() { + // Creating a bundle programmatically without specifying a source field + // should create one automagically. + /** @var FieldConfig $field */ + $field = $this->testBundle->getType()->getSourceField($this->testBundle); + $this->assertInstanceOf(FieldConfig::class, $field); + $this->assertEquals('field_media_generic', $field->getName()); + $this->assertFalse($field->isNew()); + + // Saving with a non-existent source field should create it. + $this->testBundle->setTypeConfiguration([ + 'source_field' => 'field_magick', + ]); + $this->testBundle->save(); + $field = $this->testBundle->getType()->getSourceField($this->testBundle); + $this->assertInstanceOf(FieldConfig::class, $field); + $this->assertEquals('field_magick', $field->getName()); + $this->assertFalse($field->isNew()); + + // Trying to save without a source field should create a new, de-duped one. + $this->testBundle->setTypeConfiguration([]); + $this->testBundle->save(); + $field = $this->testBundle->getType()->getSourceField($this->testBundle); + $this->assertInstanceOf(FieldConfig::class, $field); + $this->assertEquals('field_media_generic_1', $field->getName()); + $this->assertFalse($field->isNew()); + + // Trying to reuse an existing field should, well, reuse the existing field. + $this->testBundle->setTypeConfiguration([ + 'source_field' => 'field_magick', + ]); + $this->testBundle->save(); + $field = $this->testBundle->getType()->getSourceField($this->testBundle); + $this->assertInstanceOf(FieldConfig::class, $field); + $this->assertEquals('field_magick', $field->getName()); + $this->assertFalse($field->isNew()); + // No new de-duped fields should have been created. + $duplicates = FieldConfig::loadMultiple([ + 'media.' . $this->testBundle->id() . '.field_magick_1', + 'media.' . $this->testBundle->id() . '.field_media_generic_2', + ]); + $this->assertEmpty($duplicates); + } + }