Problem/Motivation
The modules that want add fields to an entity bundle field in hook_entity_bundle_field_info()
don't have a class to use for that. The BaseFieldDefinition
class is the wrong one to use, since its isBaseField()
returns TRUE
, when they need it returns FALSE
. They would end up with defining their own class just to override that method.
Drupal core should define that class for them, so third-party modules don't need to define that class.
Proposed resolution
Add a FieldDefinition class for defining bundle fields in code.
Remaining tasks
Commit the patch.
User interface changes
None.
API changes
API addtion.
Data model changes
None.
Comment | File | Size | Author |
---|---|---|---|
#123 | 2935932-123.patch | 32.02 KB | Sam152 |
#123 | interdiff.txt | 803 bytes | Sam152 |
#118 | 2935932-118.patch | 32.02 KB | jibran |
Comments
Comment #2
apadernoComment #4
borisson_We should only add new things to core when they have a usage. Is there a possible usage in drupal core for this?
Comment #5
apadernoThe usage is for third-party modules, as described in the parent issue. Every module that adds bundle fields need to create a class with a
isBaseField()
method that returnsFALSE
.Since Drupal core defines a class where that method returns
TRUE
, it should define a class where the same method returnsFALSE
.Comment #6
apadernoI agree Drupal core should not have classes that could be used from third-party modules, but in this case the problem is that Drupal core is exposing a single function to create an entity field:
BaseFieldDefinition::create()
. It cannot be used for every field, since the created field is a base field.As I see it, the options are these:
BaseFieldDefinition
class to be used also for non base fieldsisBaseField()
returnsFALSE
The first option doesn't have sense, to me: The class would not be anymore for base fields, so at least its name should change. IMO, the second option is less disruptive for the existing code.
Comment #7
apadernoIn the first case, the
BaseFieldDefinition
class should have a method likesetBaseField()
which would be called as in the following code.IMO, code like that doesn't make sense, as being a base field (or not) should be an intrinsic property of the field, not something that can be altered at any time. I cannot even imagine a use case where a base field needs to be changed in a bundle field.
Comment #8
dwkitchen CreditAttribution: dwkitchen at Johnson & Johnson commented@borisson_
Yes, this resolves our use case. We have our internal distributions that define content types and fields, but these fields should not be changed on sites built using the profile. If the field is created using config in the install, it can be changed on the site. Using BundleFieldDefinition prevents this.
I agree with @kiamlaluno, it doesn't make sense to have a "Not a Base Field" switch on BaseFieldDefinition and the added BundleFieldDefinition is it is going to be used in hook_entity_bundle_field_info().
Comment #9
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI think the approach in #2 looks good. I think we should frame this almost as a bugfix vs a new feature, given it's finishing off part of an API is this quite visible already (a method on ContentEntityBase).
I think this is NW for some additional documentation on the class itself and also some explicit examples in tests that expose the benefits of this class (the fact that we can install bundle specific fields and storage to get parity with config base fields, the example in entity.api.php uses a computed field).
Comment #10
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedAdding some docs and replacing instances of the test-only class with the new
BundleFieldDefinition
. We seem to have all the testing we need inEntityBundleFieldTest
.Comment #11
jibranWhat about this comment from
\Drupal\entity_test\FieldStorageDefinition
?Comment #12
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedWhat about it? I'm not aware of the original goals of the
Drupal\entity_test\FieldStorageDefinition
class. I think it has been abused to do something similar to what this issue is accomplishing but they are two different things. In this case we do want both a field definition and a storage definition, just likeBaseFieldDefinition
.Comment #14
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedUsed the class from contrib instead of core.
Comment #15
joachim CreditAttribution: joachim as a volunteer commentedSome documentation erorrs:
The hook name needs a () so API module will make that into a link.
Method name needs a () too, and class should be fully-qualified.
Same -- ().
More () missing.
Comment #16
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThanks for the review! Fixed up the docs and slightly reworded the docbock to not require the large fully qualifier interface method in the text.
Comment #17
joachim CreditAttribution: joachim as a volunteer commentedLooks good!
I think we need the change record to be written in draft before this can be RTBC?
Comment #18
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedGood point, writing one up now.
Comment #19
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedComment #20
jibranWhat about #2346347: Finalize API for creating, overriding, and altering code-defined bundle fields the patch there is also doing the same thing there as this patch? I think we need a review from at least one sub-system maintiner of entity/field API.
Should we remove the
FieldStorageDefinition
class as well here?Comment #21
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThe parent is a meta. Looks like @amateescu is the only maintainer of field api. Assigning, hopefully he has some time to take a look at this.
Comment #22
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedI'm afraid that adding a bundle field definition class is not that simple :)
The current
\Drupal\entity_test\FieldStorageDefinition
is just a quick workaround, and the problem is that bundle fields should not have setters for properties that exist only at the field storage level. For example, a bundle field should *not* be able to set the cardinality, the target entity type and even the target bundle (!), because they are assigned automatically by\Drupal\Core\Entity\EntityFieldManager::buildBundleFieldDefinitions
.IMO, we need a class that extends
ListDataDefinition
, implementsFieldDefinitionInterface
and mirrors the setters from\Drupal\Core\Field\FieldConfigBase
.Comment #23
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThanks for looking into this @amateescu.
I assumed we would want a builder for bundle fields to be both a storage definition and field definition, with the intention that we could do stuff like #2980922: Applicable bundle field definitions defined in code should not have to manually implement hook_entity_field_storage_info. in the future.
If we introduced a builder for just the field definition part of a bundle field, I suppose we would also need #2280639: Add the FieldStorageDefinition class to define field storage definitions in hook_entity_field_storage_info(). I left a similar comment to that effect in that issue.
Is there a reason we wouldn't encapsulate both of those in the one builder for DX consistency with
BaseFieldDefinition
?Comment #24
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedI can't really put my finger on it, I just have the impression that it would muddy the waters on both concepts. Also,
hook_entity_bundle_field_info()
says that you need to override an existing base field or provide an explicit field storage, which makes perfect sense to me at least :)Comment #25
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedFair enough, the approach is somewhat more palatable if it's a totally new field which you'd have to write a storage definition for anyway, but it's perhaps inappropriate if you're simply doing a bundle override on an existing base field.
So this is NW to implement #22. Thanks @amateescu!
Comment #26
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedNew patch since this essentially starts from scratch. Progress so far:
EntityBundleFieldTest
currently passes utilising this builder! 🎉BundleFieldDefinitionTest
is a new test class, which essentially copies the definition related test cases fromBaseFieldDefinitionTest
Once that blocker is in, the remaining test cases in
BundleFieldDefinitionTest
can be properly ported and this should be ready for a review again.Comment #27
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedComment #28
jibranWe should also update sample code in
hook_entity_bundle_field_info
Comment #29
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedGood point re: docs. Uploading a combined patch with #2976244: The BaseFieldOverride entity fails to normalize default values into the "array keyed by delta" format in the same way BaseFieldDefinition does when a callback is specified.
BundleFieldDefinitionTest
.testDisplayConfigurationSettings
test case that was missing fromBaseFieldDefinitionTest
.Comment #31
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedFixing last test case.
Comment #34
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedUpdating the docs too as requested in #28.
Comment #36
jibranCan we update
hook_entity_field_storage_info
docs as well instead of copingfield_entity_field_storage_info
? Or do you think it is out of scope? I'd nominate replacing the docs withentity_schema_test_entity_field_storage_info
code.Comment #37
jibranWe should setup the bundle here as well using
setTargetBundle
.We also don't need to call
in
entity_schema_test_entity_bundle_create
andComment #38
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedFeedback from #37 and reroll after the blocker is in.
It would be great if @amateescu could have another look at this if it goes green.
Comment #39
jibranYay! blocker is in and it is green. I think @Sam152 we agreed on addressing #36 and thank you for addressing the feedback. Coding wise patch looks fine to me. Here are some docs update suggestions.
I think we can remove this @todo from both hook_entity_bundle_field_info and hook_entity_bundle_field_info_alter.
We can have bundle specific overrides for base field definitions but that doesn't make them bundle fields they are still base fields. I think this should be removed.
s/requires storage/requires storage then.
Why are we adding this here this interface is not used in this class?
Comment #40
apadernoActually, if the field definition is changed just for a bundle, that field becomes a de facto bundle field, but it still using a class that shows the field to be a base field.
I would remove that part of the comment because, IMO, is not sufficiently clear, and could suggest a bad practice.
It is possible to remove a field definition, and add another one using the
BundleFieldDefinition
class, but I don't see any reason to use the same field ID, nor can I imagine a use case where it would be necessary to change a base field definition for a bundle. I cannot change what that field accepts, since that is probably going to cause errors in the entity code, and I should not change how the field is shown, since that is set in the entity field UI.Comment #41
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThis is getting close :) I'll have to review again after the feedback below is addressed because these changes will have a big impact on the patch..
We should use the fully qualified method name for
bundleFieldDefinitions()
.Missing a comma after
storage
I think.Unlike
BaseFieldDefinition
, the field type is not an "important" property of a field (definition), it's just there for optimization purposes (see \Drupal\Core\Field\FieldConfigBase::$field_type) and it should be populated automatically by getting the value from the field storage.We should also keep the field storage in an internal
fieldStorage
property, just like we do in\Drupal\field\Entity\FieldConfig
.However, the most important part missing here IMO is a
createFromFieldStorageDefinition(FieldStorageDefinitionInterface $definition, $bundle)
, which would look very close to the implementation fromBaseFieldDefinition
(without cardinality and custom storage), and with one very important addition: it would also set the target bundle.Bundle fields can not choose the entity type they are attached to, that's a property of the field storage, so we need a check that the entity type set here is not different than the one defined by the field storage.
This should work like
\Drupal\Core\Field\FieldConfigBase::getSetting()
and also look at the storage settings if a setting is not found on the item definition.Like above, this needs to merge in the storage settings.
As already pointed out above, a bundle definition does make sense on its own, so we need to pass in a storage definition and use that to populate the field name automatically.
We should use the
$bundle
variable here.Comment #42
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThank you for the review. I only have these questions:
3. How would be make use of this method in the wild? Ie, what scenarios should we be testing for when introducing it?
7. Can you expand on this? Do you think we always need access to a storage definition when creating this kind of definition?
Comment #43
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedI'm not sure I understand the question for point 3..
As for 7, we already have access to the storage definitions (at least the base field ones), because both
hook_entity_bundle_field_info()
andFieldableEntityInterface::bundleFieldDefinitions()
receive an$base_field_definitions
argument.It could be argued that the base field definitions are not enough, and that we should receive all the field storage definitions of an entity type (i.e. what
EntityFieldManager::getFieldStorageDefinitions()
provides), but that would mean we would be able to override in code the properties of a configurable field (which is stored in config), and I'm not sure that we want to go that far.. :)Comment #44
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedSpoke to @amateescu on IRC. This was a summary of what we agreed:
This should make these definitions more robust, since it would be harder to create an invalid definition, given the constraints for what core supports.
Given that #2280639: Add the FieldStorageDefinition class to define field storage definitions in hook_entity_field_storage_info() was raised as a potential blocker, however the
\Drupal\entity_test\FieldStorageDefinition
class might still be usable for testing purposes to decouple these two issues.Comment #45
jibranI think we should add a test for the computed bundle field as well to make sure that
hook_entity_bundle_field_info
can create the computed fields.Comment #46
TwoDA few questions as I've been battling with implementing bundle specific computed fields for a couple of days now and the many issues involved are so spread out it gets quite a bit confusing.
Am I missing something or does not the code comments (after patches/suggestions here) still point to not needing to implement
hook_entity_field_storage_info()
for computed fields? Not needing that would makes sense to me since computed field values are not actually stored anywhere, and most of the information for that hook seems to be mostly a duplicate of what's given inhook_entity_bundle_field_info()
but for the purpose of DB/querying implementations (either that or most of the example code I've seen so far is incorrect/misleading).The discussion here seems to however indicate implementing
hook_entity_field_storage_info()
should be required even for computed [bundle] fields?Somewhat related, #2852067 made it sound like
\Drupal\views\Plugin\views\field\EntityField
should now be possible to use with all computed fields. That would be awesome for my use as I'm computing simple numeric statistics values for one node type and I want flexibility in how they are displayed.But, that class only works with computed base fields because storage definitions are still expected to be there to get the column types from (and base fields implicitly provide them).
Modifying that
EntityField
class to add support for bundle fields is possible (make it look for a'bundle'
property in the Views field definition to get bundle-specific entity field definitions if set, and use #38 to define the bundle fields), but it would need more complex logic changes to actually fully support computed bundle fields without storage, which I've been lead to believe is the expected case for computed fields, no?)I may be completely wrong here tho, given
\Drupal\Core\Field\FieldDefinitionInterface::getFieldStorageDefinition()
must return something, and it can not return anything if the storage info hook was not implemented for bundle fields - as it was decided they do not implicitly also represent storage like base fields () -, which is probably the main cause of me being confused about all this.Comment #47
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedI'll give the same example as I used earlier when I was walking to @Sam152.
If we don't require bundle fields (computed or not) to also have to define a field storage, then we can get into this situation: you define a bundle field using the
string
field type for thearticle
bundle, and a different bundle field with the same name but using thetext
field type for thepage
bundle. In that case, there is no way for some generic code (e.g. Views, Rules, etc.) to know the properties available for that field.This is the reason for @Sam152's first bullet point in #44:
Comment #48
TwoDAh, so that meant two bundle fields with the same name are always required to be of the exact same type (but some of their properties may still be of different types between bundles, like
target_id
).Allowing the same field be two completely different types across multiple bundles would indeed be overly complicated, and you're saying the
hook_entity_field_storage_info()
would be used to dictate which type it actually is, as there can be only one storage definition for each entity type and field name combination.And that is why the
$type
argument in the newBundleFieldDefinition
constructor is suggested to be removed (or perhaps change to instead be a field storage definition?). Field storage definition are gettable from the list of base field definition list passed to the bundle field definition hook, and for computed bundle fields you need to get them via the field type manager.Makes more sense now, thanks.
Should not all mentions of "if it is not computed" and "unless they are computed" in relation to the bundle field definition hooks/classes be removed then?
Comment #49
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedIf people agree with my explanation above, then yes, we should probably remove references to whether a field is computed or not from this area, because all of them should be treated in the same way.
Comment #50
dwkitchen CreditAttribution: dwkitchen at Johnson & Johnson commentedHere's an example of how I'm using this. Our profile includes a set of pre-defined content types; we do not use config for these as that is a one-off install and features does not allow simple extending of content types.
So in our hero module, we define the hero field storage:
Then in our news module we can add an instance of that field to the article:
We use the same methodology to create all the fields on the news article that are extra to the base fields, but I haven't included them here for simplicity. We can do the same to add the same hero field to any other content type. Further, all the fields on the hero_image are created in the same way as we don't change the base fields from paragraph module.
You can see that we only have one target bundle for hero's ATM, but when we have built that we can add it to the handler settings.
I shouldn't be able to define
->setRevisionable(TRUE)
&->setCardinality(1)
in the Bundle but at patch 2 that was needed.Comment #51
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThose are exactly the things that stood out while reading the code, and your conclusion is correct that you shouldn't be able to do that at the field definition level since those are storage-related properties.
Comment #53
tstoecklerI don't agree that a computed bundle field should require a field storage definition. While I get that a common field storage definition is useful for some modules, declaring it comes with a cost. First of all it will actually create the respective database tables/columns, which is certainly not what you want. You can work around that by setting
hasCustomStorage()
toTRUE
but first of all, that really is a workaround and not the correct semantic of "custom_storage" - because there is in fact no storage at all in this case - and secondly you will still have it in the list of all storage definitions and modules may not always exclude definitions with custom storage - and depending on what they do with the storage definitions that may very well be justified. So just to make the life of a hand-picked list of very generic modules easier we would be introducing a workaround that can lead to weird and unexpected behavior elsewhere.The only way that this would be feasible in my opinion is if we introduce the notion of "computed" on the field storage definitions themselves. But that is somewhat paradoxical in terms of semantics. So really think it is correct to not have a field storage definition for something that will not be stored and that Views, etc just have to live with the consequences of that.
Also see the linked issue for a way to let Views deal with this issue. That would in fact forces every views field to be specific to a certain bundle, so if you have computed bundle fields on different bundles with the same name but of a different type you cannot use the same views field name for it, but you can still use computed bundle fields in general.
Comment #54
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedAddressing all the comments since the last patch.
#39
1. I don’t think we're ready for that. I would say this is the first step towards that issue but doesn't resolve it.
2. I've tried to clarify this.
3. This part has been rewritten.
4. It's a a heavily related interface, why wouldn’t we add it as something people should also be aware of?
#41
1. Done.
2. This part has been rewritten.
3. Done. I think by keeping the storage definition as a property, we don’t have to copy it’s values, we can simply satisfy the relevant storage-only parts of the interface by calling off to the storage directly. What do you think to this approach? For example ::getName becomes
return $this->getFieldStorageDefinition()->getName()
instead ofreturn $this->definition['field_name’]
. I think this makes more sense because we don’t actually have setters for things like name anymore.4. Setter removed.
5. Done.
6. Done.
7. Done.
8. Done.
#45
I don’t think this should be in scope.
#48/#49
Like 45, I think a different issue to properly explore computed fields would make the most sense.
Comment #55
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedCross-posted with #52/#53.
Right now we don't have any computed fields that don't actually have an associated storage definition. Computed base fields have one because a
BaseFieldDefinition
satisfies both interfaces andFieldDefinitionInterface
enforces that a storage definition always be available:I think these discussions are important but don't actually block this issue. If at some point in the future the way fields were defined allowed storage definitions to be optional,
BundleFieldDefinition
could be adjusted to account for definitions with and without an associated storage definition.Can we agree to discuss storage-less base/bundle field definitions in a follow-up?
Comment #56
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@tstoeckler, if computed bundle fields should not have a field storage, what would
\Drupal\Core\Field\FieldDefinitionInterface::getFieldStorageDefinition()
return?Here's a review for #54:
This not correct anymore :)
Creates ;)
We need to add this parameter to the doxygen block.
We don't have a
$type
variable anymore.Should be
{@inheritdoc}
.Should be
@return $this
.We shouldn't need to set the bundle here but make
EntityFieldManager::buildBundleFieldDefinitions()
do it forBundleFieldDefinition
just like it does forBaseFieldDefinition
.Comment #57
tstoecklerThat is a valid question! I wonder what your response to the rest of #53 is, though. I really don't think the current approach is viable as is.
Comment #58
TwoDMaybe for computed fields it could return some kind of
NullFieldStorageDefinition
implementation? It would returnNULL
/[]
in::getMainPropertyName()/code>, <code>::getSchema()/code>, <code>::getColumns()/code>, <code>TRUE
in::hasCustomStorage()
(andFALSE
in::isBaseField()
).I did a quick test by copying most of the
\Drupal\Core\Field\BaseFieldDefinition
methods into such a new class (forced it to be created using a\Drupal\Core\Field\FieldDefinitionInterface
instance) and at first glance it works well with computed bundle fields, at least through Views when using the #2981047 patch.Comment #59
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedAddressing feedback from #56.
@amateescu had some similar ideas on slack. I've split out #2986634: Discuss the role of field storage definitions for computed fields., lets move that discussion over there so we're not blocking this issue. As per #55, we don't need all the answers to those questions here.
Comment #60
jibranThis is not correct. This should be a re-usable code example.
Comment #61
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedCan you explain this a bit further? What would you suggest the code snippet be and why is the current one incorrect?
Comment #62
apadernoI take jibran means the example should show code that a real hook implementation would use (apart the hook name, which is clearly
hook_entity_bundle_field_info()
because that is the name we give to hook implementations in example code).You would never have code like
$storage_definitions = hook_entity_field_storage_info($entity_type);
in a hook implementation.That is not how hooks are invoked.
Comment #63
tstoecklerA: I don't understand #60 and #62 either. It actually makes a lot of sense to call the
hook_entity_field_storage_info()
implementation explicitly so as not to duplicate the definition of the field storage definition (no pun intended).B: Having thought about this for a while, I'm fine with the current direction of the patch. I will open a follow-up to introduce the notion of computed field storage definitions in order to properly support computed bundle fields.
C: Here's a review of the actual patch:
While I like the idea of a
createFromStorageDefinition()
method, we also havecreate()
,createFromDataType()
andcreateFromItemType()
methods, that we inherit. So it might make sense to shuffle parts of this around intocreate()
or something, not sure.At least we should add test coverage for those methods.
The
[]
is the default value, so could be omitted.This is not inherited.
I wonder why this is not being forwarded to the field storage definition?
Comment #64
tstoecklerOpened #2986836: Support computed bundle fields by adding the notion of computed field storage definitions.
Comment #65
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@tstoeckler, I've re-read #53 a few more times and I think that introducing the notion of "computed" at the field storage level is the proper way to go, as you seem to agree with in #63.
The concept of "computed field storage" might sound paradoxical in terms of semantics, but that's only because we haven't found a better term for "field storage" during Drupal 8's development cycle. The actual meaning of field definition and field storage definition is (IMO) something like this:
- field storage definition: the properties of an entity field that are applied at the entity type level (i.e. across all bundles). Some of these properties are simply defaults that can be overridden per-bundle (e.g. the field label, field settings, etc.) while others are "fixed" (e.g. field name, field type, target entity type, cardinality, etc.)
- field definition: the properties of an entity field that are applied only at the bundle level (e.g. bundle-specific label for the field, bundle-specific settings for the field, etc.)
About #63.4: I think content translation can be enabled per-bundle, so I don't see why would not want to expose that flexibility here. Except if you meant that we should also ask the field storage whether it is translatable or not.
Comment #66
jibranAren't #2986634: Discuss the role of field storage definitions for computed fields. and #2986836: Support computed bundle fields by adding the notion of computed field storage definitions duplicate of each other?
What I meant was after applying this patch, reading
hook_entity_bundle_field_info
andhook_entity_field_storage_info
side by side doesn't make sense. For starter we don't define new field stroage definition inhook_entity_field_storage_info
so we need to updatehook_entity_field_storage_info
and initiate a proper field stroage definition for node entity type.As far as duplicating the field storage definition is concerned, we should be calling
\Drupal\Core\Entity\EntityFieldManager::getFieldStorageDefinitions
instead ofhook_entity_field_storage_info
becuase we want to give people a chance to alter the storage usinghook_entity_field_storage_info_alter
.Comment #67
tstoecklerHmm... seems a bit arbitrary to allow it for translatability, but not for e.g. the name.
Good point! That should be fixed.
Not sure about that, I think that would lead to recursion. Even if not, the bundle definitions themselves can also be altered later, so having them rely on already-altered information while again being altered doesn't really sound like such a great idea to me, to be honest.
Comment #68
tstoecklerAlso:
Yay, that's awesome!!!
Comment #69
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedI have no idea why translatability can be enabled/disabled per-bundle, I haven't worked on that part (or with translations in general) at all, but I don't see how the machine name of a field could be different per bundle. It is a basic architectural design of the field system that this code will always return true:
Comment #70
jibranCan you elaborate?
Comment #71
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedGreat discussion so far thanks for swarming on this issue! Exciting to come this far in a short amount of time :). Responding to all the comments since the last patch:
#62
I think @tstoeckler nailed this in #63.A.
#63
A: Great, I agree!
B: Woohoo! Consensus is a beautiful thing in the core queue :)
C.1: Good point. This also illustrated the only way to set the storage definition was via the new factory. I've resolved that with a setter and updated the test coverage to test every method with every factory! I think that's a big improvement.
C.2: Done, good catch.
C.3: Good catch, fixed.
C.4: I believe @plach knows the most about this, but from what I've learned in the past, translatability never affects the table layouts/storage by design. We have systems like content_translation which create BaseFieldOverride entities that allow the user to flip translatability with a checkbox, so this is indeed something which can be turned on or off at the definition level and the result is changes in the semantics of how that field is treated when new translations are created. I might be corrected on this, hopefully this can get across @plach's desk too.
#64
Nice, look forward to seeing that.
#65
Agree with these points.
#66
1. There is nothing that says all the example API hooks must form a functioning module/implementation. I believe that hook could happily be fixed in #2280639: Add the FieldStorageDefinition class to define field storage definitions in hook_entity_field_storage_info().
2. Exploring this is out of scope, right now we're not enforcing the method the user takes to get a reference to the storage definition. We might want to do that in the future.
#67
@see response to #63.
Agree re: alters.
#69
@see response to #63. I believe flexibility on this is simply a feature that the content translation UIs expose.
Comment #72
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI have updated the change record with the updated consensus.
Comment #73
tstoecklerRe #69: Sorry, I mixed up label vs. name. I now realize that e.g. label and description already use the same pattern as translatable via inheritance from
ListDataDefinition
, so my point about inconsistency is moot.However, the point about asking the field storage definition for translatability still needs to be fixed. Re #71: Both
FieldConfig
andBaseFieldOverride
(through their common ancestorFieldConfigBase
) have the following:So I think we should implement
isTranslatable()
analogously here.Re patch in #71: Nice!!! Really great idea with the
setFieldStorageDefinition()
class. Also really great work on the tests. Read through the patch again and couldn't find anything to complain about, but marking needs work for the translatability thing from above.Comment #74
jibranThat's not really helpful. Sorry, I don't understand the relationship between this issue and #2280639: Add the FieldStorageDefinition class to define field storage definitions in hook_entity_field_storage_info()
Can we call
\Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type)
instead ofentity_schema_test_entity_field_storage_info($entity_type)
?Comment #75
tstoecklerRe #74: See my #67:
If a module wants to alter field storage definitions I think it may very well want to alter bundle field definitions as well. So I think we explicitly shouldn't call into the
EntityFieldManager
inhook_entity_bundle_info()
. I think the idea is literally to avoid copy-paste betweenhook_entity_field_storage_info()
andhook_entity_bundle_info()
, nothing else.Since the bundle field definition calls out to the storage definition anyway for various things altering it will already have an effect on the bundle definition anyway. Only e.g. if a module alters the label of a field storage definition it will not automatically propagate to the bundle field definition, but it's not clear to me that that would be expected in the first place, without the module explicitly altering the bundle field definitions as well.
Comment #76
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedRe #73, good point, the interface states:
So I think updating is correct. Attached is a patch for that.
Comment #77
tstoecklerAwesome, this looks great to me. I'll leave @amateescu to RTBC this, though. Also I guess we still need some confirmation from @jibran that they are fine with this.
Comment #78
jibranYeah, it is fine. It is a small step to mature this API which has been WIP for a long time now but we still have a lot to figure out and bikeshed. ;-) We all kinda have different ideas how it should/would work I'd like to have @plach or @Berdir's opinion on the patch before we commit.
Comment #79
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedI wonder whether the
Bundle
prefix is actually needed for this class.Since
FieldDefinitionInterface
represents the per-bundle properties of a field, shouldn't this be simply calledFieldDefinition
? That would also be in line with the field storage part of the API, as proposed in #2280639: Add the FieldStorageDefinition class to define field storage definitions in hook_entity_field_storage_info().These can be removed :)
BundleFieldDefinitions
is not an actual class name, so I would rewrite this with something like: "Field definitions do not currently ...".This test looks excellent!
Let's add
@group field
as well :)Comment #80
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedFeedback from #79. I had a similar thought that this was not really bundle specific, but the only thing that gave me pause was the fact we were already covering the base field use case. I like the flexibility of keeping this naming FieldDefinition for the moment. It could always be subclassed if it needed some specific utility for bundle fields.
Comment #82
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedWhoops, patches need to be against the correct branch :)
Comment #83
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedFixing test.
Comment #86
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedRerunning for testbot fail and fixing cs error.
Comment #87
jibranFrom #2280639: Add the FieldStorageDefinition class to define field storage definitions in hook_entity_field_storage_info()
So
FieldDefinition
class suppose to extendFieldStorageDefinition
, does this mean we are blocked on that or can it be done even after this patch?Comment #88
jibranI stumbled upon #2315237: Rename FieldDefinition to BaseFieldDefinition and
FieldDefinition
mentioned in IS of #2280639: Add the FieldStorageDefinition class to define field storage definitions in hook_entity_field_storage_info() is now calledBaseFieldDefinition
so no I don't think #2280639: Add the FieldStorageDefinition class to define field storage definitions in hook_entity_field_storage_info() is blocking this but it is all so confusing.Comment #90
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedFixing tests for the new class name.
Comment #91
dwkitchen CreditAttribution: dwkitchen at Johnson & Johnson commentedLots of work going on here and trying to figure out how to update my BundleFieldDefinitions to match this latest patch.
This assumes the FieldStorageDefinition comes from the same module. This might not be the case. The storage could be defined in multiple modules.
The simplest alternative I could find was
Would be good if you could just do
@jibran, agreed!
What happens if you try and create two fields with the same storage definition?
Comment #92
dwkitchen CreditAttribution: dwkitchen at Johnson & Johnson commentedHaving had a night to sleep on it I have been able to get my project back running using the latest patch.
I needed to have a FieldStorageDefinition class to be able to do
So I have created a patch over on #2280639; this makes that issue a blocker for this one.
Comment #93
dwkitchen CreditAttribution: dwkitchen at Johnson & Johnson commentedI needed to create a new patch for Composer to be able to apply after #2280639
Comment #94
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThe issues aren't blocking each other. A field definition is still useful stand-alone when used with storage provided by a base field. There is also other ways to implement field storage without a dedicated builder class, like the one in contrib for example.
Comment #95
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedReuploading #90 for clarity.
Comment #96
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedAgreed with @Sam152, this can go in separately from #2280639: Add the FieldStorageDefinition class to define field storage definitions in hook_entity_field_storage_info() because existing code can still use their custom classes.
What we really now is another opinion from an entity maintainer for the patch in #95 , let's try @Berdir first :)
Comment #97
dwkitchen CreditAttribution: dwkitchen at Johnson & Johnson commentedBut if its a base field it would already be there, so you wouldn't be adding it to the bundle - unless you can at the same base field to a bundle multiple times? This was my question in #91
What is that example, I couldn't spot it in in the history?
Oh I know the example now - here's a link for anyone else https://cgit.drupalcode.org/entity/tree/src/BundleFieldDefinition.php?h=...
I guess, it is not truly blocked, it's just infinitely more useful if #2280639: Add the FieldStorageDefinition class to define field storage definitions in hook_entity_field_storage_info() makes it in as well.
I have added a patch with the amendment I proposed in #91 as this was what @jibran and @tstoeckler said in #66 and #67.
Don't know what I'm doing different, but this should be the only diif in the patches.
Comment #99
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commented@dwkitchen, yes, it is more useful when we also have a field storage builder, that's why we have an issue for it. "Not blocking" meanings both can be worked on in parallel and will both hopefully be committed.
@tstoeckler explicitly disagreed with calling
getFieldStorageDefinitions
in #67.When you add a bundle field matching a base field storage definition, the bundle definition becomes the canonical definition
. You could for example have a base field with the label "Foo" and override the label to be "Bar" for one bundle only.
Not sure why the interdiff is being messed up, right now they are really hard to read. There are steps documented here if that's helpful.
In the meantime, lets please be aware of scope. Adding this building block doesn't mean all work has to stop on this once it goes in. We have the option to continue to refine and build on the various docs and ancillary classes in the future.
Needs review for #95.
Comment #100
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedI reviewed the patch again thoroughly and I think it's ready to go. We are at the beginning of the 8.7.x cycle so we have enough time to spot any possible regression in contrib.
Another review from other entity maintainers and @bojanz would still be very helpful, but I don't think it needs to block progress in this area that has been neglected since 8.0.0 :)
Re-uploading #95 again so it's clear which patch is RTBC.
Comment #101
jibranIt's about damn time.
Edit: It is pointed to me that my comment was a bit harsh. I was in no way criticizing people for neglecting this part of the core. I have a huge respect for all the entity/field API maintainers & contributors and I would never criticize anyone on not working on something. It was just a joyous comment.
Edit 2: It is also pointed to me that my comment can also be offensive to religious people so once again I amended my comment.
Comment #102
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commented@amateescu Thanks again for the review.
Comment #103
bojanz CreditAttribution: bojanz at Centarro for Ny Media AS commentedI'm a bit sad that we abandoned the pattern promoted by entity_test before (having a BundleFieldDefinition that's a parallel to BaseFieldDefinition, providing both the definition and the storage). It means that Commerce and all users of Entity API's Bundle Plugins will now be on an API that's different to the core one. But it's not the end of the world, we'll just keep our \Drupal\entity\BundleFieldDefinition.
There is one DX consequence that should be addressed somewhere, the need to rewrite the docblock for FieldableEntityInterface::bundleFieldDefinitions():
It says that it can be used both for overriding base fields, and for providing bundle-specific fields. After this patch, it only makes sense to use it for overriding base fields, since bundle-specific fields now require a storage that can only be provided from a hook. So it makes sense for both the storage and the definition to be provided from the appropriate hooks.
Comment #104
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@bojanz, the class we are introducing here does not mention "bundle" in its name, it's only a builder class for
FieldDefinitionInterface
, so I think we can still add something like a "BundleFieldDefinition
" class in core, which would work like the one provided by the Entity module in contrib. Would that be too confusing naming wise?Maybe we should first discuss this whole problem space properly in #2346347: Finalize API for creating, overriding, and altering code-defined bundle fields, and only implement the decisions taken there in sub-issues like this one..
Comment #105
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI think #41 to #44 touch on some of the reasoning for making the builder a definition only, I think it boiled down to: two field definitions attached to two different bundles require the same storage definition, so storage related things aren't mutable and instead reference a common item of storage.
I don't think necessarily introducing a definition builder would stop you from creating builders which are composites of both storages and definitions. Internally BundleFieldDefinition/BaseFieldDefinition could maintain their own individual definition and storage objects if we went ahead with this issue and #2280639: Add the FieldStorageDefinition class to define field storage definitions in hook_entity_field_storage_info().
Doing so would allow us to continue to use patterns like in
entity.module
andBaseFieldDefinition
where appropriate (ie, we are implementing storage hooks behind the scenes for users) or allow them to be used directly where scenarios favour individual storage/definition implementations.It would be good to discuss further.
Comment #106
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI think keeping that level of granularity also has some parallels to #1426804: Allow field storages to be persisted when they have no fields.:
...
So, maintaining the granularity (optionally) seems a bit analogous to the API version of
persist_with_no_fields
.Comment #107
jibranAdded IS template.
Comment #108
alexpottThere is a lot of shared code between \Drupal\Core\Field\BaseFieldDefinition and \Drupal\Core\Field\FieldDefinition. For example ::setDisplayOptions() and ::getDefaultValueCallback()
It makes me wonder if there should be an abstract parent of FieldDefinition and BaseFieldDefinition. Also with the current naming I'm left wondering why BaseFieldDefinition doesn't extend from FieldDefinition.
Leaving at RBTC as there is no actionable comment - just thoughts that might prompt some changes by the patch authors.
Comment #109
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThanks for the review @alexpott!
My take on that is,
BaseFieldDefinition
is equally a definition and a storage definition. Once we have #2280639: Add the FieldStorageDefinition class to define field storage definitions in hook_entity_field_storage_info(), there will probably also be a lot of code that is duplicated there too. I think an ideal implementation would be thatBaseFieldDefinition
would be a composite of both, implement both interfaces and delegate its own methods to each where applicable, to definition and storage objects managed internally.Comment #110
Wim LeersIf #2943899: Moderation state field cannot be updated via REST, because special handling in ModerationStateFieldItemList lands before this, we should update the patch here to also apply the "attempt 1" patch at #2943899-44: Moderation state field cannot be updated via REST, because special handling in ModerationStateFieldItemList.
Comment #111
apadernoI take the previous comment means Needs work as status.
Comment #112
apadernoOr maybe not. (I apologize: I take I didn't understand it correctly.)
Comment #113
kristiaanvandeneyndeLoving the work being done here, nice job!
Re #103 Would it be possible to, somewhere down the line, make both BaseFieldDefinition and BundleFieldDefiniton extend or use FieldDefinition to share that code and share some storage logic from either a trait or by having a storageDefinition property populated in the constructor that then leads to the same class tree but for storage?
E.g.:
Comment #114
plachJust a reroll for now
Comment #115
plachThis looks great, I read the whole discussion and I really like the solution we came up with: retrieving storage definitions is a neat way to minimize the redundancy implied by having to implement two hooks/methods. If I understand correctly the main use case here would be to override existing definitions in code, while to define a new bundle field from scratch we would implement a
BundleFieldDefinition
complementingBaseFieldDefinition
. However it would important to preserve the ability to ensure that two bundle fields with the same machine name will always share the same storage definition. Anyway, I'm wondering whether #2980922: Applicable bundle field definitions defined in code should not have to manually implement hook_entity_field_storage_info. could be the right place to do this.Regardless of where we actually do this, I think that would be the place to address the questions @alexpott raised in #108: on one hand I can see us implementing both
BaseFD
andBundleFD
as wrappers around a storage + field definition pair via composition, OTOH I can also see how a base/bundle field definition is a field definition having a storage definition associated, more or less what @kristiaanvandeneynde is suggesting in #113.Here are a few nitpicks as dressing ;)
Can we change this to call
mymodule_entity_field_storage_info($entity_type);
for consistency with the rest of the code? This would also have the advantage of not forcing us to keep the two examples in sync.Can we add a couple of lines quickly mentioning the difference between this and
BaseFieldOverride
? I guess we would say that the former is for code-driven overrides, while the latter uses config for them.Typo: "can be"
Since all classes will implement
FieldableEntityInterface::bundleFieldDefinitions()
one way or another, even if they return an empty array, what about changing this to say, something like:IMO "provided via" would read better than "implemented with" here.
The "name" property holds the machine name, it would be more correct to use
test_field_name
here."Creates" :)
It seems we also need to address #110, unless we create a dedicated follow-up for that.
Comment #116
plachIf my understanding in #115 is correct it would be good to expand the IS to provide some more details around the solution.
We also need a CR for this.
Comment #117
plachLiar
Comment #118
jibranDo we need to document this somewhere?
Let's create follow up for #110. The changes required for that seems out of scope here. Addressed rest of the review from #115.
Comment #119
plachYes, we should definitely mention that in the PHP docs.
Comment #120
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedAdded the follow up here: #3012944: [PP-1] Use a FieldDefinition to customise the required status of the moderation_state field conditionally per bundle..
Comment #121
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedRe: #108 and #115, I had a similar thought in #2280639: Add the FieldStorageDefinition class to define field storage definitions in hook_entity_field_storage_info():
My take on this so far is that both
FieldDefinition
andFieldStorageDefinition
are useful stand-alone components. While discussing the role of(Base|Bundle)FieldDefinition
, we're not really proposing any public API changes, so really the question is around implementation details: should either make some use of these other two primitives?Since it's purely a discussion about implementation, someone could easily evaluate and experiment with composition or inheritance based approaches, but I don't think that necessarily has to block either this issue or #2280639: Add the FieldStorageDefinition class to define field storage definitions in hook_entity_field_storage_info().
Comment #122
plachI agree, let's fix that last bit of documentation mentioned in #119 and get this in.
Comment #123
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThanks for the reviews everyone. Adding some docs as requested.
Comment #124
jibranLet's get this in.
Comment #125
kristiaanvandeneyndeThis will go a long way towards a sane DX for working with bundle fields. +1
Comment #126
plachSaving credits
Comment #128
plachCommitted 98d7171 and pushed to 8.7.x. Thanks!
I also published the CR, onto the follow-ups now!
Comment #129
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThanks for reviewing @plach! :)
Comment #130
plachYou're welcome :)
Comment #132
plach