Closed (won't fix)
Project:
Drupal core
Version:
8.0.x-dev
Component:
field system
Priority:
Major
Category:
Task
Assigned:
Unassigned
Reporter:
Created:
19 Jun 2014 at 19:50 UTC
Updated:
24 Oct 2014 at 23:23 UTC
Jump to comment: Most recent
Comments
Comment #1
effulgentsia commentedComment #2
yched commentedWhatever the outcome, I think it's a good thing to allow us to re-evaluate this at this point.
Yes, I'm not fond either of FieldDefinition implementing both interfaces. AFAIK the only (but very valid) reasons are
- DX for defining fields through code in EntityType::baseFieldDefinitions() : for most fields, providing just one definition (the FSDI, which aloso contains the info to act as a FDI, the same for all bundles) is enough. I guess we could still keep that DX sugar while still internally creating, out of those FSDI, separate FDI objects for each bundle.
- The reason then becomes performance - the current formulation in HEAD tries to minimize the # of objects we need to instantiate at runtime.
Comment #3
yched commented#2143291: Clarify handling of field translatability got in, we can unpostpone ?
Comment #4
plachRelated issue: #2280639: Add the FieldStorageDefinition class to define field storage definitions in hook_entity_field_storage_info().
Comment #5
plachI think we need this sorted out before beta, I am afraid it would be a big API breaking change...
Comment #6
plachI am wondering whether a possible solution to cope with the DX regression implied by making FD implement just FDI, could be adding a method to get a (wrapped?) FSD from a FD and keep defining it with all the properties in just one object as we do now.
Comment #7
yched commentedI've always struggled with the notion of "base fields" as implemented by the current FieldDefinition class as a weird and confusing singularity with respect to how the rest of the ecosystem works with FieldDefinitionInterface objects.
The recent evolutions (the formalization of FieldStorageDefinitions, the "bundle overrides" being worked at #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields) only strengthen that feeling.
My current thoughts on this (and from an IRL discussion, @amateescu shares the same):
Those FieldDefinition objects are primarily handy shortcuts when writing your entity type's baseFieldDefinitions(), that lets you specify in one single definition object :
- a field storage definition, seen by the ecosystem as an FSDI
- and a "bundle field definition" on no specific bundle, seen by the ecosystem as "an FDI present on all existing bundles"
That's a very valid feature, baseFieldDefinitions() would be very painful to write otherwise.
But then it is currently painful for the rest of the system to have to deal with those weird animals at runtime.
In such a case IMO, the "shorthand" aspect that is valid at definition time should not leak out to the rest of the consuming runtime APIs.
Meaning, when building the "registry of FDI objetcs for a given $bundle", EM::buildBundleFieldDefinitions() would expand those shorthand objects into actual FDI objects, with an actual bundle.
API-wise :
- The "shorthand" class itself does not implement FDI, it's not an actual "definition o a field on a bundle".
It thus wouldn't be named FieldDefinition, but BaseFieldDefinition.
--> In MyEntityType::baseFieldDefinitions(), I create and return BaseFieldDefinition objects - makes sense.
- A BaseFieldDefinition is "the definition of one FSDI + a shorthand for definition FDIs on all bundles". It could still implement FSDI directly, I guess. No need to create a separate object for "the FSDI of this BaseFieldDefinition".
- The FieldDefinition class, implementing FDI, is the one used for the actual FDIs expanded for each bundle at runtime.
- FDI objects, manipulated by all the runtime ecosystem, are "field definitions on a bundle". No more "FDIs with no bundle because all bundles" singularity.
- If you need a list of "fields that are constituent of the Entity Type and appear across all bundles no matter what" (that's a need for Rules, if I understand correctly), you still have EM::getBaseFieldDefinitions(), that returns BaseFieldDefinition objects.
- #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields would then mean that all FDIs are in fact ConfigEntities. The ones expanded out of BaseFieldDefinition objects simply happen not to be ->save()d and written into config by default, but they *can* be (and you then have an override).
--> might go as far as FDI extends ConfigEntityInterface ?
The above IMO is an API design that faithfully represents what our actual concepts are.
I do realize that there's some perf overhead to be watched for :
- more objects creation in EM::buildBundleFieldDefinitions()
- more objects serialization on cache write, more unserialization on read, and after that more objects in memory at runtime (when no overrides though, those additional objects would be just a $bundle string and a pointer to their source BaseFieldDefinition)
But I would rather try to actually measure the perf cost and find ways to mitigate it on top of a sound & consistent API structure, rather than having confusing optimization shortcuts baked in the API design.
Comment #8
alexpottI totally agree with the main points of #7 and the idea that
is pretty exciting.
If we get agreement on this - then I would suggest postponing #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields on this and making this beta blocking.
Comment #9
alexpottActually I think that we could do #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields first as long as we agree the class names and config file names. And do this as part of the beta - yes we'd want to get this in early but the only modules we'd break as ones providing new Entity types or extending ContentEntityDatabaseStorage. I don't think this change falls under "stable data model and stable critical APIs" criteria for beta blockers. (Of course Berdir or yched might prove me wrong :) )
However I do think the concept of FieldDefinition and FieldDefinitionStorage and their separation is critical and it will be great when both FieldAPi and EntityFieldAPI both enforce this distinct through the implementations - rather than having just FieldAPI do it.
Comment #10
xjmIsn't this pretty much exactly the definition of the content storage model though?
Comment #11
alexpottYes this will change how you define the content storage model ie...
There will be a new class here - it won't be FieldDefinition - but the model itself show not change. But the methods should all be the same. And there will be internal change in the EntityManager but I think that should be about it.
Comment #12
xjmSo, I think that is actually pretty much the definition of a beta blocker, if it's critical. The question for me is whether it's actually critical (I struggle to make a case), and how much time it would take.
Comment #13
alexpottI think we should proceed with #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields since this creates a BundleFieldDefinition object and goes a lot of the way there. This issue could then discuss if we want to change getFieldDefinitions() to always return objects that implement BundleFieldDefintitionInterface objects and whether we want to merge FieldDefintionInterface and BundleFieldDefinitionInterface together and rename the FieldDefinition object to something like BaseFieldDefinitionBuilder.
As to whether this is a critical beta blocker - the more I think about it I think that with #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields we're in a situation we could live for D8. I think if we do the simple renames and merges this won't be a lot of work. But actually trying to remove FieldDefinition and replace it with a builder that implements both FSDI and FDI and then decomposing that into 2 run time objects feels like a lot of work.
Comment #14
yched commentedMy point is, I'd really like to avoid introducing BundleFieldDefinitionInterface in addition to FieldDefinitionInterface, IMO that is adding awkward complexity to work around a flawed model, adds more confusion on top of the current confusion :-/
Fine if BFDI is added as an intermediate step, but it would be sad if we shipped with both.
FWIW, I'm willing to work on this once "rename FieldConfigl is in.
Comment #15
berdirThe main reason we went with the current approach is to try and limit the amount of definition objects we need at runtime because this sums up very very fast. Take a bigger site which displays nodes of 10 different types on a page, with terms, users, comments and that's just core. You're quickly way past 100 field definitions that you're carrying around.
Right now, we need both the definition and the storage definition to do anything useful.
I can see that this might actually be an improvement *if* we manage to avoid loading the storage definitions on a normal, cached node view, for example. Then this could actually be an improvement compared to now as we only have to do 1 cache get instead of 2. (only applies to the first bundle of an entity type), although we trade it with needing more memory as soon as multiple bundles are involved.
Comment #16
fagoI don't think our current model is a confusing shortcut - it models the reality of having a single field definition for all the base fields that have no per-bundle variations. There is just one thing - but if the API makes two out of the single-defined thing, this is something our (simplified) model enforces on it.
Anyway, if the simplified model works sufficiently there is nothing against it to say. However, I must say that performance concerns me here as well. I don't think it makes sense to change this when we end up having more stuff being loaded into memory just for keeping our simplified model intact.
That said, I'm not sure I fully understand the reasoning on why the current model is seen as flawed / bad. Is it the aim to have the simplified storage+instance model everywhere, or is it more related to having #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields and per-bundle variations pretty much everywhere anyway?
BundleFieldDefinitionInterface sounds like making it confusing, yep - not sure where the need for this would come from. I guess I head over and read up on #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields now, which is probably related.
Comment #17
effulgentsia commentedWe discussed this issue at length in yesterday's #drupal-entity IRC meeting, and here's what we came up with as I understand it. Please correct me if there's something here that doesn't reflect that consensus, and now's a good time to make some coffee to drink slowly while reading this:
FieldStorageDefinitionInterfaceandFieldDefinitionInterface. The meaning of the former is: the information about a field that is required to be the same for all of that field's items, regardless of bundle or any other per-entity variation. Because of that requirement, that information can be used by the storage handler to optimize the schema, by Views to set up exposed filters, and by Rules, Panels, and other code that needs to rely on something about a field that is independent of bundle. The meaning of the second is: the information about a field that might vary from entity to entity. Now currently in Drupal, in practice and with core's UIs and some APIs, that variation can only be by bundle (i.e., the field definition of $entity_1->FIELD_FOO and $entity_2->FIELD_FOO will be the same if $entity_1 and $entity_2 have the same bundle); however, there's nothing about FieldDefinitionInterface itself that specifies or requires that (except the getBundle() method, which I propose later in this comment to remove).So, if you accept all of the above (especially that last point), then our conclusion was:
FDI::getConfig($bundle)method). yched recommends the latter, but plach said that was tried in #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields and was problematic for some reason, but it may be worth trying again. In any case, let's move that part of the discussion to that issue.Finally, here's the proposed hierarchy/naming for the above, to be implemented in #2312093: Rename FieldInstanceConfig to FieldConfig and #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields:
Comment #18
plach#17 looks accurate to me (@effulgentsia++), just a couple of minor remarks that do not affect in any way the validity of this writeup:
This is a bit misleading: it's correct that you don't need to worry whether a particular field on an
$entityobject is base or bundle, BUT the main reason to have base fields is exactly being always able to rely on them. If a module defines (or targets) a particular entity type, the business logic it implements should be built on base fields, which are guaranteed to be there for any$entityobject. A good example of this would be the price of a commerce product (aside from the fact I am pretty sure it will be implemented as configurable field as in D7, that is as a bundle field).Shouldn't it implement also
ConfigEntityInterface? @yched brought up the idea of having a single config entity shared betweenField[Instance]ConfigandBaseFieldBundleOverride, which would have lots of advantages, however @alexpott in #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields brought up good reasons for not doing that. I guess this discussion should be continued over there.Comment #19
plachScratch that, sorry. I missed this due to the new line:
Comment #20
yched commentedThanks for the comprehensive summary, pure @effulgentsia magic :)
I still stand that having both FieldDefinitionInterface and FieldConfigInterface, meaning a 3rd "orthogonal distinction" in your explanation, is an unfortunate cognitive overload, only justified by performance impact that we never actually evaluated, and thus qualifies as a preemptive, possibly premature optimization sadly baked in the shape of the API.
But I agree that this is our best way forward with #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields atm.
Then BaseFieldDefinition needs to duplicate those setters' doc (which would also be the case in my scenario where BFD *not* implements FDI).
Also, if the setters present on BFD are not part of any interface, what type-hint do we use for methods/functions that:
- return such objects specifically:
EntityType::baseFieldDefinitions(), hook_entity_base_field_info[_alter](), EM::getbaseFieldDefinitions()...
- receive such objects with the explicit intent to use those setters :
EntityType::bundleFieldDefinitions(), hook_entity_bundle_field_info[_alter]()...
?
It seems we either type-hint on BFD directly, or need a BaseFieldDefinitionInterface with setters duplicated from FCI ?
Hm - maybe FieldConfigInterface for consistency ?
(I know I suggested FieldConfigurationInterface in IRC, re-reading it tickles now)
entity_type id and config prefixes: that's were I was not really sure, that's merely a proposal to be discussed in #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields.
Comment #21
yched commentedFurther thinking on my comment #20 above :
With #2283977: Create a new ConfigEntity type for storing bundle-specific customizations of base fields, EntityType::bundleFieldDefinitions(), hook_entity_bundle_field_info[_alter]() should be made to return FCIs rather than clones of BFDs ? And thus, renamed to bundleFieldConfig() ?
Before :
After :
A by-bundle clone of a BFD makes little sense, that's what FCIs are here for. Plus the current DX is horrible :-)
Comment #22
alexpottWhat's great about #21 is that FD's could lose all their setters! +1Comment #23
alexpottre #22 ignore me I'm wrong.
Comment #24
yched commentedTrying to translate #17 into actionable issues:
Opened #2315237: Rename FieldDefinition to BaseFieldDefinition (major, beta-target)
Less sure whether those should be split to dedicated issues or adressed as a whole. The latter still has unresolved consequences mentioned in #20.
Comment #25
fagoThanks for the great write-up effulgentsia.
I've read through all the IRC log as well, but #17 is a great summary to me. I think this is a solid plan, so overall +1, but here some additional comments:
$override = $base_field_definitions['some_field']->getBundleOverride($bundle);If getBundleOverride() finds an pre-existing bundle field which is not a bundle override, i.e. someone hand-crafted another implementation, it can still take over its values based on the interface getters + replace the object with an suiting override object. It's a litte bit of extra work for us to do, but that way we end up with a consistent way to override while the field definitions returned to and from the EM stay read-only again, i.e. if you ask the EM about the field definitions of a bundle you are not supposed to change anything and you can't.
Yep, that seems reasonable!
Comment #26
xjm@alexpott, @effulgentsia, and I (well mostly @alexpott and @effulgentsia) discussed this issue at TCDrupal. We had the following @todo for @effulgentsia:
I still think this is a major beta deadline, not a critical beta target, though I guess it's a moot point if we're closing it works as designed. As I stated above, I'm not comfortable with the idea of changing this after beta; it seems like it would impact a lot of code. Tagging to look at during the pre-AMS beta sprint.
Comment #27
effulgentsia commentedClosing this per #26, except not calling this "by design", because it's more of a performance compromise than a purely design-driven decision.
From what I can see, here's the feedback from comments that has not already been implemented:
#2346421: WTF with getTargetEntityId() / getBundle() renamed FDI::getBundle() to getTargetBundle() rather than removing it entirely. We discussed this at DrupalCon Amsterdam though, and decided it's okay to leave this method on the interface. The claim about that method making BFD objects not reusable cross-bundle was incorrect, since the method is defined as being able to return NULL in that case. I still think that creates cruft that isn't ideal, but in the absence of it posing any functional problem, it's too late to change for D8.
We also discussed this at AMS, and while I disagree with using FCI for something that is not configuration, I agree that HEAD's current approach of BFD cloning is problematic, so we opened #2346347: Finalize API for creating, overriding, and altering code-defined bundle fields for that.
Please comment if there's anything else left outstanding here other than the above.