Yet another 'cherry-pick' from the patch in #1847596: Remove Taxonomy term reference field in favor of Entity reference.

When an entity bundle is deleted, entity_reference needs to inspect all its instances that might be affected by that change and update them if needed.

Bug :
- Let bundle 'foo' be referenceable by field_ref
- Delete bundle 'foo', field_ref can still lists 'foo' as a referenceable bundle, but that is ignored since the bundle doesn't exist anymore
- A couple weeks later, create a new bundle with the same name
--> The new bundle is instantly referenceable by field_ref, because 'foo' is stil listed as a referenced bundle, while that was not intended by the site admin.

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Bug: bad housekeeping of the "referenceable bundles" setting in e_r fields
Issue priority Major : blocks #2412569: Allow setting the auto-create bundle on entity reference fields with multiple target bundles, which requires schema/config updates
Disruption None
CommentFileSizeAuthor
#104 interdiff.txt995 bytesyched
#104 1978714-104.patch21.75 KByched
#102 1978714-102.patch21.75 KBjibran
#102 interdiff.txt2 KBjibran
#97 1978714-97.patch21.75 KBamateescu
#89 interdiff.txt899 bytesamateescu
#89 1978714-89.patch23.63 KBamateescu
#86 interdiff.txt948 bytesamateescu
#86 1978714-86.patch23.1 KBamateescu
#83 interdiff.txt11.44 KBamateescu
#83 1978714-83.patch23.05 KBamateescu
#74 interdiff.txt2.52 KBamateescu
#74 1978714-74.patch22.01 KBamateescu
#73 interdiff.txt5.92 KBamateescu
#73 1978714-73.patch20.73 KBamateescu
#70 interdiff.txt11.3 KBamateescu
#70 1978714-70.patch16.3 KBamateescu
#63 interdiff.txt12.89 KBclaudiu.cristea
#63 1978714-63.patch11.18 KBclaudiu.cristea
#46 interdiff.txt11.46 KBclaudiu.cristea
#46 entity_reference-1978714-46.patch12.93 KBclaudiu.cristea
#39 interdiff.txt3.39 KBclaudiu.cristea
#39 entity_reference-1978714-39.patch8.65 KBclaudiu.cristea
#35 interdiff.txt1.11 KBclaudiu.cristea
#35 entity_reference-1978714-35.patch8.28 KBclaudiu.cristea
#34 interdiff.txt3.09 KBclaudiu.cristea
#34 entity_reference-1978714-34.patch8.21 KBclaudiu.cristea
#33 entity_reference-1978714-33.patch8.61 KBclaudiu.cristea
#29 1978714-29.patch6.25 KBfilijonka
#25 interdiff.txt5.04 KBamateescu
#25 1978714-25.patch6.5 KBamateescu
#24 interdiff.txt1.63 KBpfrenssen
#24 1978714-24.patch6.17 KBpfrenssen
#22 interdiff.txt2.1 KBpfrenssen
#22 1978714-21.patch5.86 KBpfrenssen
#19 interdiff.txt5.84 KBpfrenssen
#19 1978714-19.patch5.48 KBpfrenssen
#13 1978714-13.patch7.19 KBamateescu
#13 interdiff.txt2.12 KBamateescu
#11 1978714-11.patch7.05 KBamateescu
#11 interdiff.txt3.02 KBamateescu
#9 1978714-9.patch7.16 KBamateescu
#9 interdiff.txt1.92 KBamateescu
#7 1978714-7.patch8.47 KBpfrenssen
#6 1978714-6.patch8.5 KBamateescu
#6 interdiff.txt1.02 KBamateescu
#3 1978714-3.patch8.56 KBamateescu
#3 interdiff.txt2.71 KBamateescu
#1 1978714.patch8.38 KBamateescu
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

amateescu’s picture

Status: Active » Needs review
FileSize
8.38 KB

Here we go.

dawehner’s picture

