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.

CommentFileSizeAuthor
#97 2949964-97.patch33.79 KBsam152
#97 interdiff.txt650 bytessam152
#94 2949964-94.patch33.77 KBsam152
#89 2949964-89.patch33.62 KBsam152
#89 interdiff.txt637 bytessam152
#83 2949964-83.patch33.63 KBsam152
#69 2949964-69.patch33.37 KBsam152
#67 2949964-67_COMBINED.patch38.02 KBsam152
#67 2949964-67.patch33.37 KBsam152
#67 interdiff.txt1.22 KBsam152
#61 2949964-61_COMBINED.patch41.34 KBsam152
#61 2949964-61.patch33.37 KBsam152
#61 interdiff.txt703 bytessam152
#59 2949964-59_COMBINED.patch41.3 KBsam152
#59 2949964-59.patch33.33 KBsam152
#59 interdiff.txt1.71 KBsam152
#56 2949964-56.patch32.56 KBsam152
#56 interdiff.txt5.93 KBsam152
#45 2949964-45.patch32.89 KBsam152
#45 interdiff.txt5.96 KBsam152
#42 2949964-42.patch30.42 KBsam152
#42 interdiff.txt4.32 KBsam152
#40 2949964-40.patch30.42 KBsam152
#40 interdiff.txt879 bytessam152
#37 interdiff.txt881 bytessam152
#37 2949964-37.patch29.56 KBsam152
#36 2949964-36.patch29.23 KBsam152
#36 interdiff.txt625 bytessam152
#33 2949964-33.patch28.62 KBsam152
#33 interdiff.txt5.08 KBsam152
#28 interdiff.2949964.19-28.txt15.87 KBlongwave
#28 2949964-28.drupal.entityownertrait.patch28.88 KBlongwave
#27 interdiff.2949964.19-27.txt15.86 KBlongwave
#27 2949964-27.drupal.entityownertrait.patch28.87 KBlongwave
#19 interdiff.2949964.18-19.txt1.12 KBlongwave
#19 2949964-19.drupal.entityownertrait.patch23.77 KBlongwave
#18 interdiff.2949964.15-18.txt1.67 KBlongwave
#18 2949964-18.drupal.entityownertrait.patch23.2 KBlongwave
#15 interdiff-2949964-12-15.txt10.84 KBlongwave
#15 2949964-15.patch22.07 KBlongwave
#12 2949964-12.patch20.67 KBlongwave
#8 2949964-8.patch17.05 KBlongwave
#5 2949964.patch17.06 KBlongwave

Comments

amateescu created an issue. See original summary.

hchonov’s picture

Could we instead simply handle this in \Drupal\Core\Entity\ContentEntityBase::baseFieldDefinitions()?

amateescu’s picture

I guess we could, but do you see any reason for handling this one differently?

hchonov’s picture

For 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.

longwave’s picture

Status: Active » Needs review
StatusFileSize
new17.06 KB

First pass at this. Not sure if we need new tests or if the existing coverage is enough, as this is just a refactor?

longwave’s picture

Somehow testbot tested Payment module instead of core?!

Status: Needs review » Needs work

The last submitted patch, 5: 2949964.patch, failed testing. View results

longwave’s picture

Status: Needs work » Needs review
StatusFileSize
new17.05 KB

A silly copy-paste typo caused most of those errors.

Status: Needs review » Needs work

The last submitted patch, 8: 2949964-8.patch, failed testing. View results

longwave’s picture

So this appears to be running into two main problems:

  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.
  2. I 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 #2841291: Fix NOT NULL handling in the entity storage and 'primary key' changes when updating the storage definition of an identifier field

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?

berdir’s picture

> 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).

longwave’s picture

Status: Needs work » Needs review
StatusFileSize
new20.67 KB

Status: Needs review » Needs work

The last submitted patch, 12: 2949964-12.patch, failed testing. View results

berdir’s picture

+++ b/core/lib/Drupal/Core/Entity/EditorialContentEntityBase.php
@@ -22,6 +23,9 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     $fields += static::revisionLogBaseFieldDefinitions($entity_type);
 
