Spin-off from #2164601: Stop auto-creating FieldItems on mere reading of $entity->field[N]
Problem
The notion of "computed property" is pretty clear - typically, $field->field_ref->entity.
This is defined at the level of a property definition, for example in FieldItemInterface::propertyDefinitions() :
$properties['entity'] = DataReferenceDefinition::create('entity')
->setComputed(TRUE);
This means the property value will never be stored, and is lazy-computed on request by some custom code in the corresponding Data class.
The notion of "computed field" is still quite shaky.
It can be defined at the level of a field definition, for example in FieldableEntityInterface::baseFieldDefinitions() :
$field['foo'] = BaseFieldDefinition::create('string')
->setComputed(TRUE);
But the actual effect of that is quite unclear at the moment. The only sure thing is that storage will never try to store the field values.
Proposal
This is the current state of our discussions with @fago in Ghent. For clarity, this tries to expose the concepts of "computed properties" (pretty much OK in HEAD currently) and "computed field" (still largely TBD) side by side.
Computed property
Sample case: EntityReferenceItem->entity
Behavior: don't save or load the value of that property when saving/loading the items, the value is populated on read at runtime.
The existence of a computed property is not field-by-field, it is determined by the field type, in the propertyDefinition() method of its Item class. It necessarily relies on the property definition providing a custom property class that implements the "compute the value" logic.
Also, the schema() method will likely omit to declare a schema column for that property, so nothing can be stored for that property anyway (well, at least in SQL - schemaless storage probably require some code to remove the value before storing the entity).
Computed field
Sample case: a "back reference" field (a field that lists all the entities that have an entity_ref field pointing to that entity)
Behavior : don't save or load *anything*, we don't know whether there are items and how many, that will be determined on read at runtime.
--> In other words, the "computed" thing here is the FieldItemList.
This is field-by-field, specified by the individual field definitions, and is not tied to the field type. You can define a computed field of any existing field type. That necessarily relies on the field definition providing a custom FieldItemList class, that implements the "compute the existing items" logic.
This raises the question : how can a configurable field be made "computed", since the only way to specify that custom List class currently is in runtime code ? A back_ref field would typically be manually added by site admins, and would thus need to be configurable...
Also, the field type can happen to have computed properties, that's orthogonal. For example, a back_reference field could be a field of type entity_ref, with a computed ->entity property in each item that loads the referencing entity (that's for illustration's sake, in reality it would probably have more specific needs and be a separate field type that extends entity_ref).
In short, the two "computed" operate at different levels:
- computed field means : don't store anything, I'll tell you which Items exist at runtime
- computed property means : in each item that exist (however they happen to exist, stored or computed), don't store that property value, I'll tell you the value at runtime.
Remaining tasks
Create test-passing patches implementing progressively more complete computed fields:
- Base field, manually computed.
- Base field, manually computed, configurable.
- Field type, annotated as computed, configurable.
Comment | File | Size | Author |
---|---|---|---|
#166 | interdiff-8.5.x.txt | 1.5 KB | amateescu |
#166 | 2392845-166-8.5.x.patch | 14.6 KB | amateescu |
#164 | interdiff-8.4.x.txt | 499 bytes | amateescu |
#164 | 2392845-164-8.4.x.patch | 12.34 KB | amateescu |
#164 | interdiff-8.5.x.txt | 1.31 KB | amateescu |
Comments
Comment #1
yched CreditAttribution: yched commentedBump for #2401117: Ensure computed field definitions always have custom storage.
I updated the IS with the current state of our discussions with @fago in Ghent.
Comment #2
yched CreditAttribution: yched commentedComment #3
yched CreditAttribution: yched commented@penyastiko asked how he could help.
I think one way to move forward would be to try writing a sample "computed FieldItemList" class for a simple case - keeping the "entity back reference" case in mind, but simpler (without actual db queries to fetch the results).
Could be something like: a FieldItemListIntegerRange class for a computed integer field, with values = range from 1 to "the value of some other integer field in the entity" (and empty / no value if the controlling field is empty or 0). For simplicity, the name of that controlling field can be hardcoded in the FieldItemListIntegerRange.
Comment #4
yched CreditAttribution: yched commentedIn terms of code, the first change that comes to mind would be that ItemList::get() needs to stop auto-creating an item for computed fields :
If you're a computed field, we don't know in advance if your ItemList has Items, and how many. This is entirely up to the custom FieldItemList class that does the computation.
This also spills to any method that tries to determine if the field is empty, or to count its items...
Comment #5
penyaskitoDemo field.
Comment #6
penyaskitoThis patch instead of throwing the dice a random number of times, uses the value defined by a different field.
Comment #7
penyaskitoHope I understood your idea, yched.
Attached patch includes a computed field, which depends on a different field. This is a working solution, I highlight the things that a computed field writer will struggle with:
I would expect this is not needed.
Shouldn't this be easier/less verbose? What's that flag about?
Let me know how we can move further and I'll do my best.
Comment #8
yched CreditAttribution: yched commentedHey, I didn't expect you to be so quick :-) @penyaskito++ !
Having a quick look at it (sunday, not supposed to be in front of my keyboard :-p)
- We should remove the "dice" field type (DiceFieldItem). The idea is that you can define a "computed field" of any existing field type (in our case here, integer). Its' the field *definition* that says:
I'm an integer field :
new BFD('integer')
,I'm computed :
->setComputed(TRUE)
,and my "computed" logic lies in this custom ItemList class :
->setClass($class_name)
(not really obvious, but this one talks about the *list* class)- Then we can remove the custom formatter - that's the whole idea : a computed field just has custom logic to determine the values at runtime, other than that it's a regular field that can use the regular formatters for its field type.
- Once we get that working for base fields, we'll have to figure out how this can work for configurable fields... The current field_config / field_storage_config entities have no entries (and no UI...) for "I'm computed" / "This is my custom list class".
- Also, there is no randomness here, so we should rename DiceFieldItemList to RangeFieldItemList :-)
Comment #9
penyaskitoRemoved the field item and formatter. Used
hook_entity_base_field_info
for adding both fields.The field is not shown, still have to figure out why.
Comment #10
yched CreditAttribution: yched commentedThis is the crux of the issue here, we need to create the Items at some point. The intent of this issue is to find the various entry points that we need to plug (offsetGet, count, isEmpty...), and see if we can shape that into a base class to help other implementations of "a computed FieldItemList that determines its actual values at runtime"
Comment #11
Alumei CreditAttribution: Alumei commentedIt seems regular fields get their values set by the TypedDataManager when they are first accessed in getPropertyInstance(). So I thought that place would be optimal to create the items.
Created a new list class FieldItemListComputed that has two methods:
In getPropertyInstance() the field is checked whether it uses FieldItemListComputed and is instructed to populate it's values with computeItems().
I moved the computation logic in DiceFieldItemList from getValue to computeItemValues(). I also removed the overriden getValue() and count() because computeItems() writes the values into the $list property so their implementations in FieldItemList should give the desired result.
Also FieldItemListComputed has a bunch of ovverriden methods which should enforce that the computed list can't be changed from the outside of the class.
Do these changes make sense?
Comment #12
penyaskitoThanks for working on this, @Alumei!
I like it, would love @yched view on this.
Comment #13
Alumei CreditAttribution: Alumei commentedSadly the patch above is not the full solution as it works for base-fields only. I did some additional work to make it work for configurable fields but haven't come around to making a patch out of it ...
Working on it now.
I found a solution that works for fields that use a custom field type as those can specify their list class in their annotation. Sadly this enables computes configurable fields for custom field types only, but its a start ;-)
Comment #14
Alumei CreditAttribution: Alumei commentedApparently my solution wasn't finished yet -_-
While trying to incorporate a computed configurable field into the test module I encountered several problems e.g.:
- views integration does not account for computed fields
- when uninstalling and validating the uninstallation of a module computed fields are checked for content on which the storage errors out because there is no data in the db for computed fields
Sadly is seems like I messed up my code base and I don't know when I will have time to work on it again.
Comment #16
Alumei CreditAttribution: Alumei commentedA fresh start really helped ;-)
I created a small increment on top of my previous patch containing:
list_class = "\Drupal\field_computed_test\Plugin\Field\FieldType\DiceItemList"
.This should make the "computed field" use-case for base-fields pretty much usable.
As I wrote before: In theory these changes could work for configurable fields as well (At least for custom field types). But there probably will be problems with the UI and with FieldConfig & FieldStorageConfig though i haven't tested it yet.
Comment #17
Alumei CreditAttribution: Alumei commentedJust did a fast test for configurable fields:
I had that problem in a previous version but don't know why that happens.
Comment #22
Alumei CreditAttribution: Alumei commentedTried to fix most of the test fails and it also contains the fixed schema for the custom field type.
If I did everything right it should come down to two fails, but I have not particular experience in writing tests so ...
Comment #23
Alumei CreditAttribution: Alumei commentedFor testbot ...
Comment #25
Alumei CreditAttribution: Alumei commentedI think this fixes the last two fails.
I assumed for both patches that they only failed because isComputed() calls getClass() which uses the
typed_data_manager
service which was in all instances not part of the testing container.Comment #26
Alumei CreditAttribution: Alumei commentedIt's getting late -_-
Gn8 @ testbot
Comment #30
yched CreditAttribution: yched commentedThanks a lot @Alumei, and sorry for the lack of feedback, busy days with client work :-/
This is one of the things I plan to focus on at Dev Days next week.
Comment #31
Alumei CreditAttribution: Alumei commentedNp ;-)
Got inspired by #1548204-88: Remove user signature and move it to contrib and thought of a computed field approach. Then I remembered stumbling upon this issue and checked back on it.
Nevertheless I finally found the reason why this still fails. Apparently I overlooked that the EntityAdapterUnitTest already defined a typed-data-manager and i accidentally replaced it with my lines further down ...
Let's see if it works now.
Comment #32
Alumei CreditAttribution: Alumei commentedFinally got computed fields to work .. even for configurable fields.
Wanted to go into detail but it really is to late now :-/
@yched I hope the patch is enough for now if you get the time at the dev days, but will try to follow up tomorrow.
Comment #33
Alumei CreditAttribution: Alumei commentedThese are the changes from the last patch:
FieldItemListComputedInterface
now extends fromFieldItemListInterface
to emphasise that the computed item is a specal case of an item listReflectionClass
based subclass checks to useis_subclass_off()
insteadFieldConfig
and implemented theisComputed()
method to check against the interface and cache its result in the new$computed
propertyisReadOnly()
inside ofFieldConfig
to return the result ofisComputed()
analogue to the implementation inBaseFieldDefinition
$computed
propery toFieldStorageConfig
and also added a protectedisComputed()
method that works the same as inFieldConfig
(Would be nice if it could be public and on the interface t=so that it could be reused onFieldConfig
but wasn't sure whether that interface change would be appropriate)isQueryable()
andhasCustomStorage()
inside ofFieldStorageConfig
to use the result ofisComputed()
analogue to the implementation inBaseFieldDefinition
hasData()
onFieldStorageConfig
to only check the database for data if the field is not computed and return false for that case.FieldStorageAddForm
to skip the field storage config and field config forms for computed fields (This made more sense for me when I implementet it. The intention was that computed fields may not need to configure anything and in that case the only important data is the field name & label, which is already specified at that point. Nevertheless both forms are still accessible from the field overview)Comment #34
Alumei CreditAttribution: Alumei commentedObvious problems I know exists atm are:
Comment #35
Alumei CreditAttribution: Alumei commentedBased on the last patch on needs to implement the following to realize a computed fields:
FieldItemListComputedInterface
.computeItems()
.computeItemValues()
instead. IncomputeItemValues()
you specify an array keyed by delta. where each value is an array again containing the values for an field-item and that is keyed by property name. This makes it possible to easily create a custom field-type for computed fields in the same way you would for a normal fields or provide the correct data structure to be able to reuse an existing field type.list_class = ""
in the filed type annotation to point at the item-class from 1. (This is necessary because list_class can not be specified in the UI only in the annotation and in code)Should the widget requirement be removed from field UI for computed fields? I think that requirement is bad DX for computed fields as they usually should not require the input of data in an edit or insert form (In those cases a computed field would probably be the wrong choice and computed properties may be more appropriate). I would propose not to remove widgets for computed fields but make them optional instead.
Comment #38
tim.plunkettAttempted reroll.
Comment #42
lokapujyareroll
Comment #44
guy_schneerson CreditAttribution: guy_schneerson commentedI have an urgent need for a computed field for a contrib module see #2738807: Stabilize the stock level computed field implementation
Anyone know of an alternative approach?
Comment #45
joachim CreditAttribution: joachim commentedQuite a few contrib modules could make use of computed entityreference fields. I tried making one for Group module, and it didn't seem to be possible: #2718195: Add a computed field for entity's group(s).
Comment #46
vprocessor CreditAttribution: vprocessor at Skilld commentedrerolled
Comment #47
vprocessor CreditAttribution: vprocessor at Skilld commentedComment #48
vprocessor CreditAttribution: vprocessor at Skilld commentedComment #49
vprocessor CreditAttribution: vprocessor at Skilld commentedComment #51
vprocessor CreditAttribution: vprocessor at Skilld commentedComment #52
fagoThanks Alumei for all the work done here already!
ad encountered bugs:
I think the best way to proceed here would be to get an example implementation + base class committed first and then open follow-up bug reports for any remaining issues not causing test fails, and addressing them individually while improving test coverage.
I took a look at the patch:
I don't think we should introduce a new interface. So far we have not interface that computed fields or properties must implement and I do not really see a need for having it as computeItems() shuold be only called internally, not?
Interesting idea, but why is that necessary? When someone sets a custom class for a field to become computed, one can set the flag as well?
Is it for allowing a field type to be computed by default? That's a different feature/topic which I'd prefer to deal separately with.
seems useless, should stick with default integer formatters.
hm, a widget for a computed field generally seems wrong -> we should throw exceptions when that is configured. OR, are there valid use cases for having that, e.g. embedding a form for editing computed data?
Summarizing I think we should go step by step and reduce the scope of the first patch to a single base field, that's manually defined computed. Then, we can add another configurable, computed one. And finally, as last step the field type being computed.
Comment #53
vprocessor CreditAttribution: vprocessor at Skilld commentedComment #55
steveoliver CreditAttribution: steveoliver commentedUpdated issue summary and re-rolled for 8.2.x. I'll be looking at this today, seeing if I can create a patch for a manually computed base field first.
Comment #56
steveoliver CreditAttribution: steveoliver commentedLet's see what testbot has to say.
Comment #58
steveoliver CreditAttribution: steveoliver commentedUnassigning myself as I don't have time to work on this now and didn't make any progress since last week.
Comment #59
Wim LeersThe semantics of "computed fields" being unclear also is getting in the way at #2826101-8: Add a ReadOnly constraint validation and use it automatically for read-only entity fields.
Comment #60
Wim LeersComment #62
seanBNew reroll against 8.4.x. Also fixed comments from #52 and minor fixes I noticed.
FieldItemListComputedInterface
isComputedByClassInheritance()
related logic fromListDataDefinition
(this also seemed to fix the tests!)DiceFormatter
andDiceWidget
Comment #63
seanBWhoops!
Comment #64
seanBComment #65
seanBWhile using this to create a computed entity reference field
BaseFieldDefinition::create('entity_reference')
inhook_entity_base_field_info()
, I found that the class implementing the computed field needs to extendEntityReferenceFieldItemList
. This off course means I can't useFieldItemListComputed
. Other field types might have the same problem.This leads me to believe we should probably only define a
FieldItemListComputedInterface
which computed field should implement.Comment #66
seanBSomething like this should do it. Renamed
computeItems()
tocomputeValue()
to be more in line with the regularsetValue()
method for non computed fields.Comment #67
hchonovIf I am not mistaken then this will compute the values when the field item list is being initialized, but the values have to be computed only when they are needed/accessed, right?
Let's convert this simply to:
$this->setValue($values);
A traditional for loop is faster than foreach + range.
Re #65:
About
@fago, has said in #52
I think we should put the computeValue method into the FieldItemListInterface instead and in the implementation just check if the field definition is computed and only in this case compute the values, otherwise do nothing.
Comment #68
Wim LeersThanks so much for pushing this forward again!
Comment #69
seanB#67.1 I think so. Not sure if this is a bad thing. Do you have suggestions on how to improve this?
#67.2 Done
#67.3 Done
I think adding
computeValue()
toFieldItemListInterface
breaks BC. Having a interface only for computed fields seems to be a clean solution to deal with this. I like the idea of checking theisComputed()
method from the definition inTypedDataManager
, but we need a way to enforce the implementation of thecomputeValue()
method. If there is a way we can avoid the new interface without breaking BC, I'm open to suggestions.We could maybe use the
getValue()
method and skipcomputeValue()
? When reading the documentation it seemed this is the only method computed fields need to implement anyway? Not sure if this would break anything though.Comment #70
hchonovIt might be a bad thing if the computed field has to perform complex computations. The example form the issue summary - a "back reference" field (a field that lists all the entities that have an entity_ref field pointing to that entity) - will execute an entity query each time an entity with such a field is loaded, but probably the field will be accessed only in some cases e.g. on entity form edit, but not on entity view.
If we want to solve this for all the cases e.g. cover the use case where the value is retrieved directly from the property instead from the field item list or from the field item - $field_item->get($property_name)->getValue() - then the only place where we could implement this logic is in TypedData::getValue(), however we don't have any field related logic in TypedData. Probably we could add something like the following code there :
but I guess a lot of people might be against this...
There are good news about this, because it is not a BC break :). From #2862574-11: Add ability to track an entity object's dirty fields (and see if it has changed):
We add new methods to interfaces without having a BC break if there is a base class implementing the interface and here we have this relationship. This is described as well in https://www.drupal.org/core/d8-bc-policy under the section Interfaces within non-experimental, non-test modules not tagged with either @api or @internal.
Well this sounds good as well :). I don't think either that we really need a new method, expect we decide to use the solution I've proposed at the beginning of this comment about #67.1.
Comment #71
pwaterz CreditAttribution: pwaterz commentedHow does this solve the issue of FieldConfig's not being able to be computed? I.e. https://api.drupal.org/api/drupal/core%21modules%21field%21src%21Entity%... is hard coded to return FALSE.
Let me explain my use case:
Currently I am building out a framework where node types and fields are dynamically generated. Each node type has a set of fields that are stored in the database, custom_storage set to FALSE. Then there are a handful of field configs where there custom_storage is set to TRUE. It's a hybrid data model.
The custom storage field values are looked up from an external datasource(Cassandra) based on the field values that are stored in the database. Then their values are filled in via hook_entity_storage_load.
In addition, the fields that have custom storage set to TRUE are still queryable because we have implemented a new entityquery backend that detects when a query is ran against these bundles and queries a backend service that can handle the query (Elasticsearch).
At this point I have gotten all this working nicely. The issue I am facing now is that I want to create a search index from my entities via search_api. Search api relies on fieldconfig object to determine if an field is indexable via isComputed method, but it's hard coded to be FALSE.
In my opinion, fieldconfig should allow the following:
1. Allow the configuration to set isComputed
2. Allow the configuration to set isQueryable
If this is the wrong ticket or if I am taking the wrong direction to my problem please let me know, but I feel like it ties into this.
Comment #73
rjacobs CreditAttribution: rjacobs commentedFrom #14 and #52:
It looks like some related activity on the views-integration is happening at #2852067: Add support for rendering computed fields to the "field" views field handler.
From #52
I'm not sure if this is still an open question or not, but I do believe that having the ability to leverage the field API front-end, and related widgets, for computed data is quite compelling. I'm thinking specifically of custom DB storage needs that do not directly leverage Drupal field-based storage models. We have a case where several custom entity types have many-to-many relationships that are handled via custom mapping tables. Being able to expose these relationships (editorially) as a computed entity reference field, using the native entity reference widgets, is extremely useful. It's very easy to then use pre/postSave() to persist the data from the computed field widget in a custom way. I'm sure there are numerous other use cases where field API widgets can be an asset to custom computed storage needs.
Comment #74
nuezI've been working on some patches, I will clarify things later this week. My biggest considerations are:
setComputed()
andsetClass()
methods on the BaseFieldDefinition. Using existing field types we can use their validations too. I've added some examples in the testsI see i missed #69, I will revise that one later.
More later...
Comment #76
seanBCould you also add interdiffs for easier review?
Comment #77
nuezApologies I rushed that a bit too much and uploaded your patch in stead of interdiff. On the other hand I realise that I'm probably missing things that were mentioned already in this issue queue.
About #69
I think the computed result here is later overwritten by ContentEntityStorageBase::initFieldValues(), changing the computed value that was set earlier. That's why the tests that I added to your patch fail.
On the other hand I think that these values should be validated.
I'm not a big fan of random values in tests, as it introduces random errors and it makes it more difficult to test. Therefore I replaced this test with another, but I might have thrown the baby out with the bathwater. Will revise that later to see I can incorporate things.
Wouldn't it be better (thinking out loud) to return the values as an array and let a protected method set them. A 'computeValue' method that 'sets' something sounds confusing. Plus it requires the developer do make an extra step that we could do in a protected method. Just returning the array might be more intuitive.
I would also suggest to rename the method to 'computeValues'.
Comment #78
nuezAbout #79
This means the computed field is computed again and again everytime it is requested. The reason why I added this is because the entity might change during a single page request, and the computed value with it.
e.g. when creating an entity with a computed value based on its ID, the entity ID won't be available until the entity is saved, yet the computation - in the current setup - will have run before that. That's why it might be a good idea to force the computation to run again and again, and let the implementation define the way the computed value is stored.
I'm not sure this is still valid when implementing hchonov's suggestion in #70 of only computing the value when called from
TypedData::getValue()
Don't apply default values to computed fields.
This can go obviously.
Here I propose to let another method set the values returned by the
computeValues
method, for better developer experience. The developer just has to return an array with values.I also added field validation for computed fields, which should throw the right exception when the values returned don't match the data types.
The tests I've added are meant to check if we can get computed fields by just having one field item class, and if the values are correctly parsed and throw the right exceptions.
I realise that I haven't incorporated some of the things mentioned in #70 yet.
Comment #79
seanB#77.1 Haven't run into that yet, but it seems to make sense.
#77.2 Let's see what the testbot says.
#77.3 Sounds reasonable.
#78.1 I agree, we should document on the interface computed fields are responsible for their own caching.
#78.2 Sounds reasonable
#78.3:)
#78.4 I like it.
Fixed some small nits. Let's run the tests and see what will happen...
Comment #81
seanBThe test seem to fail because of the change in #9. Reverted the change in
Drupal\Core\TypedData\Plugin\DataType\ItemList
to fix it.Comment #83
seanBHmm, I think we found a bug in the ContentEntityBaseUnitTest? The test provided a string where
$this->fields
should be "The array of fields, each being an instance of FieldItemListInterface." (at least according to the docs). So I added a mocked FieldItemList and that fixed it. We definitely need some feedback from the entity system maintainers though since I'm not sure if this was actually a bug.Comment #84
seanBAnd a whole bunch of coding standard fixes while I was at it...
Comment #86
nuezI have tried this but have given up. I think that what would be required is not
$this->getParent()->getFieldDefinition()->isComputed
but$this->getParent()->getParent()->getFieldDefintion->isComputed()
as the flag is set on field item list level, and not on the item level itself. I've tried a lot of ways to letTypedData::getValue()
fetch the computed values of both a field property (typed Data) as well as a field item list, but It doesn't seem to be easy and is likely to require a lot of small interventions. (But I might lack the in-depth knowledge to achieve this).This would also be more coherent with the way computed values are fetched on Typed data level (see
TextProcessed
), and maybe serve integrations with Views etc too. This is implemented in this patch. Let's see what the testbot says.Agreed and implemented.
OK. If we don't have a 'computed' field item list interface anymore, we need to check for the isComputed flag. I've added a method to the TypedData class.
Comment #88
nuezSo it turns out that
ModerationStateFieldItemList
implements the computed field item list all along, correctly as it seems ,without having to patch anything. For it to work it seems to have to overwrite the following methods: get(), getIterator(), setValue(), onChange().Maybe the solution is as simple as to create a abstract base class
ComputedFieldItemListBase
that helps setting up these methods, so the implementation is less cumbersome for developers.I feel like we're getting a bit distracted by all the comments, and we need to go back to the issue summary:
Writing tests covering the following scenario
Added to that: updating https://www.drupal.org/docs/8/api/entity-api/dynamicvirtual-field-values... (that got me distracted too).
Comment #89
seanB+1 Nice find, I guess there should also be one for entity references with a
referencedEntities
method.Comment #90
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI proposed #88 in #2857301: Make the process of creating computed fields less bug prone.
Comment #91
nuezThanks @Sam152 for the hint.
I've practically copy pasted your solution (added the validation I mentioned in #74) and ran the tests and they all pass. Hooray!
Not sure if I'm the person to do so, but I've added you to the to-be-credited list.
So we're getting closer: What's left now is
- Adding documentation to base class and tests.
- Writing some tests for configurable base fields and field types.
Comment #92
nuezI don't think I can add the credit. Not sure what the policy is, a maintainer will have to look at it.
Comment #93
seanBA lot of the coding standard fixes done in #84 are reverted again. You might want to take a look at that., Besides that, looks good!
One thing though, to generate
$this->list
, you need to addTypedData
and do something like acreateItem
to create theTypedData
from your values. We might want to add a wrapper for that as well. Developers just return an array of values in, let's say, acomputedValues()
method. ThecomputeFieldItemListValues()
takes the data and creates TypedData from that with something like:This might make it a little easier for developers?
Comment #94
nuez#93: coding standards: I'll have a look at it again when we have a patch ready for reviewing. thanks!
#93: wrapper: Agreed, as a matter of fact I'm working on that right now.
I'm going through a bunch of scenarios of fully computed and partially computed new field types that are configurable, and computed field item lists that are computed and configurable. Trying to write up tests for all scenarios that I can think of.
Comment #95
nuez1. About fully computed field types
I've added an example of a fully computed field type 'dice' based on the example by penyaskito. You can use the field configuration as a setting for the computed values.
Having a fully computed field type does however does create a series of problems, that we should handle in a follow up issue imho:
::schema()
to return an empty array of columns.We should somehow detect if all field properties are computed and then not create the table at all.
2. About computed field item lists and entity edit forms:
It is possible to expose computed field item lists in an Entity form. The forms will be populated with the computed values. The user can fill in new values, but they will obviously not be saved.
Should we add the 'disabled' attribute or show a message to the form element form element. Or is this something we leave to other developers. In any case I think this should go a follow-up issue.
3. About the changes in the unit tests
I haven't further revised the changes in the unit tests proposed earlier, but left them there in the tests.
4. About the coding standards of #84
The rest I've implemented the rest of the suggestions and ran everything through codesniffer.
Comment #97
seanBIn core there are exceptions, but I think snake case is used pretty consistently. So that would be a reason to change it. The coding standards were changed mostly to allow more freedom for contrib. Switching to lowerCamelCase in core is a separate discussion. I don't think this patch should deviate from the standard in core, and changing this should be a separate issue imho.
Again, consistency. Symfony uses the annotation a lot. Core at the moment uses
$this->setExpectedException()
. Changing this to annotations should also be done consistently and is a separate issue.Comment #98
nuezComment #99
nuez@seanB OK, thanks for the feedback. I will incorporate this as soon as we get feedback from the core maintainers.
Comment #100
nuezComment #101
nuezComment #102
nuezA new patch with the following improvements.
isEmpty()
method that now always returns FALSE. This is necessary to force the computation to be executed. If not it might not always work, for example in this issue: https://www.drupal.org/node/2912116.Comment #103
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedIn addition to the methods above, we need to add the call to
initValues
to thegetValues
method.Bumped into this in #2915398: The moderation_state field is not computed during the creation of a new entity translation..
Comment #104
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedSome thoughts on the progress of #102. Apologies if I've missed some context, the issue is quite long.
Why can we always make this assumption?
Is this how we handle validation for non computed fields? I don't think setting field values to something invalid throws an exception, so I don't think computing invalid values should either?
Is this now scope creep based on the new direction of the issue? We're only introducing a base class for computed fields, not properties right?
My instinct is we can rely on field types as an abstraction and trust
createItem
will behave for different field types as long as we're calling it without our computed values. Seems a little over-tested here, but I might be missing something.Comment #105
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThis issue is quite long indeed, but sadly no one actually took into consideration the path forward suggested by the entity field API maintainers years ago :(
Quoting @yched in #10:
A part of that suggestion won't work, this can not be a base class (like the one implemented in the latest patches) because some field types provide their own field item list class (for example
\Drupal\Core\Field\EntityReferenceFieldItemList
), so a computed entity reference field can not extend both the existing field item list class and the computed one at the same time. This leaves us with a single choice: a computed item list trait :)As for the entry points, there are a lot more than those discovered in this issue so far. I looked at the class hierarchy starting from
\Drupal\Core\Field\FieldItemList
and going up to its parents (\Drupal\Core\TypedData\Plugin\DataType\ItemList
,\Drupal\Core\TypedData\TypedData
, etc.) and this seems to be a complete list of entry points for a computed item list:The criteria for adding a method to that list was: "does this method do something with
$this->list
?". If yes, we need to compute the values first.Now that we have a complete list of entry points, we can go further and look at @fago's comment from #52:
He suggests that this issue should only be about implementing a computed base field, and I fully agree with that. The problem space is already very complicated without thinking about computed configurable fields and computed field types at the same time.
Thankfully, we now have a test computed base field in core since #2852067: Add support for rendering computed fields to the "field" views field handler, so we can simply use that for writing full test coverage for the trait that I'm proposing.
Finally, here's a patch that implements all of the above :)
I'm also reclassifying this as a major bug because the two non-testing computed base fields that we have in core right now,
\Drupal\path\Plugin\Field\FieldType\PathFieldItemList
and\Drupal\content_moderation\Plugin\Field\ModerationStateFieldItemList
are receiving a galore of bug reports, most of them being major:#2905524: The count() method used on PathFieldItemList behaves differently if isEmpty() has been called.
#2906600: FieldItemList::equals() doesn't work correctly for computed fields with custom storage
#2908674: When using get() method directly on PathItem the alias is not loaded
#2915398: The moderation_state field is not computed during the creation of a new entity translation.
Comment #106
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedWow, great analysis. Looking at all the touch points with
$this->list
certainly beats bumping into bugs in each method one by one :-/Side note, I wonder if there is any chance overriding
__get
and computing on reads of$this->list
. Maybe that would make methods on sub-classes ofFieldItemList
work that we don't know about.In any case, this looks way better than what we've got so far, so very excited about this.
Need some better docs here.
We should standardise on computed/calculated, do they mean the same thing? "Whether the values have already been computed or not." maybe?
Is this left over?
Comment #107
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedLooking a bit at the previous patches, there was one thing that should be done here: don't create an empty item automatically for computed fields anymore. This also revealed a bug in the test coverage.
Edit: cross-post with #106, I'll address that in the next comment.
Comment #108
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@Sam152, whoa, that was fast :)
__get()
calls throughfirst()
, which in turns callsget()
, so we should be fine. All subclasses ofFieldItemList
that are computed will get everything for free when they start using the trait.1. Already fixed in #107.
2. Right, let's standardize on 'computed'.
3. Yes, I always forget to remove that..
Comment #111
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThis is looking really good. I think the issue summary needs to be rejigged if we're just addressing computing FieldItems in this issue and not properties. Huge +1 generally, this would have prevent a lot of bugs in content_moderation.
Comment #112
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@Sam152, the issue summary only mentions computed properties as something that already "makes sense" in HEAD, as opposed to field item lists which have an undefined behavior when they are computed.
I also posted two patches for updating
\Drupal\path\Plugin\Field\FieldType\PathFieldItemList
andModerationStateFieldItemList
to use the trait added here, which will bring two real-world use cases for it and will allow us to see if there are any hidden bugs before promoting it as the "official" way of implementing computed fields.#2915398-11: The moderation_state field is not computed during the creation of a new entity translation.
#2916300: Use ComputedFieldItemListTrait for the path field type
Comment #114
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThe test failures from #108 show that we can not remove the code that creates an empty item automatically for computed fields because that will be an API break for fields that are not using the new trait.
Comment #115
nuez@amateescu The analysis in #105 is absolutely spot on and really helpful. I was looking at the patch yesterday and came to the same conclusion that we cannot simply remove the creation of the empty item on computed fields. For the rest it looks very good to me.
I like the fact that the trait is on typed data level. However this issue is about facilitating computed field items. Can we assume this works on typed data level? Should we either move the trait to the field module or add tests on typed data level?
Comment #116
Wim Leers❤️
Comment #117
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@nuez, I think the integration tests from the patch are far more useful than some unit test of the trait at the typed data level. For example, it's a lot easier to test that setting a value and then calling a getter does not trigger a re-computation of the values.
Now, both of the followups have green patches: #2916300-8: Use ComputedFieldItemListTrait for the path field type and #2915398-16: The moderation_state field is not computed during the creation of a new entity translation., and, along with the computed test field that we extend here, we have enough proofs that we finally nailed the behavior of computed fields :)
Comment #118
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedWrote a change record to describe in detail what module developers need to do in order to implement a computed field properly: https://www.drupal.org/node/2916667
Comment #119
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedWill wait for any other reviewers who might want to have a look at this, but +1 RTBC.
Comment #120
nuez+1 RTBC from me too. Good work!
Comment #121
seanBAwesome work, let's go!
Comment #122
jibranPatch looks good. We need a change notice for this. Just one question.
Should we add an abstract method and introduce a new abstract class for computed fields?
Comment #123
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedChange record is here: https://www.drupal.org/node/2916667
The abstract method is on the trait. #105 describes:
Comment #124
larowlanshould we be calling the parent here?
Comment #125
jibranThanks, sorry for the noise for change record. Can we create an interface for all trait functions and also add
computeValue
function to the interface? Something like this:Comment #126
yched CreditAttribution: yched commentedSuper super thrilled that the issue has come to this point ! I was kind of hoping for something like this, but was not sure it would be doable. Thanks a ton for pushing this folks :-)
Two remarks :
Sad, those lines have always made me cringe ;-), but I get the BC argument in #114 that it needs to stay to preserve behavior for existing ItemList classes not using the new trait.
Maybe then it would deserve adjusting the comment to clarify that this code now only because of "old computed fields not using the trait"
?
Also, maybe the condition could move from "if (isComputed())" to "if (isComputed() && !instanceof ComputedItemListTrait)" ?
That was quite some time ago and this code might have gone since then, but wondering if we should check that "regular operations" (for example : just loading an entity, just saving it, just displaying it in a view mode / form mode that doesn't involve the computed field) do not trigger the computation
Maybe the tests added in the patch do account for that, in which case, never mind me :-)
Comment #127
kristiaanvandeneyndeFrom the issue summary:
Does that not void the current meaning of
FieldStorageDefinitionInterface::hasCustomStorage()
? That's a problem to fix in #2401117: Ensure computed field definitions always have custom storage, but I thought I'd raise it here before code goes in that makes that issue a nightmare to solve :)Awesome patch by the way!
Comment #128
larowlan@jibran
computeValue
is protected, no need for an interface.The rest of the methods already exist on FieldItemListInterface right?
Comment #129
jibranRight! so
will be just fine. Just for the record symfony has same pattern with
ContainerAwareInterface
andContainerAwareTrait
.Comment #130
larowlancomputeValue is protected - I don't think you need an interface for protected methods?
Comment #131
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThanks @yched for chiming in! As always, you asked all the right question :)
Re #126.1:
Sadly,
instanceof
does not work on traits, only on classes and interfaces. And since we're not introducing an interface here, we can not use that suggestion. However, there is something we can do in the trait itself: don't call the parentget()
anymore and just inline its code, which means that computed fields that implement the trait will not have an empty item added automatically, but still leave this behavior for "old" computed fields.#126.2:
Loved this question! Your reason for being worried is still true in HEAD, not because of an
isEmpty()
call (although that was very close), but because ofapplyDefaultValue()
which is called when creating an entity by\Drupal\Core\Entity\ContentEntityStorageBase::initFieldValues()
, which has this code that you will probably remember very well:This is actually the reason for the trait implementation of
applyDefaultValue()
which simply returns without calling the parent. This is also the answer to #124 :)However, after writing tests for this remark, I realized that what I wrote above is not enough because values are also being computed just before saving the entity by
\Drupal\Core\Field\FieldItemList::preSave()
which callsfilterEmptyItems()
which will trigger the computation through thefilter()
method.So we have to implement
filterEmptyItems()
in our trait, which is a bit sad because this is a method provided byFieldItemListInterface
, which in turn means our trait can not stay at the typed data level anymore. Oh well.. it was worth a try :)@jibran, the computation of the field's values is something internal to the field implementation, so it should not be publicly available to the outside code. This means the
computeValue()
needs to stay protected, which means no interface. This is also in line with @fago's thoughts from #52.1.The test-only patch does not contain the trait's
filterEmptyItems()
method, which will show how the added test coverage fails for the expectation that the values are never computed if no code interacts with the computed field.Comment #133
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@Wim Leers seems to be having some trouble with
FieldItemList::equals()
for computed fields in #2824851: EntityResource::patch() makes an incorrect assumption about entity keys, hence results in incorrect behavior, so let's make sure that we have some explicit test coverage for that here.Comment #134
jibranThanks for the explanation @larowlan and @amateescu!
Comment #135
Wim Leers#126 + #131: This is such an excellent, thorough discussion! 👏
#133: ❤️
Comment #136
yched CreditAttribution: yched commentedRe @amateescu #131 :
Re: Re: #126.1 :
❤️, much cleaner than an instanceof check
Re: Re: #126.2 :
applyDefaultValue() : HAH!, you bet I remember that code :-D. Pretty simple though, default values indeed don't make sense for computed fields, so +1 on overriding applyDefaultValue() to a no-op in the trait.
filterEmptyItems() : wondering if this should rather be plugged at the level of the more generic \Drupal\Core\TypedData\Plugin\DataType\ItemList::filter() helper : it probably needs that same fix, and that could allow the trait to remain at the TypedData\ItemList level ?
Side note though : if the trait is at the TypedData\ItemList, then I guess strictly speaking its code comments can't mention "fields" and "entities" ? possible meh :-)
Nit : computeValue() / doComputeValue()
The logic in other places where we have a pattern of X() / doX() is usually the other way around : X() being the outside facing API, holding some common logic and deferring to the internal, overridable doX().
Maybe s/doComputeValue()/ensureComputedValue() would be more accurate ? All the places that currently guard with doComputeValue() precisely do not necessarily trigger a recompute, that's the nice thing :-)
nit : something wrong around 'when there the field" ;-)
ubernit while I'm in there : patch contains both wordings "life cycle" / "lifecycle". Not sure which is best, but we seem to have slightly more of the latter in current core ?
(yeah I know, I haven't changed much it seems :-p)
Comment #137
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedRe #136:
Yes! That makes perfect sense. Somehow I missed that the more generic
filter()
doesn't need to compute the values for the following reasons:- when a computed field is instantiated (i.e. when an entity is created), the field is empty so there's nothing to filter
- when some code interacts with a computed field through a getter or a setter, the values will be computed so
filter()
will do its jobMeh indeed, but fixed anyway :)
Yup, I had that distinction in mind when I wrote the methods, but I preferred to have
computedValues()
as the "API" that computed fields need to override to calculate their values. Anyway, I like the suggestion to useensureComputedValue()
for the safeguard method so consider it done.I blame that on working late into the night.
Also fixed the 'lifecycle' ubernit ;)
And I hope you never do :D
Comment #138
Wim LeersThis blocks #2916300: Use ComputedFieldItemListTrait for the path field type, which blocks #2824851: EntityResource::patch() makes an incorrect assumption about entity keys, hence results in incorrect behavior.
Comment #139
Wim LeersAFAICT #137 addressed @yched's feedback in #136.
I tried to find even a single supernitpick, but failed. I almost wrote
s/lifecycle/life cycle/
, but turns out even Microsoft says "lifecycle", so screw macOS' built-in dictionary for saying otherwise.Just want to say, major kudos for this super clear, super valuable test coverage!
Comment #140
kristiaanvandeneyndeLoving this patch.
Maybe a small thing to note: Can we put a todo in the codebase along with this patch that reminds us of the BC we couldn't get around (see #114)? Might be cool to fix this for D9.
Comment #141
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@kristiaanvandeneynde, that's a good point, added the usual deprecation message and a
@trigger_error
as well.Also, sorry that I forgot to answer your earlier comment in #127. I did look at that issue and it is not impacted by this patch :)
Comment #142
yched CreditAttribution: yched commentedLooking at this again, I'm not sure I understand why the override is needed, actually.
@amateescu in #131 :
But ItemList::filter() simply does array_filter($this->list, $some_callback). So, as you wrote in #137, in itself filter() should never trigger a computation, and is simply a no-op on a not-yet-computed field, where $this->list is []. So it doesn't seem we need an override to opt-out early when !$this->valueComputed ?
In other words, I don't get what triggers the computation you noticed during FieldItemList::preSave() ?
+1 on the trigger_error deprecation warning, wondering if it we should remove the "$index == 0 && !isset($this->list[0])" part of the condition, and warn whenever a computed field doesn't use the trait. As is, it seems the warning will trigger a bit randomly ? (well, not randomly, but only in specific cases).
(probably not a blocker for the patch though)
Comment #143
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThis is a brown paper bag over my head situation :P It was the patch itself which was triggering the computation in
filter()
, as can be seen in the patch from #133 :) So, of course, you're right that we should simply remove thefilter()
override.As for removing the "$index == 0 && !isset($this->list[0])" part of the condition, I wouldn't do that at all (not even in a followup) because who knows what custom code we might break.
Comment #144
yched CreditAttribution: yched commentedHeh, ok, that makes more sense :-)
RTBC+1, awesome to see this fixed !
Comment #145
kristiaanvandeneyndeRTBC+1 as well. Thanks for taking the time to adjust the patch to meet all of the recent comments' remarks!
Comment #146
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedRe-reviewed #143, +1 RTBC.
This unblocks several bug fixes and improvements, would be great to get this in.
Comment #147
jibranQuoting @amateescu from #2543726-206: Make $term->parent behave like any other entity reference field, to fix REST and Migrate support and de-customize its Views integration
I think we need an issue title update because this trait can be used for custom storage fields as shown in #2921048: Introduce temporary work-around to make $term->parent work in REST API + JSON API in Drupal 8.5 — 8.6 ships with root cause fix.
Comment #148
catchShouldn't this be in a separate patch? I'd love to get rid of the behaviour but doesn't look necessary. Also the deprecation message is going to need to be 8.5.0 now.
Comment #149
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@catch, would you like to have separate patches for 8.4.x and 8.5.x? I agree that the deprecation could be 8.5.x only, but the trait provided by this patch is needed in 8.4.x because there are many bug reports that depend on it..
Comment #150
catchSorry I mis-typed, I meant putting the deprecation in a separate issue - it looks like scope creep here a bit.
However if this is targeted fro 8.4.x too should we also make the Trait @internal in 8.4.x? - then no-one can accuse us of adding a new API in a patch release.
Comment #151
BerdirPatch overall looks great, I really like the naming.
Only thing I'm not 100% sure about is deprecating this now, here, with a trigger_error(). This does seem to be triggered fairly often, removing the @ and going to node/add/artcile gives me 6 deprecated messages all from the applyDefaultValue() and 11 when saving. *
The thing is that @trigger_error() is fairly expensive, because what it does is jump to the error handler, which has to load errors.inc where error_reporting() returns 0 (this is what @ actually does) and then we go back.
Haven't done any actual benchmarks, I guess in the end it's not that much compared to all the crazy things we do in typed data when creating a new entity, but still wondering if we want to postpone the actual call (we could have it commented out or something) until we have a solution for our own calls and know that we actually *can* deprecate and eventually remove it.
* On my test site, thd calls on save have a number of different sources, 2x creating a new entity (the entity form re-creation and metatag), comment form, token_node_insert().
Comment #152
BerdirDid some more debugging, and I think it's fine, didn't fully understand the implications and that it is really only called for computed fields. Can't see the error handling show up in profiling and the 6 calls are from 3 fields, path (which will be committed soon after this), menu_link from token.module (which is a workaround until we have something like that in core to have menu link tokens during node save for pathauto, and we could also use this trait in 8.5, if we don't simply get into core directly) and the metatag field, which also does re-triggers the whole entity create.
As we'll simply remove the whole block there, even without doing anything about #2356623: Remove usages of FieldItem::applyDefaultValue() specifically, all performance implications will be resolved.
So, definitely +1, lets get it in :)
Comment #153
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@catch, IMO the deprecation is actually in the scope of this issue, so how about this:
- commit a patch to 8.4.x with the new trait marked as @internal and without the deprecation
- commit the patch as-is to 8.5.x with the deprecation message updated to mention 8.5.x
Comment #154
AnybodyYes, +1 for #153, makes sense. Thank you all for your great work on this! :)
Comment #155
kristiaanvandeneynde+1 too and nice way of dealing with the 8.4.x vs 8.5.x differences.
Comment #156
BerdirRe-testing, expecting this to fail now because of #2870194: Ensure that process-isolated tests can use Symfony's PHPunit bridge to catch usages of deprecated code if I understand that correctly?
Comment #157
catchYes that's another reason I wanted the deprecation in a separate issue but we can update the phpunit deprecation exclusion list on the assumption it fails if it really needs to be done here.
Comment #158
xjmTBH I don't see the point of marking the trait as internal; the point of not backporting additions is to avoid method collisions and the like.
@internal
is just a documentation convention; it is not API fairy dust that somehow changes the scope of the addition. ;) And if we mark things that are clearly intended as public API as@internal
, all that does is confuse the issue of what our API actually is.So if this is needed for multiple bugfixes, let's backport it, scope the issues as @catch suggested, inform developers with the CR, and evaluate the individual disruptions in the followup issues?
Thanks!
Comment #159
catchSo the reason for me for marking it @internal is because we're not using it in core yet. If we find something unexpected, that gives us room to update things before 8.5.x, if we're 100% happy after a couple of conversions, we can remove @internal in a later patch release of 8.4.x.
Comment #160
cburschkaI think the API addition thing goes beyond just collisions... as far as I understand (may be assuming wrongly here), semver patch releases are supposed to be forward compatible, so a module compatible with 8.4.3 should be compatible (bugs aside) with all 8.4.* versions. If we add this to 8.4.3 without a special designation, then contrib authors may use it and mistakenly declare >=8.4 compatibility when in fact they need >=8.4.3.
Though I agree that having @internal in there without explanation is confusing. It seems to say "this is not part of the API", but what we actually mean is "this will become part of the API, but it doesn't reliably exist in 8.4.*, so if you use it in contrib you need to require a particular patch release." Maybe just specifically state that it is added in 8.4.3 (possibly along with the @internal tag, to draw attention to it).
[crosspost with @catch]
Comment #161
xjm#160 makes sense also.
Maybe we can just commit the 8.5.x version for now, and consider a backport once we want to backport something that would depend on it? There's only a few more weeks in which it'd matter since 8.4.4 will be the last normal patch release of 8.4.x. (The February window is criticals only as per https://www.drupal.org/core/release-cycle-overview#current-development-c....)
Comment #163
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThese are the issues that are waiting for this, four bugs with two of them being major:
#2915398: The moderation_state field is not computed during the creation of a new entity translation.
#2916300: Use ComputedFieldItemListTrait for the path field type
#2824851: EntityResource::patch() makes an incorrect assumption about entity keys, hence results in incorrect behavior
#2921048: Introduce temporary work-around to make $term->parent work in REST API + JSON API in Drupal 8.5 — 8.6 ships with root cause fix
All of them have green patches thanks to this trait, isn't that enough validation?
Comment #164
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedOne last try. The 8.5.x patch needs to account for #2392845: Add a trait to standardize handling of computed item lists and I added some more information to the @internal doc of the trait for the 8.4.x patch.
Comment #166
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedComment #167
catchCommitted/pushed to both branches, really good to see this in.
Comment #169
catchComment #171
andypostCR published, thanx a lot for getting in
Comment #172
Wim LeersYAAYYYYYYY! This unblocked #2916300: Use ComputedFieldItemListTrait for the path field type, and made #2824851: EntityResource::patch() makes an incorrect assumption about entity keys, hence results in incorrect behavior less blocked.
Comment #173
yched CreditAttribution: yched commentedAwesome!
Comment #175
Wim LeersFirst (?) contrib use of this at #2939810: EntityResource::patchIndividual() should allow fields that the user is not allowed to change, as long as they match the current value! :)
Comment #176
Wim LeersComment #177
kristiaanvandeneynde@Wim We've also used this trait on client projects already.
Comment #178
claudiu.cristeaI've created a computed entity reference field and hoped that
$entity->get('computed_entity_reference_field')->referencedEntities()
will work. But it didn't. I've opened a followup to address the issue #2978848: EntityReferenceFieldItemList::referencedEntities() doesn't work for computed fields.