+++ b/core/modules/entity_reference/entity_reference.moduleundefined
@@ -470,3 +470,71 @@ function entity_reference_create_instance($entity_type, $bundle, $field_name, $f
+    foreach ($instance['settings']['handler_settings']['target_bundles'] as $delta => $target_bundle) {

Just in general I'm wondering whether it should be converted on the longrun to key/value with the target bundle. This for example results in a better diff at the end.

+++ b/core/modules/entity_reference/entity_reference.moduleundefined
@@ -470,3 +470,71 @@ function entity_reference_create_instance($entity_type, $bundle, $field_name, $f
+      if (empty($instance['settings']['handler_settings']['target_bundles'])) {
+        $instance->delete();
+      }

It feels wrong to delete the full instance, as it still somehow contains data.

+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceFieldTest.phpundefined
@@ -0,0 +1,161 @@
+    $this->field = field_info_field($this->fieldName);
+    $this->instance = field_info_instance($this->entityType, $this->fieldName, $this->bundle);

We could use Field::fieldInfo()->getField directly

+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceFieldTest.phpundefined
@@ -0,0 +1,161 @@
+  function testEntityReferenceFieldValidation() {
...
+  function testEntityReferenceFieldChangeMachineName() {

should be public ...

+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceFieldTest.phpundefined
@@ -0,0 +1,161 @@
+    $bad_referenced_entity = entity_create($this->referencedEntityType, array('type' => drupal_strtolower($this->randomName())));
+    $bad_referenced_entity->save();
+
+    $entity = entity_create($this->entityType, array('type' => $this->bundle));
+    $entity->{$this->fieldName}->target_id = $bad_referenced_entity->id();
+    try {
+      field_attach_validate($entity->getBCEntity());
+      $this->fail('Wrong reference causes validation error.');
+    }
+    catch (FieldValidationException $e) {
+      $this->pass('Wrong reference causes validation error.');
+    }

Nice test coverage. Maybe add a short sentence to the top of the two groups to explain the idea of the test.

amateescu’s picture

FileSize
2.71 KB
8.56 KB

Just in general I'm wondering whether it should be converted on the longrun to key/value with the target bundle. This for example results in a better diff at the end.

I'm not really sure if that buys us anything, but I'm certainly open to discuss it in a separate issue :)

It feels wrong to delete the full instance, as it still somehow contains data.

taxonomy.module does the same thing (in a different place though): http://drupalcode.org/project/drupal.git/blob/refs/heads/8.x:/core/modul...

Fixed all the rest.

Dave Reid’s picture

taxonomy.module does the same thing (in a different place though):

Because we know that when you delete a taxonomy vocabulary, it deletes all of the containing entities. This is not the case for other entity types/bundles. When you delete a node type, it does not delete the nodes of that type. I agree that auto-removing the field does seem out of place. Maybe it should be only limited if the entity type target is taxonomy terms.

amateescu’s picture

I agree that auto-removing the field does seem out of place.

Well, the thing is that we're removing only the instance (not the field like taxonomy.module), and only if that instance is set to reference just the bundle that was removed.

amateescu’s picture

FileSize
1.02 KB
8.5 KB

On second thought, let's just remove that part from this patch so we can move on with this necessary cleanup.

pfrenssen’s picture

FileSize
8.47 KB

Rerolled

Status: Needs review » Needs work

The last submitted patch, 1978714-7.patch, failed testing.

amateescu’s picture

Status: Needs work » Needs review
FileSize
1.92 KB
7.16 KB

Removed the part of the patch that wasn't actually testing the bug we're fixing here. Tests for field validation should be added in #2015701: Convert field type to typed data plugin for entity reference module .

dawehner’s picture

@@ -0,0 +1,132 @@
+    $test_bundle_2 = drupal_strtolower($this->randomName());
...
+    $test_bundle_2_new = drupal_strtolower($this->randomName());

It would be cool to use Unicode::strtolower() instead.

@@ -0,0 +1,132 @@
+    entity_test_create_bundle($test_bundle_2, NULL, $this->referencedEntityType);

lol, I had a bit of grime while reviewing exactly over the ',' so it looked like a ';'

@@ -0,0 +1,132 @@
+      $test_bundle_2,
+      $test_bundle_2,

It is a bit odd to target the same bundle twice?

@@ -0,0 +1,132 @@
+    $this->assertEqual(count($target_bundles[0]), 1, "The 'target_bundles' setting contains only one element.");

count of a non array for sure returns 1 ... it should be count($target_bundles) instead. Why do we not just use $this->assertEqual(array($this->bundle)) (potentially also before)

amateescu’s picture

FileSize
3.02 KB
7.05 KB

Thanks for reviewing!

It would be cool to use Unicode::strtolower() instead.

Fixed.

It is a bit odd to target the same bundle twice?

It is indeed, removed the second one.

And fixed that last assertion as well, doh! :) No real preference about asserting a count or array equality..

Status: Needs review » Needs work

The last submitted patch, 1978714-11.patch, failed testing.

amateescu’s picture

Status: Needs work » Needs review
FileSize
2.12 KB
7.19 KB

Using the latest field api standards should work better.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

This looks great now.

tstoeckler’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/modules/entity_reference/entity_reference.module
    @@ -424,3 +424,68 @@ function entity_reference_create_instance($entity_type, $bundle, $field_name, $f
    +  // Iterate over all entity reference fields referencing the given entity type
    +  // and delete those which target only the bundle that was deleted.
    ...
    +    if ($modified_instance) {
    ...
    +      // Update the instance definition with the new target bundles.
    +      $instance->save();
    +    }
    

    The comment does not match the code, but IMO the comment does actually make sense. The current code makes the instance reference *all* bundles, if I'm not mistaken.

    I just read that this was discussed above, but I'm not sure the behavior above was taken into account. I think if you have a field instance set up to specifically reference a certain entity bundle, having that instance reference all bundles of that entity types is a huge WTF. Ideally we would have the instance reference *no bundles at all*, so that it does not show up on the edit form at all, but then lets the administrator decide whether he wants the field to reference a different bundle or if he wants to delete the field.

  2. +++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceFieldTest.php
    @@ -0,0 +1,134 @@
    +    $this->assertEqual($target_bundles[1], $this->bundle, 'Index 2: Machine name was left untouched.');
    

    Should be "Index 1"

    Also might make sense to add a check for count() as well, here.

I would have re-rolled for the test changes, but I didn't know what to do about the first item.

amateescu’s picture

The problem with the first item is, as always, how to present this in the UI. In code, we can look for all form modes that use that instance and set it to "hidden", but not telling the user about this would be a problem as big as letting that instance in place with the ability to reference all bundles.

tstoeckler’s picture

OK, so should we simply disallow deleting the bundle then? That would be a pretty major change but it really sounds like that's what would be the ideal solution in this case.

amateescu’s picture

Yeah, that would be pretty intrusive. Maybe an answer to #16 is just do a d_s_m() and tell the user what happened?

pfrenssen’s picture

Issue summary: View changes
Status: Needs work » Needs review
FileSize
5.48 KB
5.84 KB

Rerolled the patch to bring it inline with the latest changes. Setting to needs review for the bot. This still needs work as per the above.

pfrenssen’s picture

I agree with #18, just show a message to the user. It will mainly be site builders that are going to delete bundles, and they will know how to update the reference fields.

amateescu’s picture

Yep, I think that'll be enough. Should we tag this issue with "usability" or something to get more opinions?

And a small self-review of my original patch / your interdiff:

+++ a/core/modules/entity_reference/entity_reference.module
@@ -238,21 +238,21 @@ function entity_reference_create_instance($entity_type, $bundle, $field_name, $f
-  foreach (field_read_instances() as $instance) {
+  foreach (entity_load_multiple('field_instance') as $instance) {

We really should do better here than getting all field instances. We can use Field::fieldInfo()->getFieldMap() and iterate on it to get only 'entity_reference' fields. Also, we need to make sure we're not trying to do anything for base fields, which have the same 'entity_reference' field type afaik.

pfrenssen’s picture

FileSize
5.86 KB
2.1 KB

Updated the patch with the remarks from #15, and provided the message.

I noticed that when a node bundle is deleted it still appears in the list of the entity reference field. This is because the cache of node_type_get_names() is not cleared when a node type is deleted. Created an issue for this: #2169531: Invalidate node_type_get_names() cache when deleting a node type.

pfrenssen’s picture

Assigned: Unassigned » pfrenssen
Status: Needs review » Needs work

@amateescu, I missed your post from #21.

I'll try implementing your idea. If you want to tag this for some more input, sure!

pfrenssen’s picture

Assigned: pfrenssen » Unassigned
Status: Needs work » Needs review
FileSize
6.17 KB
1.63 KB

Now only loads the entity_reference field instances, instead of all of them.

amateescu’s picture

FileSize
6.5 KB
5.04 KB

Thanks @pfrenssen! Started a review of the latest patch but after finishing it I realised that I wrote almost all the code that needs to be updated, so attaching a new patch instead :)

pfrenssen’s picture

Lol seems like I forgot to update entity_reference_entity_bundle_delete(). Thanks for catching it!!

There's some duplicated code between update entity_reference_entity_bundle_delete() and update entity_reference_entity_bundle_rename() now - the discovery of the entity_reference field instances from the field map. Shall we factor that out into a separate function?

amateescu’s picture

Since there's only two implementations of that code atm, I'd rather not.

Instead, we should add this 'feature' to Field API itself: something like "Give me all the fields/instances across all entity types and bundles for the X field type" (with 'include_base_fields' as a boolean param) because this is a very common need.

The last submitted patch, 25: 1978714-25.patch, failed testing.

filijonka’s picture

FileSize
6.25 KB

Made a reroll for this; bit unsure cause by some reason the test in previous patch wasn't merged so added it manually.

amateescu’s picture

This patch needs more a rewrite than a reroll :)

filijonka’s picture

yeah I figured that by the comments but

1. I needed to try to a reroll on a patch involving psr-0 to -4
2. It's often easier to work form a green patch then from a red one.
3. nothing have happened in this since january so figured it needed some attention.

Status: Needs review » Needs work

The last submitted patch, 29: 1978714-29.patch, failed testing.

claudiu.cristea’s picture

Assigned: Unassigned » claudiu.cristea
Status: Needs work » Needs review
FileSize
8.61 KB

Completely rewritten, so no interdiff.

claudiu.cristea’s picture

FileSize
8.21 KB
3.09 KB

The test is more compact now.

claudiu.cristea’s picture

amateescu’s picture

Assigned: claudiu.cristea » yched
Issue tags: +Entity Field API

I think the EntityManager code from the current patch is implementing my idea from #27, but it's hardcoding the new method to ER fields :/ I still think such a method can be useful but it should receive the field type as an argument.

Let's see if @Berdir and/or @yched have some thoughts about this :)

claudiu.cristea’s picture

@amateescu, I had no idea where to place it. I saw it sitting in EntityManager next to ::getFieldMap() or ::getFieldMapByFieldType() or other FiledInfo methods. And yes, it hardcodes ER because right now I cannot imagine that this can be used to other fields.

amateescu’s picture

In case we don't want to expand it with a $field_type argument, that means it has to live somewhere outside EntityManager :P

claudiu.cristea’s picture

FileSize
8.65 KB
3.39 KB

OK. Here with $field_type but I still think that maybe we need to filter the whole list of fields, check if their field type plugin class descends from entity reference.

jibran’s picture

Status: Needs review » Needs work
+++ b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
@@ -143,6 +143,22 @@ public function getFieldMap();
+  public function getFieldsReferencing($field_type, $target_type, $target_bundle);

This method is specific to ER so it doesn't make sense to have it in EMI. How would DER going to override it? It's a helper method in a wrong class imo. We should move it to some other class or ER.module file.

yched’s picture

+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -30,6 +30,7 @@
+use Drupal\field\Entity\FieldConfig;

Ouch - that's really unfortunate, we tried hard to remove dependencies of Core to field.module :-/.
Couldn't getFieldsReferencing() use $this->getFieldDefinitions() instead, and entity_reference_sync_fields() would then limit itself to configurable fields ?

Or, agreed with @jibran that putting ER-specific code in EntityManager feels a little off.

Also, what's the plan once e_r.module is gone ?

amateescu’s picture

Ouch, I didn't realize the new getFieldsReferencing() is so specific to the internal structure of ER settings (i.e. it even filter by a specified target bundle) :( Now that I actually read its code, I definitely think it shouldn't live in EntityManager.

How about this then:
- we move it somewhere inside field.module
- we drop the $target_bundle argument and let entity_reference_sync_fields() do the bundle filtering
- we accept an array of target entity types
- we take into account that $storage->settings['target_type'] might be an array, which, even if that's not the case for the core ER field, it should make this helper useful to DER as well (I hope?)

Thoughts? :)

jibran’s picture

I think moving it out of EntityManager is enough for DER. If we are making it specific for ER field then core shouldn't worry about contrib. As long as it's not in the EntityManager DER can always implements it's own hooks.

amateescu’s picture

andypost’s picture

+++ b/core/modules/entity_reference/src/Tests/EntityReferenceSettingsTest.php
@@ -0,0 +1,96 @@
+  public static $modules = ['node', 'taxonomy', 'field', 'user', 'text', 'entity_reference'];

This test should live in taxonomy module

claudiu.cristea’s picture

I know, I know... we still have to deal with auto_create_bundle that is coming only in #2412569: Allow setting the auto-create bundle on entity reference fields with multiple target bundles but let's have some feedback here.

EDIT: Because no one liked to add that field finder to Entity Manager, I created a new service that can accept additional methods in the interface in the future. I really see no place where to move that.

jibran’s picture

Well it's a shame we don't fire an event when a bundle is renamed this all can live in a one class.

yched’s picture

Not sure if we really need a service for the "find field definitions referencing $entity_type + $bundle", but its current name & doc are a bit cryptic / misleading :

+/**
+ * Interface for entity reference finder.
+ */
+interface EntityReferenceFinderInterface {

I'd more expect that to be something that finds references (like, find entities referencing some other entity, aka backreference) ?

amateescu’s picture

That new method/service would probably make more sense as part of one the new services that will be created by this issue: #2337191: Split up EntityManager into many services

claudiu.cristea’s picture

Few comments:

yched’s picture

That new method/service would probably make more sense as part of one the new services that will be created by this issue: #2337191: Split up EntityManager into many services

+1

claudiu.cristea’s picture

@amateescu, @yched, I'm OK with #49 but right now that is NR for 1 month and it's not a simple issue. We can expect some time till will go in. In the meantime it would be good if we can make some steps forward with this issue. So, let's move on with this and put back that 'finder' in EntityManager. If #2337191: Split up EntityManager into many services will be in first, then we'll update this. Otherwise that will take care to move this method in a new class.

What you think?

yched’s picture

Mhyeah, I guess you're right ... :-)

Other remarks then :
- The logic in getFieldsReferencing() should leverage the field map (EntityManager::getFieldMap()) rather than loading all field storage config on earth :-)
- There's a question of "do we want all fields including base fields" or "only configurable fields" ?
The specific use case here only wants to update configurable fields, but it would be strange for a "generic getFieldsReferencing() method" to omit base fields ? (As a side note, the field map will get you all fields)
- Not sure statically caching (especially without a way to clear the cache) is a good thing. The field map is already statically cached.

claudiu.cristea’s picture

@yched, thank you. Now re-reading older comments I found @jibran's comment #40. He, and also @amateescu, are saying that this is too ER specific to live in EntityManager (or its successors). And I'm tempted to agree. So #49 conflicts with #40.

I still think that we need a ER method (but where?) and since ER will move in core, we can add the new method directly in core. Course we include observations from #53.

Out of ideas :(

yched’s picture

Well, if a "FieldDefinitionsManager" was split off from EntityManager, then a method in there to find ER fields pointing to some entity-type + bundle could be OK IMO, ER fields are an important, structuring field type provided by Core.

Adding it in the already much crowded EntityManager is more debatable, true.

For the time being, do we really need to put that code in a reusable place ? Looks like the patch only calls the method once ?

claudiu.cristea’s picture

...do we really need to put that code in a reusable place ? Looks like the patch only calls the method once ?

We can make it a procedural helper in ER and in #2429191: Deprecate entity_reference.module and move its functionality to core we'll move it to system module (grrr, how ugly!)

claudiu.cristea’s picture

Or...

If #2553169: Dispatch bundle CRUD events would go in, we just add the event subscriber and make the 'finder' a protected method in that subscriber.

claudiu.cristea’s picture

@yched,

I'm very tempted by #57 because is not forcing us to choose an unnatural home for getFieldsReferencing(). The event subscriber would be a good place for that. But then #2553169: Dispatch bundle CRUD events needs a review and commit. Is there a chance for that?

claudiu.cristea’s picture

Issue tags: +ER check for RC

Tagging.

claudiu.cristea’s picture

Tagging.

claudiu.cristea’s picture

Closed #2571187: EntityReferenceItem should add the target_bundles as config dependencies in favor of this. And yes, we must handle the bundle entity removal through config dependencies.

amateescu’s picture

Status: Needs review » Needs work

@claudiu.cristea, in order to move forward here, let's just create a procedural function in e_r.module, then we can move it to a better place either in #2337191: Split up EntityManager into many services or #2429191: Deprecate entity_reference.module and move its functionality to core.

#53 still needs to be addressed so putting at NW for that.

claudiu.cristea’s picture

Status: Needs work » Needs review
FileSize
11.18 KB
12.89 KB

This includes #53, #61, #62.

yched’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
    @@ -284,9 +284,12 @@ public function hasNewEntity() {
    +    $manager = \Drupal::entityManager();
    +    $target_entity_type = $manager->getDefinition($field_definition->getFieldStorageDefinition()->getSetting('target_type'));
    

    Clarity nitpick : s/$manager/$entity_manager ?
    Not clear what the "manager" is when it's used 10 lines below ;-)

  2. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
    @@ -298,6 +301,19 @@ public static function calculateDependencies(FieldDefinitionInterface $field_def
    +    $handler = $field_definition->getSetting('handler_settings');
    

    Clarity : s/$handler/$handler_settings ?
    Sorry about those, but calling it a "handler" is kind of a lie :-)

  3. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
    @@ -305,12 +321,14 @@ public static function calculateDependencies(FieldDefinitionInterface $field_def
    +    $manager = \Drupal::entityManager();
    

    $entity_manager

  4. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
    @@ -322,6 +340,25 @@ public static function onDependencyRemoval(FieldDefinitionInterface $field_defin
    +    $handler = $field_definition->getSetting('handler_settings');
    

    $handler_settings

  5. +++ b/core/modules/entity_reference/entity_reference.module
    @@ -211,3 +210,40 @@ function entity_reference_query_entity_reference_alter(AlterableInterface $query
    +function entity_reference_entity_bundle_rename($entity_type, $bundle_old, $bundle_new) {
    

    Minor : the function could use a couple inline comments, 30 lines of code without a comment is usually not good :-)

  6. +++ b/core/modules/entity_reference/entity_reference.module
    @@ -211,3 +210,40 @@ function entity_reference_query_entity_reference_alter(AlterableInterface $query
    +  foreach (FieldConfig::loadMultiple($ids) as $config) {
    

    Nitpick : s/$config/$field_config for clarity ?

  7. +++ b/core/modules/entity_reference/entity_reference.module
    @@ -211,3 +210,40 @@ function entity_reference_query_entity_reference_alter(AlterableInterface $query
    +      $bundles = !empty($handler['target_bundles']) ? array_keys($handler['target_bundles']) : [];
    +      foreach ($bundles as $bundle) {
    +        if ($bundle == $bundle_old) {
    +          $handler['target_bundles'][$bundle_new] = $bundle_new;
    +          unset($handler['target_bundles'][$bundle_old]);
    +          $config->setSetting('handler_settings', $handler);
    +          $config->save();
    +          break;
    +        }
    +      }
    

    Would be simpler as :

    if (isset($handler['target_bundles'][$bundle_old]) {
      unset($handler['target_bundles'][$bundle_old])
      $handler['target_bundles'][$bundle_new] = $bundle_new;
      $config->setSetting(...)->save();
    }
    

    ?

  8. +++ b/core/modules/entity_reference/entity_reference.module
    @@ -211,3 +210,40 @@ function entity_reference_query_entity_reference_alter(AlterableInterface $query
    +  // Release memory.
    +  unset($map, $ids);
    

    Is that really needed ? The memory should be freeable by GC the moment we leave the function and the variables get out of scope ?

    I don't think we do similar things (unset vars used in the function before leaving it) in any other place ?

  9. +++ b/core/modules/entity_reference/src/Tests/EntityReferenceSettingsTest.php
    @@ -0,0 +1,96 @@
    +  public function testSyncTargetBundleChanges() {
    

    Nitpick : the "Sync" part refers to a function name that no longer exists, right ?
    Why not just testTargetBundleChanges ?

yched’s picture

Also : that is an awful lot of non-trivial code to repeat for anything that stores "a list of bundles for which some feature is enabled", which seems like a fairly common pattern :-/

Not for this issue to enhance, though...

yched’s picture

Forgot : EntityReferenceItem::onDependencyRemoval() could use a couple inline comments (mirroring the ones delimiting code sections in calculateDependencies())

jibran’s picture

yched’s picture

@jibran : see over there - that issue got fixed meanwhile :)

amateescu’s picture

Assigned: yched » amateescu

Working on this.

amateescu’s picture

Status: Needs work » Needs review
FileSize
16.3 KB
11.3 KB

Fixes #64 and also brings back a very important part from the initial patches where we also react to bundles being deleted.

claudiu.cristea’s picture

+++ b/core/modules/entity_reference/entity_reference.module
@@ -228,22 +226,57 @@ function entity_reference_entity_bundle_rename($entity_type, $bundle_old, $bundl
+/**
+ * Implements hook_entity_bundle_delete().
+ */
+function entity_reference_entity_bundle_delete($entity_type_id, $bundle) {

This is useless. We handle this now in onDependencyRemoval(). See the tests passing in my latest patch without this hook implementation :)

Status: Needs review » Needs work

The last submitted patch, 70: 1978714-70.patch, failed testing.

amateescu’s picture

Status: Needs work » Needs review
FileSize
20.73 KB
5.92 KB

This is useless. We handle this now in onDependencyRemoval(). See the tests passing in my latest patch without this hook implementation :)

It is not useless at all :) We also need to take into account bundles that are not provided by a config entity type and therefore are not config dependencies.

amateescu’s picture

Putting the reasoning from above in the code so it's clear for anyone else looking at it in the future. And we also have to log the problem when a config entity bundle is removed.

The last submitted patch, 70: 1978714-70.patch, failed testing.

The last submitted patch, 73: 1978714-73.patch, failed testing.

jibran’s picture

Patch looks solid now.

  1. +++ b/core/modules/taxonomy/src/Entity/Vocabulary.php
    @@ -127,40 +127,6 @@ public function getDescription() {
       public function postSave(EntityStorageInterface $storage, $update = TRUE) {
    

    <3 this clean up.

  2. +++ b/core/modules/entity_reference/entity_reference.module
    @@ -211,3 +210,78 @@ function entity_reference_query_entity_reference_alter(AlterableInterface $query
    + * Implements hook_entity_bundle_rename().
    

    I get it that I have to update DERItem like ERItem. But do I have to add this hook to DER as well?

  3. +++ b/core/modules/entity_reference/entity_reference.module
    @@ -211,3 +210,78 @@ function entity_reference_query_entity_reference_alter(AlterableInterface $query
    +        if ($handler_settings['target_bundles'] === []) {
    

    Can't this also happen in entity_reference_entity_bundle_rename?

Thanks @yched for closing the issue as duplicate.

claudiu.cristea’s picture

Issue summary: View changes

It is not useless at all :) We also need to take into account bundles that are not provided by a config entity type and therefore are not config dependencies.

hook_entity_bundle_delete() is invoked only by bundles based on config entities. At least in core. If a module wants to provide a bundle that is not a configurable, we needs to provide a mechanism for keep in sync entities.

yched’s picture

@claudiu.cristea : Yeah, we discussed that with @amateescu yesterday.

If an entity type has non-config bundles, then they are most probably hardcoded in code somewhere. If one of them is removed from the code, then the corresponding module should call hook_entity_bundle_delete() in a hook_post_update_N()

I hate the code duplication between onDependencyRemoval() and entity_reference_entity_bundle_delete(), but we couldn't find a way around that.

@jibran re #77.3 : I don't think that can happen, a rename cannot cause the set of bundles to become 0.

yched’s picture

  1. +++ b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/SelectionBase.php
    @@ -207,7 +210,14 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
    +    // Normalize the value of the 'target_bundles' setting. In case it is an
    +    // empty array we need to change it to NULL in order to allow the field to
    +    // reference all bundles of the target entity type.
    

    Nitpick : we could clarify that this is about adjusting the UI behavior.

    Suggestion :
    "If no checkboxes were checked for 'target_bundles', store NULL ("all bundles are referenceable") rather than empty array ("no bundle is referenceable" - typically happens when all referenceable bundles have been deleted).é

  2. +++ b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/SelectionBase.php
    @@ -326,6 +336,15 @@ protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS')
    +
    +    // If the 'target_bundles' setting is an empty array, force the query to
    +    // never return anything and bail out early.
    +    if (isset($handler_settings['target_bundles']) && $handler_settings['target_bundles'] === []) {
    +      $query->condition($entity_type->getKey('id'), NULL, '=');
    +
    +      return $query;
    +    }
    +
         if (!empty($handler_settings['target_bundles'])) {
           $query->condition($entity_type->getKey('bundle'), $handler_settings['target_bundles'], 'IN');
         }
    

    That works, but the NULL case could be a bit more explicit, for clarity.

    Maybe :

    // If 'target_bundles' is NULL, all bundles are referenceable, no further conditions are needed.
    if (!is_null($handler_settings['target_bundles']) {
      // If 'target_bundles' is an empty array, no bundle is referencable, force the query to    // never return anything and bail out early.
      if ($handler_settings['target_bundles'] === []) {
      ... add unfullfillable condition ...
    }
    else {
      ... add condition on bundles...
    }
    

    ?

  3. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
    @@ -305,12 +321,14 @@ public static function calculateDependencies(FieldDefinitionInterface $field_def
       public static function onDependencyRemoval(FieldDefinitionInterface $field_definition, array $dependencies) {
    

    Can we add short inline comments delimiting the two parts ("default values" & "target bundles"), like calculateDependencies() does ?

  4. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
    @@ -322,6 +340,39 @@ public static function onDependencyRemoval(FieldDefinitionInterface $field_defin
    +              // In case we deleted the only target bundle allowed by the field we
    +              // have to log a warning message because the field will not function
    +              // correctly anymore.
    

    copy/paste breaks the 80 chars limit ;-)

    Also, nitpick, but "warning" is an actual log level, and that's not the one we use here. So maybe just "Log a message if the field no longer has any referenceable bundle" ?

jibran’s picture

Status: Needs review » Needs work

Thanks for the explanation @yched.

+++ b/core/modules/entity_reference/entity_reference.module
@@ -211,3 +210,78 @@ function entity_reference_query_entity_reference_alter(AlterableInterface $query
+ * Implements hook_entity_bundle_rename().
...
+ * Implements hook_entity_bundle_delete().
+ *
+ * We are duplicating the work done by
+ * \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::onDependencyRemoval()
+ * because we need to take into account bundles that are not provided by a
+ * config entity type so they are not part of the config dependencies.

Can we add a test for non config entity types?

yched’s picture

Hm, so FWIW, #2172843: Remove ability to update entity bundle machine names just got bumped to critical :-)

amateescu’s picture

Status: Needs work » Needs review
Related issues: +#2172843: Remove ability to update entity bundle machine names
FileSize
23.05 KB
11.44 KB

Ok, since now we're sure that we won't allow bundle renames, let's take that part out of the patch. Also fixed #80 and added the test requested in #81.

amateescu’s picture

Title: Entity reference doesn't update its instance settings when referenced entity bundles are renamed or deleted » Entity reference doesn't update its field settings when referenced entity bundles are deleted

Clarifying the title.

Status: Needs review » Needs work

The last submitted patch, 83: 1978714-83.patch, failed testing.

amateescu’s picture

Status: Needs work » Needs review
FileSize
23.1 KB
948 bytes

Oops.

The last submitted patch, 83: 1978714-83.patch, failed testing.

yched’s picture

@amateescu: Thanks !

Now that #2064191: Fix the 'target_bundle' field setting - 'target_bundles' are not validated when entity_reference is enabled got in, we probably want to adjust the code that it added in ERItem::getConstraints(), for the NULL / [] difference added in this patch here.

(if NULL, we don't want to add any Bundle constraint. If [], the Bundle constraint should be fine, since the in_array() check its validator does will always fail)

amateescu’s picture

FileSize
23.63 KB
899 bytes

That's a very good point. Fixed :)

yched’s picture

Status: Needs review » Reviewed & tested by the community

Yay ! RTBC if green :-)

jibran’s picture

RTBC +1

The last submitted patch, 86: 1978714-86.patch, failed testing.

yched’s picture

Gee, those testbot fail notifications on old patches are painful :-/

pfrenssen’s picture

Good job, I like that the potentially confusing difference between NULL and an empty array is spelled out very clearly in the documentation every time it is used.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 89: 1978714-89.patch, failed testing.

jibran’s picture

Issue tags: +Needs reroll
amateescu’s picture

Status: Needs work » Reviewed & tested by the community
Issue tags: -Needs reroll
FileSize
21.75 KB

Here we go.

jibran’s picture

Assigned: amateescu » alexpott

Let's assign the issue to @alexpott for the review of config dependencies part of the patch.

alexpott’s picture

Issue tags: -rc target +rc deadline
alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs issue summary update
  1. The patch generally looks good - I think the issue summary could do with fleshing out why this is important to get done before the release candidate.
  2. +++ b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/SelectionBase.php
    @@ -326,8 +337,20 @@ protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS')
    +      // If 'target_bundles' is an empty array, no bundle is referencable, force
    

    referenceable to be consistent with the rest of core.

  3. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
    @@ -321,12 +337,15 @@ public static function calculateDependencies(FieldDefinitionInterface $field_def
    +    $changed = parent::calculateDependencies($field_definition, $dependencies);
    

    I think this should be parent::onDependencyRemoval().

yched’s picture

Issue summary: View changes

@alexpott : Now that bundle renames are out of the equation, I'd think the patch on itself is not critical to get before RC1, and could go in as a bugfix after that.
But it's blocking #2412569: Allow setting the auto-create bundle on entity reference fields with multiple target bundles, which would really need to get in before RC since it implies config/schema changes.

Added that to the IS

jibran’s picture

claudiu.cristea’s picture

+++ b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/SelectionBase.php
@@ -326,8 +337,20 @@ protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS')
+    if (isset($handler_settings['target_bundles']) && !is_null($handler_settings['target_bundles'])) {

This is somehow superfluous. If $handler_settings['target_bundles'] is NULL both expressions are FALSE, so the second doesn't make any sense -- isset() should be enough. Maybe we can test if that is an array?

if (isset($handler_settings['target_bundles']) && is_array($handler_settings['target_bundles'])) {
  ...
  if (empty($handler_settings['target_bundles'])) {
    ...
yched’s picture

@jibran

IS still needs an update as per #100.

I did that in #101, but forgot to remove the tag.

@claudiu.cristea :
Correct. This should fix it.

jibran’s picture

Status: Needs review » Reviewed & tested by the community

Back to RTBC as #100 got addressed.

PS: I only made a doc fix and a typo fix so I think I'm eligible to RTBC it.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

This is a good bug fix to make before the rc deadline and it has test coverage. Sites will be more robust after a bundle delete if they use entity reference fields.

I was wondering about the upgrade path - but I think sites that are in this state will already have fixed their problematic er field or it will have broken them - so I think it is okay for this to not have an upgrade path.

Committed fa0af0b and pushed to 8.0.x. Thanks!

I discussed this with @amateescu and @yched in Barcelona therefore adding myself to the issue credit list.

  • alexpott committed fa0af0b on 8.0.x
    Issue #1978714 by amateescu, claudiu.cristea, pfrenssen, yched, jibran,...
yched’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.