+    // Add the owner field.
+    $fields += static::ownerBaseFieldDefinitions($entity_type);
+
     // Add the published field.
     $fields += static::publishedBaseFieldDefinitions($entity_type);

I 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.

longwave’s picture

Status: Needs work » Needs review
StatusFileSize
new22.07 KB
new10.84 KB

Thanks, 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.

Status: Needs review » Needs work

The last submitted patch, 15: 2949964-15.patch, failed testing. View results

longwave’s picture

I don't understand the HAL+JSON and REST fails, unless it is something to do with fragile ordering in $patchProtectedFieldNames?

longwave’s picture

Status: Needs work » Needs review
StatusFileSize
new23.2 KB
new1.67 KB
longwave’s picture

StatusFileSize
new23.77 KB
new1.12 KB

Not really sure why I need to change EntityReferenceItemTest to make it pass.

The last submitted patch, 18: 2949964-18.drupal.entityownertrait.patch, failed testing. View results

timmillwood’s picture

Looks good, only one little query:

+++ b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
@@ -500,6 +500,8 @@ public function setDefaultValue($value) {
+    // Setting a fixed value should override any default value callback.
+    unset($this->definition['default_value_callback']);

One could argue the opposite.

longwave’s picture

Any callback takes precedence over the literal value already, but maybe cleaning up the definition array is still worth doing?

    // Allow custom default values function.
    if ($callback = $this->getDefaultValueCallback()) {
      $value = call_user_func($callback, $entity, $this);
    }
    else {
      $value = $this->getDefaultValueLiteral();
    }
amateescu’s picture

TBH, I wouldn't make that change unless it is absolutely necessary :)

+++ b/core/modules/comment/src/Entity/Comment.php
@@ -52,6 +52,7 @@
+ *     "uid" = "uid",

A "uid" entity key name doesn't mean much, how about using "owner" instead?

berdir’s picture

Node 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.

timmillwood’s picture

When 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.

longwave’s picture

Assigned: Unassigned » longwave

I 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.

longwave’s picture

StatusFileSize
new28.87 KB
new15.86 KB
longwave’s picture

StatusFileSize
new28.88 KB
new15.87 KB

Copy-paste error.

The last submitted patch, 27: 2949964-27.drupal.entityownertrait.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 28: 2949964-28.drupal.entityownertrait.patch, failed testing. View results

sam152’s picture

Thanks 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.

  1. +++ b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
    @@ -500,6 +500,8 @@ public function setDefaultValue($value) {
         $this->definition['default_value'] = $value;
    +    // Setting a fixed value should override any default value callback.
    +    unset($this->definition['default_value_callback']);
         return $this;
    

    I think #21 and #23 still need to be addressed for this change.

  2. +++ b/core/modules/content_moderation/tests/src/Functional/Update/ContentModerationUpdateTest.php
    @@ -0,0 +1,43 @@
    +      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz',
    +      __DIR__ . '/../../../fixtures/update/drupal-8.4.0-content_moderation_installed.php',
    +      __DIR__ . '/../../../fixtures/update/drupal-8.default-cms-entity-id-2941736.php',
    

    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.php should be enough.

  3. +++ b/core/modules/content_moderation/tests/src/Functional/Update/ContentModerationUpdateTest.php
    @@ -0,0 +1,43 @@
    +    // Run updates.
    +    $this->runUpdates();
    
    +++ b/core/modules/media/tests/src/Functional/Update/MediaUpdateTest.php
    @@ -53,4 +53,22 @@ public function testBundlePermission() {
    +    // Run updates.
    +    $this->runUpdates();
    

    Super nit/style: it's pretty clear what is happening on these lines without the comments.

  4. +++ b/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceItemTest.php
    @@ -542,6 +542,7 @@ public function testAutocreateValidation() {
         $file = File::create([
           'filename' => $filename,
           'status' => 0,
    +      'uid' => NULL,
         ]);
    

    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.

  5. +++ b/core/modules/user/src/EntityOwnerTrait.php
    @@ -0,0 +1,89 @@
    +        ->setDefaultValueCallback(static::class . '::getCurrentUserId'),
    

    I always found [static::class, 'getCurrentUserId'] a bit more elegant, but this is purely style.

  6. +++ b/core/modules/user/src/EntityOwnerTrait.php
    @@ -0,0 +1,89 @@
    +   * Default value callback for 'uid' base field definition.
    

    I think the comment in the trait should read 'owner' instead of 'uid'.

sam152’s picture

Also, 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.

sam152’s picture

Status: Needs work » Needs review
StatusFileSize
new5.08 KB
new28.62 KB

Hey @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:

  1. +++ b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
    @@ -500,8 +500,6 @@ public function setDefaultValue($value) {
    -    // Setting a fixed value should override any default value callback.
    -    unset($this->definition['default_value_callback']);
    
    @@ -542,7 +542,6 @@ public function testAutocreateValidation() {
         $file = File::create([
           'filename' => $filename,
           'status' => 0,
    -      'uid' => NULL,
         ]);
    
    +++ b/core/modules/file/src/Entity/File.php
    @@ -214,9 +214,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    -    $fields['uid']
    -      ->setDescription(t('The user ID of the file.'))
    -      ->setDefaultValue(0);
    +    $fields['uid']->setDescription(t('The user ID of the file.'));
    
    @@ -256,4 +254,11 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public static function getDefaultEntityOwner() {
    +    return NULL;
    +  }
    

    I believe this resolves the feedback around the default value callback as well as the BC concerns. The crux of this issue was: BaseFieldDefinition has no unsetDefaultValueCallback method, so previously where File was defaulting to NULL, this patch wanted it owned by the current user. This rejiggs things by naming the default value callback getDefaultEntityOwner which lets file entity safely override that to NULL which is correct in this circumstance.

  2. +++ b/core/modules/content_moderation/tests/src/Functional/Update/ContentModerationUpdateTest.php
    @@ -18,7 +18,6 @@ protected function setDatabaseDumpFiles() {
         $this->databaseDumpFiles = [
           __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz',
           __DIR__ . '/../../../fixtures/update/drupal-8.4.0-content_moderation_installed.php',
    -      __DIR__ . '/../../../fixtures/update/drupal-8.default-cms-entity-id-2941736.php',
    

    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.

  3. +++ b/core/modules/user/src/EntityOwnerTrait.php
    @@ -54,6 +54,7 @@ public function getOwnerId() {
       public function setOwnerId($uid) {
         $key = $this->getEntityType()->getKey('owner');
         $this->set($key, $uid);
    +    unset($this->translatableEntityKeys['owner']);
    
    @@ -72,17 +73,18 @@ public function getOwner() {
       public function setOwner(UserInterface $account) {
         $key = $this->getEntityType()->getKey('owner');
         $this->set($key, $account->id());
    +    unset($this->translatableEntityKeys['owner']);
    

    This fixes NodeOwnerTest. Entity keys seem to have their own cache on ContentEntityBase (translatableEntityKeys and entityKeys). getEntityKey looks up those values first, hence the stale data in the test case.

Status: Needs review » Needs work

The last submitted patch, 33: 2949964-33.patch, failed testing. View results

sam152’s picture

Hm, I forgot about the SqlContentEntityStorageSchema nuance that all entity keys have NOT NULL applied to their storage scheme. To maintain BC on the file entity, we need to ensure uid can continue to be null.

sam152’s picture

Status: Needs work » Needs review
StatusFileSize
new625 bytes
new29.23 KB
sam152’s picture

StatusFileSize
new29.56 KB
new881 bytes

Looks like Comment needs the same default value treatment.

The last submitted patch, 36: 2949964-36.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 37: 2949964-37.patch, failed testing. View results

sam152’s picture

Status: Needs work » Needs review
StatusFileSize
new879 bytes
new30.42 KB

Last test fix. I think this is ready for review. I think if everyone is happy with getDefaultEntityOwner, we should probably keep the existing getCurrentUserId static 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 general getDefaultEntityOwner is named more appropriately and thus can be overridden for different purposes like in the case of Comment and File.

amateescu’s picture

This 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.

  1. +++ b/core/modules/comment/src/Entity/Comment.php
    @@ -323,6 +323,13 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    +  public static function getDefaultEntityOwner() {
    +    return 0;
    +  }
    
    +++ b/core/modules/file/src/Entity/File.php
    @@ -284,4 +254,11 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    +  public static function getDefaultEntityOwner() {
    +    return NULL;
    +  }
    
    +++ b/core/modules/user/src/EntityOwnerTrait.php
    @@ -0,0 +1,91 @@
    +   * @return array
    +   *   An array of default values.
    +   */
    +  public static function getDefaultEntityOwner() {
    

    The trait method documents the return value as an array so we should also return arrays in the Comment and File implementations.

  2. +++ b/core/modules/comment/tests/src/Functional/Update/CommentUpdateTest.php
    @@ -71,4 +71,22 @@ public function testPublishedEntityKey() {
    +    $this->assertEqual('uid', $entity_type->getKey('owner'));
    
    +++ b/core/modules/content_moderation/tests/src/Functional/Update/ContentModerationUpdateTest.php
    @@ -0,0 +1,42 @@
    +    $this->assertEqual('uid', $entity_type->getKey('owner'));
    
    +++ b/core/modules/media/tests/src/Functional/Update/MediaUpdateTest.php
    @@ -53,4 +53,22 @@ public function testBundlePermission() {
    +    $this->assertEqual('uid', $entity_type->getKey('owner'));
    
    +++ b/core/modules/node/tests/src/Functional/Update/NodeUpdateTest.php
    @@ -64,4 +64,22 @@ public function testStatusCheckbox() {
    +    $this->assertEqual('uid', $entity_type->getKey('owner'));
    

    We can use assertEquals() here.

  3. +++ b/core/modules/user/src/EntityOwnerTrait.php
    @@ -0,0 +1,91 @@
    +   * Default value callback for 'owner' base field definition.
    

    I don't think we need the 'definition' part of this docblock :)

sam152’s picture

StatusFileSize
new4.32 KB
new30.42 KB

Thanks 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.

sam152’s picture

Status: Needs review » Needs work

Hm, still need to add the getCurrentUserId methods back.

timmillwood’s picture

Had 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 call getDefaultEntityOwner.

sam152’s picture

Status: Needs work » Needs review
StatusFileSize
new5.96 KB
new32.89 KB

Reintroducing the getCurrentUserId methods and adding deprecations + tests. Also reverting an unrelated test change.

Status: Needs review » Needs work

The last submitted patch, 45: 2949964-45.patch, failed testing. View results

sam152’s picture

Status: Needs work » Needs review
berdir’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/comment/src/Entity/Comment.php
    @@ -260,12 +263,9 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     
    -    $fields['uid'] = BaseFieldDefinition::create('entity_reference')
    -      ->setLabel(t('User ID'))
    +    $fields['uid']
           ->setDescription(t('The user ID of the comment author.'))
    -      ->setTranslatable(TRUE)
    -      ->setSetting('target_type', 'user')
    -      ->setDefaultValue(0);
    +      ->setTranslatable(TRUE);
    

    I'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.

  2. +++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php
    @@ -37,6 +37,7 @@
      *     "uid" = "uid",
    + *     "owner" = "uid",
    

    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.

  3. +++ b/core/modules/media/tests/src/Kernel/MediaTest.php
    @@ -8,6 +8,7 @@
      *
      * @group media
    + * @group legacy
      */
     class MediaTest extends MediaKernelTestBase {
    

    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?

  4. +++ b/core/modules/user/src/EntityOwnerTrait.php
    @@ -0,0 +1,91 @@
    +    unset($this->translatableEntityKeys['owner']);
    

    I would expect that this happens automatically through \Drupal\Core\Entity\ContentEntityBase::onChange() ? Node didn't have to do this..

  5. +++ b/core/modules/user/src/EntityOwnerTrait.php
    @@ -0,0 +1,91 @@
    +  public function setOwner(UserInterface $account) {
    +    $key = $this->getEntityType()->getKey('owner');
    +    $this->set($key, $account->id());
    +    unset($this->translatableEntityKeys['owner']);
    +
    +    return $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.

sam152’s picture

Thank 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?

berdir’s picture

1.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.

timmillwood’s picture

sam152’s picture

Assigned: longwave » sam152

1.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.

berdir’s picture

3. 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?

sam152’s picture

sam152’s picture

Re: #53, I didn't realise you could add the @legacy annotation 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.

sam152’s picture

StatusFileSize
new5.93 KB
new32.56 KB

Logging #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:

  • Making the field translatable (can't see an issue with this being translatable for non translatable entity types, so applying unconditionally).
  • Moves the @legacy annotation to our dedicated legacy test methods.
  • Removes the unsetting of the keys cache as per above. This will currently fail some tests, but should be fixed after the blocker is resolved.

This still leaves todo:

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.

Sorry for the comment spam, quite a bit going on with this issue :)

sam152’s picture

Status: Needs work » Needs review
sam152’s picture

Status: Needs review » Needs work
sam152’s picture

Title: Add a EntityOwnerTrait to standardize the base field needed by EntityOwnerInterface » [PP-1] Add a EntityOwnerTrait to standardize the base field needed by EntityOwnerInterface
Status: Needs work » Needs review
StatusFileSize
new1.71 KB
new33.33 KB
new41.3 KB

This addresses the last todo based on @Berdir's feedback. I think we should support setting an unsaved user entity with setOwner as 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->set makes 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.

Status: Needs review » Needs work

The last submitted patch, 59: 2949964-59_COMBINED.patch, failed testing. View results

sam152’s picture

Status: Needs work » Needs review
StatusFileSize
new703 bytes
new33.37 KB
new41.34 KB

Fixing tests. The owner on files are non-translatable by default.

berdir’s picture

+++ b/core/modules/file/src/Entity/File.php
@@ -214,7 +214,9 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       ->setDescription(t('The file language code.'));
 
-    $fields['uid']->setDescription(t('The user ID of the file.'));
+    $fields['uid']
+      ->setTranslatable(FALSE)
+      ->setDescription(t('The user ID of the file.'));

Don'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.

sam152’s picture

I believe File entities are translatable, just not the uid field.

>>> print_r(\Drupal::entityTypeManager()->getDefinition('file')->isTranslatable());
=> true

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.

The last submitted patch, 61: 2949964-61.patch, failed testing. View results

berdir’s picture

Nope, they are not :)

>>> print_r(\Drupal::entityTypeManager()->getDefinition('file')->isTranslatable());
=> true
>>> \Drupal::entityTypeManager()->getDefinition('file')->isTranslatable();
=> false

That true is the return value of print_r(), which "prints" false and then returns TRUE that it did that successfully :)

berdir’s picture

Status: Needs review » Needs work

As discussed, this should be updated.

sam152’s picture

Status: Needs work » Needs review
StatusFileSize
new1.22 KB
new33.37 KB
new38.02 KB

Good 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, EntityTest already 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.

The last submitted patch, 67: 2949964-67.patch, failed testing. View results

sam152’s picture

Title: [PP-1] Add a EntityOwnerTrait to standardize the base field needed by EntityOwnerInterface » Add a EntityOwnerTrait to standardize the base field needed by EntityOwnerInterface
StatusFileSize
new33.37 KB

Blocker is in.

dawehner’s picture

Nice work!

  1. +++ b/core/modules/file/src/FileStorageSchema.php
    @@ -26,6 +26,11 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
         }
    +    // Entity keys automatically have not null assigned to TRUE, but for the
    +    // file entity, NULL is a valid value for uid.
    +    if ($field_name === 'uid') {
    +      $schema['fields']['uid']['not null'] = FALSE;
    +    }
    

    Nice work to remove potential breakage

  2. +++ b/core/modules/media/src/Entity/Media.php
    @@ -492,10 +464,14 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
         return [\Drupal::currentUser()->id()];
    
    +++ b/core/modules/node/src/Entity/Node.php
    @@ -410,10 +381,14 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
         return [\Drupal::currentUser()->id()];
    
    +++ b/core/modules/user/src/EntityOwnerTrait.php
    @@ -0,0 +1,90 @@
    +  public static function getDefaultEntityOwner() {
    +    return [\Drupal::currentUser()->id()];
    +  }
    

    I'm curious whether this should call out to the "parent" method.

  3. +++ b/core/modules/user/src/EntityOwnerTrait.php
    @@ -0,0 +1,90 @@
    +    return [
    +      $entity_type->getKey('owner') => BaseFieldDefinition::create('entity_reference')
    +        ->setLabel(new TranslatableMarkup('User ID'))
    +        ->setSetting('target_type', 'user')
    +        ->setTranslatable($entity_type->isTranslatable())
    +        ->setDefaultValueCallback(static::class . '::getDefaultEntityOwner'),
    +    ];
    

    Maybe a naive question: Is there a reason this is not display/form configurable by default?

sam152’s picture

Thank you for reviewing!

1. :D
2. Do you mean the deprecated getCurrentUserId methods should call out to getDefaultEntityOwner? 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.

dawehner’s picture

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.

I 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.

sam152’s picture

Perhaps 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.

cilefen’s picture

Title: Add a EntityOwnerTrait to standardize the base field needed by EntityOwnerInterface » Add an EntityOwnerTrait to standardize the base field needed by EntityOwnerInterface
berdir’s picture

I 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.

sam152’s picture

I'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?

berdir’s picture

Yeah, it's not something we did so far, agreed.

+++ b/core/modules/comment/src/Entity/Comment.php
@@ -323,6 +322,13 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+  /**
+   * {@inheritdoc}
+   */
+  public static function getDefaultEntityOwner() {
+    return 0;

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?

sam152’s picture

A 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 getDefaultEntityOwner if we're indeed changing the default owner.

sam152’s picture

Assigned: sam152 » Unassigned

Unassigning for the moment.

tstoeckler’s picture

+++ b/core/modules/user/src/EntityOwnerTrait.php
@@ -0,0 +1,90 @@
+  public static function getDefaultEntityOwner() {
+    return [\Drupal::currentUser()->id()];
+  }

Just 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.

sam152’s picture

#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 array is the best we can do, when in reality the field system is way more flexible than that.

sam152’s picture

Filing 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?

sam152’s picture

StatusFileSize
new33.63 KB

Reolling

berdir’s picture

I'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 ;)

tstoeckler’s picture

Re #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.

sam152’s picture

So 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.

tstoeckler’s picture

I 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.

sam152’s picture

Does 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.

sam152’s picture

StatusFileSize
new637 bytes
new33.62 KB

I got confused, I thought the current patch was hinting array where it's actually mixed already. 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.

Status: Needs review » Needs work

The last submitted patch, 89: 2949964-89.patch, failed testing. View results

sam152’s picture

It 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 impacts FieldConfigBase only and not BaseFieldDefinition, which explains why this only turns up when an over field is being translated: the field definition is saved as a BaseFieldOverride entity.

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.

joachim’s picture

+++ b/core/modules/user/src/EntityOwnerTrait.php
@@ -0,0 +1,90 @@
+        ->setDefaultValueCallback(static::class . '::getDefaultEntityOwner'),

It 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.

sam152’s picture

I'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.

sam152’s picture

Status: Needs work » Needs review
StatusFileSize
new33.77 KB

Rerolling now the blocker is complete.

Status: Needs review » Needs work

The last submitted patch, 94: 2949964-94.patch, failed testing. View results

sam152’s picture

sam152’s picture

Status: Needs work » Needs review
StatusFileSize
new650 bytes
new33.79 KB

Marking as @legacy as per: https://www.drupal.org/node/2985785

jibran’s picture

  1. +++ b/core/modules/comment/tests/src/Functional/Hal/CommentHalJsonTestBase.php
    @@ -42,8 +42,8 @@
    -    'entity_id' => NULL,
    ...
    +    'entity_id' => NULL,
    
    +++ b/core/modules/comment/tests/src/Functional/Rest/CommentResourceTestBase.php
    @@ -31,9 +31,9 @@
    +    'uid' => "The 'administer comments' permission is required.",
    ...
    -    'uid' => "The 'administer comments' permission is required.",
    

    Unintentional change?

  2. +++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
    @@ -616,6 +616,16 @@ public function testRevisionDefaultState($entity_type_id) {
    +  /**
    +   * Tests the legacy method used as the default entity owner.
    +   *
    +   * @group legacy
    +   * @expectedDeprecation The ::getCurrentUserId method is deprecated in 8.6.x and will be removed before 9.0.0.
    +   */
    +  public function testGetCurrentUserId() {
    +    $this->assertEquals(['0'], ContentModerationState::getCurrentUserId());
    +  }
    +
    
    +++ b/core/modules/media/tests/src/Kernel/MediaTest.php
    @@ -34,4 +34,14 @@ public function testNameBaseField() {
    +  /**
    +   * Tests the legacy method used as the default entity owner.
    +   *
    +   * @group legacy
    +   * @expectedDeprecation The ::getCurrentUserId method is deprecated in 8.6.x and will be removed before 9.0.0.
    +   */
    +  public function testGetCurrentUserId() {
    +    $this->assertEquals(['1'], Media::getCurrentUserId());
    +  }
    +
    
    +++ b/core/modules/node/tests/src/Kernel/NodeOwnerTest.php
    @@ -75,4 +76,36 @@ public function testOwner() {
    +  /**
    +   * Tests the legacy method used as the default entity owner.
    +   *
    +   * @group legacy
    +   * @expectedDeprecation The ::getCurrentUserId method is deprecated in 8.6.x and will be removed before 9.0.0.
    +   */
    +  public function testGetCurrentUserId() {
    ...
    +  }
    

    Do we really need these tests?

  3. +++ b/core/modules/file/src/FileStorageSchema.php
    @@ -26,6 +26,11 @@ protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $st
    +    // Entity keys automatically have not null assigned to TRUE, but for the
    +    // file entity, NULL is a valid value for uid.
    +    if ($field_name === 'uid') {
    +      $schema['fields']['uid']['not null'] = FALSE;
    +    }
    

    Don't we need a dedicated test for this, the update path and update path test?

sam152’s picture

1. 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.

jibran’s picture

Status: Needs review » Reviewed & tested by the community

Ok then.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 97: 2949964-97.patch, failed testing. View results

jibran’s picture

Status: Needs work » Reviewed & tested by the community
sam152’s picture

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 97: 2949964-97.patch, failed testing. View results

tacituseu’s picture

Status: Needs work » Reviewed & tested by the community

Unrelated failure.

jibran’s picture

RE #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.

sam152’s picture

Okay 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.

larowlan’s picture

Status: Reviewed & tested by the community » Needs review
Issue tags: +Needs change record

Adding review credits

We need a change record here, can't fault the patch

sam152’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs change record

Draft CR added! Thanks for reviewing @larowlan!

larowlan’s picture

Status: Reviewed & tested by the community » Fixed

Committed bea23d7 and pushed to 8.7.x. Thanks!

Published change record

  • larowlan committed bea23d7 on 8.7.x
    Issue #2949964 by Sam152, longwave, Berdir, amateescu: Add an...
alexpott’s picture

Status: Fixed » Needs review

Quick important followup spotted by @chr.fritsch - #2999306: Update numbering - quick follow-up to #2949964

alexpott’s picture

Status: Needs review » Fixed

Oops didn't mean to change the status.

  • alexpott committed e432eea on 8.7.x
    Issue #2999306 by longwave, chr.fritsch, alexpott: Update numbering -...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

gogowitsch’s picture

Issue summary: View changes
berdir’s picture

FYI, 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.