Problem/Motivation
EntityManager::getAllBundleInfo()
returns bundle information for the node entity type even when there are no node types. This incorrect information leads to bugs in the Views UI and Content translation UI.
This is critical because it can lead to all sort of odd unpredictable behaviour for anything that uses this information. Imagine if you had one node type with the machine name 'content' and you then deleted it. According to EntityManager::getAllBundleInfo() it would still exist. This is nuts.
Proposed resolution
Only return fake bundles when the entity type does have a bundle entity type.
Remaining tasks
Write tests.
User interface changes
None.
API changes
EntityManager::getAllBundleInfo()
only creates fake bundles for entity types that are not bundleable.
Data model changes
None
Previous issue summary
That function currently does this to check if an entity_type supports bundles or not:
$optional = $bundle_name != $entity_type;
There is AFAIK no such requirement, you could have an entity foo with bundles foo and bar. Then this would result in very ugly bugs.
I'm wondering if we need that check at all? Can't we just put everything in optional? That would result the complexity of that hook and also hooks that extend/alter the information in there, see #1818556-120: Convert nodes to the new Entity Field API for a bug that was caused by this complexity.
Comment | File | Size | Author |
---|---|---|---|
#36 | 1919468-36.patch | 14.47 KB | alexpott |
#36 | 33-36-interdiff.txt | 2.65 KB | alexpott |
#33 | 1919468-33.patch | 12.86 KB | alexpott |
#33 | 1919468-33.test-only.patch | 3.59 KB | alexpott |
#26 | 1919468-24.patch | 9.27 KB | alexpott |
Comments
Comment #1
BerdirSimple, untested patch.
Comment #3
BerdirAdd default bundle logic for no bundle and a single bundle to getFieldProperties().
Comment #5
Berdir#3: simplify-field_entity_field_info-1919468-3.patch queued for re-testing.
Comment #6
fagoThat we cannot do. That would mean the metadata of an entity changes once you add another bundle - wait, what happened?? ;-)
imho the wtf stems from entity::bundle() defaulting to $entity_type. As you say we do not really know whether it's just a bundle that's called that way or there is no bundle. This really is a field-API left-over we should fix to have the function return FALSE if there is no bundle.
Then, the current concept of having entity-type fields + bundle specific fields saves to have lots of base-fields in bundle maps and allows you to properly add a field to an entity-type without having to care about adding it to all bundles (what is a WTF also). Even worse, this code would have to adapt if a entity-type is altered (to get bundles later on, as first adding fields to "entity_type would suffice while later on not - yuck.
The idea is that you always have a set of fields per entity-type + optional extensions per bundle. That's imho the straight-forward appraoch one would expect.
So what about fixing Entity::bundle() instead??
Comment #7
BerdirHm.
Wouldn't bundle() being FALSE prevent us from differentiating between a entity type that has no bundles one where we don't know the bundle?
I'm actually not sure about the whole bundle mapping stuff. I'm wondering if we shouldn't drop that completely and instead cache by bundle (and a separate for no bundle maybe that would only list fixed properties). Field API switched to that too because it results in *huge* caches otherwise. I have a site with 250 fields and 500 field instances and the global field instance cache alone was between 10 and 15MB. I really don't want to back to that in 8.x.
One problem that it would solve are differences between field instances. Right now, the label is the field name which is IMHO a considerable problem when trying to do something with this information (like extract translatable strings for TMGMT and send it to a translation agency, display in review forms with sane labels and so on). Also, the label is completely useless right now, I already know the field name.
There are also other use cases, for example, there is an issue about making the translatable setting per-bundle, which is impossible right now with NG.
And the entity generic comments table has a very interesting problem of the entity_id pointing to a different entity_type per bundle. That might be possible to define per bundle then although I'm not sure if that would really work. I'm actually not sure at all how that should work, probably needs a DynamicEntityReference where the type is another field, I have multiple modules in contrib that will need something like that. One way or another, doing something like adding a reference in views from comments to nodes is going to be a very interesting problem.
Comment #8
fagoFirst off that should not matter e.g. when code conditionally incorporates bundle specific logic. Howsoever, we do not support entities without a bundle yet - if they have bundles.
I think we could solve per instance modifications like that by relying on methods. So you have a static field definition that's used to create the typed data object. But once you have the object it can take contextual information into account (= the entity's bundle) and add make use or provide us with even more specific metadata then. I.e. it could add the entity type to the reference then.
Comment #9
BerdirOk, this came back in a different way.
entity_test test entities have a default bundle that is == $entity_type but it's possible to add other bundles, which is used in tests.
This condition caused #2044841: EFQ relationships broken for entity types with bundles to not fail as it should have from the beginning. However, my suggestion above would not have caught it either. Not sure..
One thing that we could do is change the default bundle to something else, but that's going to require tons of field creation changes in tests that reference the bundle and there are two huge (500kb and 300kb) patches already messing around with that right now.
Comment #10
BerdirThe (required) follow-up of #2047229: Make use of classes for entity field and data definitions to support per-bundle definitions will mean this is a non-issue.
Comment #11
fagoI gave the bundle issue some more thought. We still have the inconsistency in the system between some code that handles the no-bundle case differently; there is field API related code which does $bundle == $entity_type then and there is code related to the new entity field API that does $bundle == NULL.
Strategy 1: For the new entity field API differentiating between $bundle == NULL and $bundle == 'something' is necessary as it supports the case of having fields attached to NO bundle (e.g. node base fields), while having fields attached to some other bundles at the same time (e.g. fields attached to node article).
Strategy 2: While field API and related code does not support having no bundle fields plus bundle-specific fields at the same time, it only needs to support he no-bundle field case when there are no other bundles - so assigning the no-bundle fields to the $entity_type bundle is fine then. However, as pointed out above, we cannot do that for all entity fields.
Summarizing so far - we've have code that supports no-bundle fields and bundle specific fields at the same time by using strategy (1), and other code which doesn't by using strategy (2). I think this is totally fine as long as this meets the desired use cases.
So what are the use cases we have right now? I'd mostly think of
a) - Defining fields no-bundle fields in code (strategy 1)
b) - Defining configurable fields (strategy 2)
c) - Defining form and view displays (strategy 2)
b&c are perfectly covered by strategy (2) I think, having screens that split up between no-bundle and bundle-specific fields would be certainly confusing. So while code has the power to do strategy (1), only strategy (2) is exposed to end-users.
Let's consider how that works out:
1) The default bundle of every type is $entity_type, as defined by $entity->bundle(). This lets strategy (2) work with the no-bundle case. This means we should always have at least one bundle.
2) Considering a module adding bundles to an entity type which originally has none, there will be new bundles in addition to the default bundle. This means configuration/code using strategy (2) won't apply to the added bundles, but strategy (1) would.
3) Entity types having bundles by default, work fine with strategy (1) and (2).
My conclusions:
Comment #12
almaudoh CreditAttribution: almaudoh commentedHow about using a class constant to define a default bundle. This would help differentiate between a no-bundle case and the case where the bundle name is simply the same as the $entity_type.
Not sure where the class constant would reside (maybe EntityManager::NO_BUNDLE or Entity::NO_BUNDLE)
Comment #13
fagoNot sure, whether this is something that is bound by beta but it would make a lot of sense to clarify it before. Reading my conclusions from #11 they still make a lot of sense.
Comment #14
BerdirThe first part is true, but I have no idea how we'd be able to do the second part? EntityManager does not and can not remember what was in the past, so I would say that providing this would have to be done by the module that adds the bundles and is aware that this existed?
I don't care about this in file_entity atm, because file is not fieldable before, not sure if I should.
Also, there is absolutely nothing stopping you from creating a node type "node" ;).
3. Yes, changing how that works would likely come with a lot of pain ;)
So yes, I agree with you, but what exactly do we need to do? That's how it already works, isn't it?
Comment #15
fagoThe problem we currently have is basically:
Also, that means $entity->bundle() is not necessarily a bundle that the entity manager knows about, what seems to be bogus to me, i.e. it is inconsistent.
To fix, I think we should add a bundle with the name $entity_type_id always *by default* unless the entity type providing module already provides bundles. We can check that later on also by only considering the entity type providing module implementation.
Comment #16
BerdirThis is almost already like it is right now, but as discussed, for modules that rely on a bundle, we should *not* define a default bundle.
We need to figure out how to deal with entity types where another module adds bundle (provider entity type != provider bundle entity type?)
This means that this is a bug, because that is how it worked in 7.x, so changing accordingly.
Comment #17
BerdirLet's try this.
Comment #18
fagoThat looks good, it still leave the issue of an 3rd party module adding bundles later on open though.
Comment #19
jhedstromMoving to needs work as per
Comment #20
alexpottSo this rears its head and causes problems in two ways. It breaks content translation UI when there are no node types and it breaks the Views UI Node wizard when there are no node types.
Steps to reproduce:
I think EntityTypeBundleInfo::getAllBundleInfo() should not return anything for an entity type which says this other entity type is my bundle type and there are none of them.no node types. The patch attached is a slightly different approach from #17 and things have been moved a lot so no interdiff.
Comment #21
catchDiscussed with @alexpott, given this is a fatal error via the UI and should be non-disruptive, tagging rc target.
Comment #22
alexpott@catch I'm not sure about the non-disruptiveness of this change since we're fixing what is returned by
EntityTypeBundleInfo::getAllBundleInfo()
Comment #24
alexpottFixing the tests - it looks the behaviour of
Drupal\migrate\Plugin\migrate\destination\EntityContentBase::processStubRow()
needs fixing to cope with the fact that the an entity type now might not have bundles - this is an example of the type of disruption this patch will cause.Comment #26
alexpottComment #27
alexpottI think this is a critical bug. With the current
EntityManager::getAllBundleInfo()
we are saying that bundleable entity types have bundles when they have none. This can lead to all sort of odd unpredictable behaviour for anything that uses this information. The only reason why we haven't come up against it is most times an entity type that uses bundles has at least one. However, imagine if you had one node type with the machine name 'content' and you then deleted it. According toEntityManager::getAllBundleInfo()
it would still exist. This is nuts.I've made this critical because:
Comment #28
alexpottComment #29
catchLooks great, one nit fixable on commit.
Over 80 chars due to the indentation change.
Given we're short on time, bumping this to RTBC. I'll give this several hours and commit either this evening or tomorrow if no objections.
Comment #31
plachBig RTBC +1
Comment #32
alexpottJust working on some explicit test coverage
Comment #33
alexpottHere's some tests - I tried to add to the unit test but the just was too much mocking to be done and would have required large changes to the way the test is setUp.
Comment #35
BerdirI think we need to keep the !isset() check here. We still have the hook as a standalone way to set non-entity type based bundles. This would also add the fallback for those.
It doesn't really make sense to do *anything* with nodes when there are no types I guess but this is probably good enough for now.
Comment #36
alexpottThanks @Berdir
Comment #37
BerdirNice, looks good to me now!
Comment #42
alexpottUnrelated test fails.
Comment #47
alexpottBack to rtbc - these installer fails have nothing to do with this patch.
Comment #51
catchCommitted/pushed to 8.0.x, thanks!