Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #3314741: EntityFieldManager::getFieldMap() doesn't show farmOS bundle fields #583

Open
wants to merge 3 commits into
base: 2.x
Choose a base branch
from

Conversation

mstenta
Copy link
Member

@mstenta mstenta commented Oct 11, 2022

@mstenta
Copy link
Member Author

mstenta commented Oct 11, 2022

Note that this PR currently includes two commits that change the equipment and quick fields from bundle fields to base fields.

If we want to split those out to review separately that's fine with me.

foreach ($entity_type_definitions as $entity_type => $entity_type_definition) {

// Only proceed for entity types that use bundle plugins.
if (!in_array($entity_type, ['asset', 'log', 'plan', 'quantity'])) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm seeing this array in a lot of places - sometimes in different orders or with/without the 'data_stream' entry.

Would it make sense to refactor it into a constant - or maybe just additional attributes on the entity definitions themselves?

e.g. here;

if (!$entity_type_definition->supportsBundlePlugins()) {
    continue;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good idea! Didn't know about the supportsBundlePlugins() method!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could use EntityTypeInterface::getBundleEntityType()


  /**
   * Gets the name of the entity type which provides bundles.
   *
   * @return string|null
   *   The name of the entity type which provides bundles, or NULL if the entity
   *   type does not have a bundle entity type.
   */
  public function getBundleEntityType();

}

// Set the bundle field map key value collection.
if (!empty($bundle_field_map)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if it became empty as part of the changes above? Shouldn't it still get set in that case?

Comment on lines +12 to +71

// Iterate through entity types.
$entity_type_definitions = \Drupal::service('entity_type.manager')->getDefinitions();
foreach ($entity_type_definitions as $entity_type => $entity_type_definition) {

// Only proceed for entity types that use bundle plugins.
if (!in_array($entity_type, ['asset', 'log', 'plan', 'quantity'])) {
continue;
}

// Get the bundle field map key value collection.
$bundle_field_map = \Drupal::service('keyvalue')->get('entity.definitions.bundle_field_map')->get($entity_type) ?? [];

// Get a list of installed bundles for this entity type.
$bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo($entity_type);

// Iterate through bundles.
foreach ($bundles as $bundle => $bundle_info) {

// Invoke hook_farm_entity_bundle_field_info() on all modules with a
// callback function to add bundle fields to the entity field map.
\Drupal::service('module_handler')->invokeAllWith(
'farm_entity_bundle_field_info',
function (callable $hook) use ($entity_type_definition, $bundle, &$bundle_field_map) {

// Get bundle fields defined by the module.
$bundle_fields = $hook($entity_type_definition, $bundle) ?? [];

// If bundle fields are empty, bail.
if (empty($bundle_fields)) {
return;
}

// Iterate through the bundle field definitions to add fields to the
// bundle field map. This mimics the field_definition.listener
// service's onFieldDefinitionCreate() behavior.
// @see Drupal\Core\Field\FieldDefinitionListener::onFieldDefinitionCreate()
foreach ($bundle_fields as $field_name => $bundle_field) {
if (!isset($bundle_field_map[$field_name])) {
// This field did not exist yet, initialize it with the type and
// empty bundle list.
$bundle_field_map[$field_name] = [
'type' => $bundle_field->getType(),
'bundles' => [],
];
}
$bundle_field_map[$field_name]['bundles'][$bundle] = $bundle;
}
}
);
}

// Set the bundle field map key value collection.
if (!empty($bundle_field_map)) {
\Drupal::service('keyvalue')->get('entity.definitions.bundle_field_map')->set($entity_type, $bundle_field_map);
}
}

// Delete the entity field map cache entry.
\Drupal::service('cache.discovery')->delete('entity_field_map');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why implement this all again? Couldn't it just call _farm_entity_rebuild_bundle_field_map('install', $modules) with a list of all the modules that implement hook_farm_entity_bundle_field_info?

/**
* Add hook_farm_entity_bundle_field_info() fields to bundle field maps.
*/
function farm_entity_post_update_add_farm_entity_bundle_field_maps(&$sandbox = NULL) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this $sandbox parameter? Do we need to use/honor it?

Comment on lines +8 to +11
/**
* Add hook_farm_entity_bundle_field_info() fields to bundle field maps.
*/
function farm_entity_post_update_add_farm_entity_bundle_field_maps(&$sandbox = NULL) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably just my Drupal naivete showing here, but does this implement a hook or is it called some other way?

Comment on lines +62 to +103
/**
* Test that bundle field maps are updated on install/uninstall.
*/
public function testBundleFieldMapUpdates() {

// Get the entity field map.
$field_map = $this->entityFieldManager->getFieldMap();

// Confirm that the 'test_default_bundle_field' exists in the log field map.
$this->assertArrayHasKey('test_default_bundle_field', $field_map['log']);

// Confirm that the 'test_contrib_hook_bundle_field' does NOT exist (yet).
$this->assertArrayNotHasKey('test_contrib_hook_bundle_field', $field_map['log']);

// Install the farm_entity_contrib_test module.
$result = $this->moduleInstaller->install(['farm_entity_contrib_test']);
$this->assertTrue($result);

// Reload the entity field map.
$this->entityFieldManager->setFieldMap([]);
\Drupal::service('cache.discovery')->delete('entity_field_map');
$field_map = $this->entityFieldManager->getFieldMap();

// Confirm that the 'test_contrib_hook_bundle_field' exists in the log field
// map, and exists in the 'test' bundle, but not in 'test_override'.
$this->assertArrayHasKey('test_contrib_hook_bundle_field', $field_map['log']);
$this->assertContains('test', $field_map['log']['test_contrib_hook_bundle_field']['bundles']);
$this->assertNotContains('test_override', $field_map['log']['test_contrib_hook_bundle_field']['bundles']);

// Uninstall the farm_entity_contrib_test module.
$result = $this->moduleInstaller->uninstall(['farm_entity_contrib_test']);
$this->assertTrue($result);

// Reload the entity field map.
$this->entityFieldManager->setFieldMap([]);
\Drupal::service('cache.discovery')->delete('entity_field_map');
$field_map = $this->entityFieldManager->getFieldMap();

// Confirm that the 'test_contrib_hook_bundle_field' no longer exists in the
// log field map.
$this->assertArrayNotHasKey('test_contrib_hook_bundle_field', $field_map['log']);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also test the case where more than one module provides a given bundle field? i.e. that the bundle gets added, the field mapping doesn't get overwritten or prematurely deleted?

Comment on lines -97 to +98
$this->assertEquals('test', $storage['assets'][0]->quick[0]);
$this->assertEquals('test', $storage['assets'][0]->get('quick')[0]->value);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need to change?

Copy link
Member

@paul121 paul121 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to capture what we chatted about on the monthly call..

This reminded me of this issue which provides a solution for a problem I have run into in update hooks adding/removing a bundle field in a custom module: https://www.drupal.org/project/drupal/issues/3129179

So considering that, it would be nice if there was a helper function we could call at any time to rebuild this field map during update hooks. To simplify things this helper function could rebuild the entire mapping, regardless of modules installed/uninstalled.

This issue I referenced may provide a solution for Drupal core, but we may need to determine if it works for the farmOS bundle field hook. It sounds like providing our own helper method for farmOS core would be the easiest near term solution (and call this helper function to do this logic as is being done by this PR)

@mstenta
Copy link
Member Author

mstenta commented Dec 8, 2022

@paul121 raised an important question on the dev call today: if we convert the equipment field to a base field, will that stop it from showing up in Log Views?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants