Problem/Motivation
This was raised as a review point in #2784921-135: Add Workspaces experimental module:
+++ b/core/modules/workspace/src/Entity/Workspace.php @@ -0,0 +1,223 @@ + ->setDefaultValueCallback('Drupal\workspace\Entity\Workspace::getCurrentUserId') ... + /** + * Default value callback for 'uid' base field definition. + * + * @see ::baseFieldDefinitions() + * + * @return int[] + * An array containing the ID of the current user. + */ + public static function getCurrentUserId() { + return [\Drupal::currentUser()->id()]; + }Node and Media also have this, this is the 3rd occurrence of this exact code + docblock in core, should we open a followup to figure out if it makes sens to move this to a common class, or a trait?
Proposed resolution
Add a EntityOwnerTrait, similar to EntityPublishedTrait.
Remaining tasks
Do it.
User interface changes
Nope.
API changes
Nope.
Data model changes
Nope.
| Comment | File | Size | Author |
|---|---|---|---|
| #97 | 2949964-97.patch | 33.79 KB | sam152 |
Comments
Comment #2
hchonovCould we instead simply handle this in
\Drupal\Core\Entity\ContentEntityBase::baseFieldDefinitions()?Comment #3
amateescu commentedI guess we could, but do you see any reason for handling this one differently?
Comment #4
hchonovFor a second I thought that we add the uid field in ContentEntityBase, but actually we don't. I am sorry. A new trait EntityOwnerTrait is fine then.
Comment #5
longwaveFirst pass at this. Not sure if we need new tests or if the existing coverage is enough, as this is just a refactor?
Comment #6
longwaveSomehow testbot tested Payment module instead of core?!
Comment #8
longwaveA silly copy-paste typo caused most of those errors.
Comment #10
longwaveSo this appears to be running into two main problems:
The two above problems cause the various update tests to fail because the entity definitions are no longer identical. Is it worth unifying the fields here so they all have the same definition, or is that out of scope?
Comment #11
berdir> 1. The field definitions for uid across entities vary quite a bit; some are revisionable, some are translatable, others are not, and most have a default value callback except file.
That's true for many fields defined in the ContentEntity base class (for revisionable/translatable), we can easily set them dynamically. We can't unify at least revision/translatable, default value we might have to.
> 2. added an entity key "uid", but for some entities this changes the definition from NULL to NOT NULL, this appears to be a symptom of
Yes, that is a known issue. The issue you linked isn't the sympton, just a way to provide an API to deal with that. The real problem is that all entity types are automatically considered to be required in the storage, which is wrong but really hard to change (if we change it, then we are affecting all existing and custom entity keys in the opposite way).
Comment #12
longwaveComment #14
berdirI don't think we should do this. This automatically expands the existing base class with new functionality and will throw an exception when the key is not defined.
Instead, each entity type should explicitly use the trait.
Comment #15
longwaveThanks, I also came to the same realisation just now. I've done some more rearranging and fixed some other issues, hopefully this is a bit more successful in the tests.
Comment #17
longwaveI don't understand the HAL+JSON and REST fails, unless it is something to do with fragile ordering in $patchProtectedFieldNames?
Comment #18
longwaveComment #19
longwaveNot really sure why I need to change EntityReferenceItemTest to make it pass.
Comment #21
timmillwoodLooks good, only one little query:
One could argue the opposite.
Comment #22
longwaveAny callback takes precedence over the literal value already, but maybe cleaning up the definition array is still worth doing?
Comment #23
amateescu commentedTBH, I wouldn't make that change unless it is absolutely necessary :)
A "uid" entity key name doesn't mean much, how about using "owner" instead?
Comment #24
berdirNode already has uid (for a performance optimization), I guess that's why we went with that.. We also introduced a new one for status/published, so agreed it makes sense here too. Luckily adding a second key for the same field doesn't result in a schema change.
Comment #25
timmillwoodWhen we introduced the EntityPublishedTrait we used the entity key 'published' even though node already used 'status', which means node now has 'status' and 'published', but newly publishable entity types like BlockContent only have 'published'. We could do the same with the 'owner' entity key.
Comment #26
longwaveI did wonder whether to use 'uid' or something like 'owner' but as Berdir says I reused 'uid' because it was already there. 'owner' does have more meaning though, so will work on that next.
Comment #27
longwaveComment #28
longwaveCopy-paste error.
Comment #31
sam152 commentedThanks for working on this, great refactor for core but also a huge productivity boost for projects which spin out a lot of entity types. Review as follows.
I think #21 and #23 still need to be addressed for this change.
We shouldn't need the last dump here. These were created to apply on top of each other cleanly (and optionally), so
drupal-8.4.0-content_moderation_installed.phpshould be enough.Super nit/style: it's pretty clear what is happening on these lines without the comments.
Perhaps this indicates a BC break. Was the default value of 'uid' NULL/empty before and is now 0? I wonder if this is important because of access control implications.
I always found [static::class, 'getCurrentUserId'] a bit more elegant, but this is purely style.
I think the comment in the trait should read 'owner' instead of 'uid'.
Comment #32
sam152 commentedAlso, it's a bit of a shame we can't add this to
EditorialContentEntityBase. Authors/owners seem quite essential for editorial related things, but I agree adding it for all existing extending entity types would be disruptive.Comment #33
sam152 commentedHey @longwave, hope you don't mind me picking up some of the points in the review. Here is a summary of some of the changes in the interdiff:
I believe this resolves the feedback around the default value callback as well as the BC concerns. The crux of this issue was:
BaseFieldDefinitionhas nounsetDefaultValueCallbackmethod, so previously whereFilewas defaulting to NULL, this patch wanted it owned by the current user. This rejiggs things by naming the default value callbackgetDefaultEntityOwnerwhich lets file entity safely override that toNULLwhich is correct in this circumstance.This will fail a test right now, but will pass once #2960054: content_moderation_post_update_update_cms_default_revisions fails if content_moderation was enabled but no entity types were being moderated is in.
This fixes
NodeOwnerTest. Entity keys seem to have their own cache onContentEntityBase(translatableEntityKeys and entityKeys).getEntityKeylooks up those values first, hence the stale data in the test case.Comment #35
sam152 commentedHm, I forgot about the
SqlContentEntityStorageSchemanuance that all entity keys haveNOT NULLapplied to their storage scheme. To maintain BC on the file entity, we need to ensureuidcan continue to benull.Comment #36
sam152 commentedComment #37
sam152 commentedLooks like
Commentneeds the same default value treatment.Comment #40
sam152 commentedLast test fix. I think this is ready for review. I think if everyone is happy with
getDefaultEntityOwner, we should probably keep the existinggetCurrentUserIdstatic methods as deprecated on the individual entity classes. Excluding them from the trait will ensure it doesn't propagate to other entity classes and in generalgetDefaultEntityOwneris named more appropriately and thus can be overridden for different purposes like in the case of Comment and File.Comment #41
amateescu commentedThis patch is looking really good! And I agree, let's keep the existing
getCurrentUserId()methods around as deprecated, who knows what custom base field definitions rely on them.The trait method documents the return value as an array so we should also return arrays in the Comment and File implementations.
We can use
assertEquals()here.I don't think we need the 'definition' part of this docblock :)
Comment #42
sam152 commentedThanks for the review!
1. Hm, maybe the return value of these should be "mixed". What does [NULL] mean in field api land? A FieldItemList with a single FieldItem with a NULL value? The trait could also probably not return an array, it doesn't add owner as a multi-value field, so not sure why arrays are involved at all.
2. Good catch fixed.
3. Agree.
Comment #43
sam152 commentedHm, still need to add the
getCurrentUserIdmethods back.Comment #44
timmillwoodHad a good look through (without reading backscroll properly) and the only thing I came up with is that we need to keep
getCurrentUserId, set them as deprecated, and callgetDefaultEntityOwner.Comment #45
sam152 commentedReintroducing the
getCurrentUserIdmethods and adding deprecations + tests. Also reverting an unrelated test change.Comment #47
sam152 commentedComment #48
berdirI'm wondering if we want to get rid of that description while we touch it, it's misleading at best anyway (if we ever display the widget then it will show there but it will *not* be the id, it will be an autocomplete widget).
Also, can't we dynamically make it translatable/revsionable like other such default field definitions? we have the entity type.
wondering if we want to have a follow-up 9.x issue to remove the duplicate uid keys that we have in some places? Their usages we could already remove.
it's just one method that's legacy but I guess we have to put it in on the class?
How do we differentiate between real legacy classes that we want to fully remove and those where we just want to remove a method?
I would expect that this happens automatically through \Drupal\Core\Entity\ContentEntityBase::onChange() ? Node didn't have to do this..
if we set it through the id anyway, we could just call setOwnerId(). However, I think maybe we should do set($key, $account), which works fine and $account could in theory be a new entity that hasn't been saved yet. We didn't do this either for node, but it wouldn't hurt.
Comment #49
sam152 commentedThank you for the review @Berdir!
1.1. I think we should aim to keep the field definitions 1 to 1 for the scope of this issue and file follow ups for definition changes.
1.2. I'm not sure what the impact of making uid translatable is. Different translations of one comment can have different authors? That may or may not be something we want to enforce in the trait, it may turn out to be something which broadly speaking doesn't make sense. Do all other entity types with authors also do this?
2. Sounds like a great idea.
3. I'm not entirely sure. Maybe we should add a new LegacyMediaTest to ONLY test deprecated methods, so the rest of the tests class is still covered by the deprecation listener?
4. Hm, definitely need to look into why this was required to make the tests green then.
5. This makes sense to me. Possibly need another test asserting setOwner works with unsaved entities too?
Comment #50
berdir1.1. Fair enough, I'd just really like those descriptions gone, but I see it's also still there on node...
1.2: Yes, they do, in fact there is even a uid field added by content_translation if it doesn't exist. If the entity type is translatable, the expecation is that this field is too. and if you don't like it, you can still override it. The trait is about providing the best-possible default. See langcode in \Drupal\Core\Entity\ContentEntityBase::baseFieldDefinitions(). In fact, I even wondered if the trait could have an optional argument for setting the form/view display settings too.
5. Yeah, not a big deal I guess, afailk nobody ever complained about this not working, but either we can avoid the code duplication or we keep it separate on purpose and then a test wouldn't hurt.
Comment #51
timmillwoodOpened #2961627: Improve entity owner base field definitions as a follow up for #48.1.
Comment #52
sam152 commented1.2: Great, we'll make owner translatable for any translatable entity types.
5. I'll check this out in some more detail shortly.
@longwave hope you don't mind me grabbing the assigned status.
Comment #53
berdir3. Not *exactly* the same, but see #6.2 on #2961691: Change SYMFONY_DEPRECATIONS_HELPER back to strict, that just adds a comment, so when we'll go through @legacy tests to remove them, we'll remember to not remove the whole thing. I think that's fine here too?
Comment #54
sam152 commentedI've logged #2961983: Allow entity keys to be deprecated for #48.2.
Comment #55
sam152 commentedRe: #53, I didn't realise you could add the
@legacyannotation to individual test methods. In our case, since we're adding a dedicated test method for our deprecated code, the whole thing will be safe to remove when we remove the deprecated code. So I don't think a comment is even necessary here. No other code inadvertently calls the deprecated methods.Comment #56
sam152 commentedLogging #2961986: The ContentEntityBase entity key cache is purged incorrectly when two keys exist for one field. for #48.4.
Uploading a progress patch which addresses:
@legacyannotation to our dedicated legacy test methods.This still leaves todo:
Sorry for the comment spam, quite a bit going on with this issue :)
Comment #57
sam152 commentedComment #58
sam152 commentedComment #59
sam152 commentedThis addresses the last todo based on @Berdir's feedback. I think we should support setting an unsaved user entity with
setOwneras suggested. We want the trait to be broadly useful, so I think it should support the same features as the field system and what you'd get with a standard$entity->setmakes sense.Also adding a combined patch with #2961986: The ContentEntityBase entity key cache is purged incorrectly when two keys exist for one field. and marking this issue as blocked.
Comment #61
sam152 commentedFixing tests. The owner on files are non-translatable by default.
Comment #62
berdirDon't set it unconditionally on the trait. Just like the example I referenced, you need to do it *only* if $entity_type->isTranslatable(), then this isn't necessary.
Comment #63
sam152 commentedI believe File entities are translatable, just not the
uidfield.I tested this and there didn't seem to be any consequences of setting a field to be translatable on a non translatable entity type, which led me to believe any additional logic would be unnecessary. Let me know if that isn't the case.
Comment #65
berdirNope, they are not :)
That true is the return value of print_r(), which "prints" false and then returns TRUE that it did that successfully :)
Comment #66
berdirAs discussed, this should be updated.
Comment #67
sam152 commentedGood catch! Sorry about that confusion, total brain fart on my behalf. Lets see how this goes.
The fails in #59 prove we have implicit test coverage for this. If you think we need more explicit coverage,
EntityTestalready implements the interface and the blocker already gives us control over the entity keys, we could switch the translatability of the entity type and check the field. I do think that is a bit overkill though.Comment #69
sam152 commentedBlocker is in.
Comment #70
dawehnerNice work!
Nice work to remove potential breakage
I'm curious whether this should call out to the "parent" method.
Maybe a naive question: Is there a reason this is not display/form configurable by default?
Comment #71
sam152 commentedThank you for reviewing!
1. :D
2. Do you mean the deprecated
getCurrentUserIdmethods should call out togetDefaultEntityOwner? If that's the case, I think the whole point of renaming it in the trait and allowing consumers to override it was the default owner !== the current user. They just happen to be the same thing in these cases.3. Media and Node are configurable while File, Comment and ContentModerationState are not, so I think it really comes down to what offers the best DX. Personally I usually have the owner hidden from forms and display for most custom entity types I've written, so I'd opt to keep them hidden, but I don't feel that strongly about it.
Comment #72
dawehnerI mean I get the point why it is hidden by default, but it this entire is display configurable thing seems to be some limitation for sitebuilders. We never know their particular usecases.
Comment #73
sam152 commentedPerhaps the trait should encourage it, developers do have the choice of altering the field definition if they like anyway.
One case for leaving it off could be security. For all access handlers which use the owner to make decisions, could making this editable be an issue in those cases? For Node and Media I think the owner field is wrapped around an "administer this thing" permission, but we aren't providing that by default here.
Comment #74
cilefen commentedComment #75
berdirI was wondering something similar in #50, however based on an argument passed to the field definition method. I don't think it should do it by default. One thing is that you can *not* mix defining it yourself and defining the widget, things will fatal due to unexpected form structures.
None of our existing traits/base classes define form/view display configuration so far.
Comment #76
sam152 commentedI'm not sure I like the idea of adding extra params to
ownerBaseFieldDefinitions. The entities in core are already making additional customisations to the field definition, why would some of those customisations be in the form of params to the helper and some be in the form of directly accessing the base field definition and making changes?Comment #77
berdirYeah, it's not something we did so far, agreed.
This might be the current behavior, but I actually think this is a bug, e.g. in the context of REST. See also #2860259: Move the comment hostname default value to a default value callback, I thought there was an issue about comment too but I couldn't find it.
I see the argument of not changing any existing behavior, but also wondering if we shouldn't just fix it as we have to touch it again afterwards. But I guess it will require some test changes/cleanup, so more out of scope changes.
There's no way that the current behavior of the file entity is by design for example. e.g. file_save_data() does set it to the current user, as does \Drupal\comment\CommentForm::buildEntity(). It's really quite unfortunate that we have to add a bunch of code and tests for that code just to ensure that we remain the current broken behavior. But I guess that is the way.. a follow-up maybe?
Comment #78
sam152 commentedA follow-up sounds reasonable because the issues in #77 should impact the trait in any way as far as I can tell. I think as far as BC goes, if we were to handle those issues down the track, we can comfortably update the return value of
getDefaultEntityOwnerif we're indeed changing the default owner.Comment #79
sam152 commentedUnassigning for the moment.
Comment #80
tstoecklerJust a minor point: It's not actually necessary to return an array here. It doesn't break anything, but we can return anything that
set()accepts. So I think in terms of naming and clarity I think it would make more sense to rename it to ....OwnerId() and just return the ID directly without wrapping it in an array.Comment #81
sam152 commented#80: If it supports everything that
set()supports, doesn't it do more than simply accept and ID? You could also return an entity in there too, no?As far as wrapping the IDs in an array is concerned, @amateescu requested that in #41 for consistency with the docblock. I suppose hinting
arrayis the best we can do, when in reality the field system is way more flexible than that.Comment #82
sam152 commentedFiling follow ups for the investigate the feasibility of fixing comment/file to behave different with regards to the owner here:
#2975218: Update the default file entity owner to the current user
#2975217: Update the default comment entity owner to the current user
Beyond those follow ups I don't think there are any more points of feedback, unless I've missed something. Anyone interested in another review of this issue?
Comment #83
sam152 commentedReolling
Comment #84
berdirI'm also not quite sure about the feedback from @tstoeckler, so I'll leave it to him to deploy on that.
The test results in both those follow-ups look pretty encouraging, I suppose I'm still secretly hoping that we could do that directly instead first keeping the column/default value as NULL and then changing it ;)
Comment #85
tstoecklerRe #81: Looking at #41 it seems that the complaint was just that the docs didn't match the documentation. So I don't think @amateescu would object to simplifying the method, as long as we update the docs, as well.
On the other hand, this is a very minor point so I don't feel strongly at all about it and we shouldn't hold anything up on it. I just think it's a way to both simplify the code and make it more readable.
Looked through the patch and it really does look great to me, nice work! One thing I noticed: We are converting all entity types directly here except for the newly introduced Workspace and EntityTest. Should we fix those as well, right off the bat? EntityTest could be annoying because we then have to add the owner key to about a billion entity types.
Comment #86
sam152 commentedSo in reality the docblock is "mixed" because of everything field API supports right? From there we can return an int to simplify the code, as well as indicate with a comment the range of values that are acceptable. I'd be happy to take that approach.
With regards to Workspace and EntityTest, I'd be happy to delegate those to follow-ups, simply to keep the scope of this issue manageable. I think the entity types that @longwave already converted prove the API sufficiently. Same preference with the follow-ups in #82.
Comment #87
tstoecklerI don't think we have to explicitly document everything Field API supports (i.e.
@mixed). I think we just choose one that works and document what it is. I was just suggesting to use the simplest one possible.Fine with the followups for the other entity types, makes sense.
Comment #88
sam152 commentedDoes that reduce some of the utility though? I know int is a simple primitive that gets the job done, but would it ever be valid or useful to do something more complex, like
return ['entity' => User...]for example? Just don't want to force an int if it's possible the usefulness suffers.Comment #89
sam152 commentedI got confused, I thought the current patch was hinting
arraywhere it's actuallymixedalready. So addressing the feedback from @tstoeckler, we should return the simplest mixed thing we can, and that's just the user ID. Also fixing the docblock, there can only be one default value.Also filed these:
#2975957: Convert the Workspace entity to use EntityOwnerTrait
#2975958: Convert EntityTest to use EntityOwnerTrait
If everyone is happy with the follow-ups I think this was the last bit of feedback.
Comment #91
sam152 commentedIt looks like #2347711: FieldItemlListInterface::processDefaultValue($default_value) is expected to massage polymorphic data ensured field values from callbacks were arrays (as expected by
processDefaultValue) and then #2529034: Replace direct access to FieldConfigBase::default_value with methods removed it again. This impactsFieldConfigBaseonly and notBaseFieldDefinition, which explains why this only turns up when an over field is being translated: the field definition is saved as aBaseFieldOverrideentity.I've logged #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 for this bug, which will fix the fails in ##8.
Comment #92
joachim commentedIt would be nice to get #2975503: allow FieldConfigInterface::setDefaultValueCallback() to accept a callback in service notation in first, so we don't have to add this pretty pointless wrapper method.
Comment #93
sam152 commentedI'm loving the direction of that issue, but I'm also hesitant to add another blocker to this issue. Still waiting on #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, so I suppose we can see how it progresses and take it from there.
Comment #94
sam152 commentedRerolling now the blocker is complete.
Comment #96
sam152 commentedContext of fail in #2973791: Fix deprecation messages related to deprecated comment Action plugins.
Comment #97
sam152 commentedMarking as @legacy as per: https://www.drupal.org/node/2985785
Comment #98
jibranUnintentional change?
Do we really need these tests?
Don't we need a dedicated test for this, the update path and update path test?
Comment #99
sam152 commented1. Nope, this makes the test pass.
2. These methods aren't executed otherwise. Completely dead code seems risky enough to add a test for.
3. This doesn't have anything to do with update paths. It maintains the same schema before and after and was only changed because it was already covered by another test.
Comment #100
jibranOk then.
Comment #103
jibranComment #104
sam152 commentedI wonder if we should be waiting for #2975503: allow FieldConfigInterface::setDefaultValueCallback() to accept a callback in service notation?
Comment #106
tacituseu commentedUnrelated failure.
Comment #107
jibranRE #104: #2975503: allow FieldConfigInterface::setDefaultValueCallback() to accept a callback in service notation is a feature request and this is a task so I don't think we should be waiting for that issue.
Comment #108
sam152 commentedOkay great. There is some utility in a method that is ready to override in a parent anyway, so I don't have any reservations about introducing this with
::getDefaultEntityOwner.Comment #109
larowlanAdding review credits
We need a change record here, can't fault the patch
Comment #110
sam152 commentedDraft CR added! Thanks for reviewing @larowlan!
Comment #111
larowlanCommitted bea23d7 and pushed to 8.7.x. Thanks!
Published change record
Comment #113
alexpottQuick important followup spotted by @chr.fritsch - #2999306: Update numbering - quick follow-up to #2949964
Comment #114
alexpottOops didn't mean to change the status.
Comment #117
gogowitsch commentedComment #118
berdirFYI, the media update path is not working for me in a project: #3040746: Update functions that set owner entity key updates can fail on existing data with NULL values, reviews would be great to get this fixed asap.