ContentEntityBase provides methods and functionality that revolves around entity keys (id, uuid, bundle, etc).
And yet, ContentEntityBase does not provide a baseFieldDefinitions() method which would provide the field definitions for the defined keys.
This means that each content entity type needs to do this on its own, increasing boilerplate and code duplication.

In the Entity API module for D8 we have provided a trait for this: https://github.com/fago/entity/blob/8.x-1.x/src/EntityKeysFieldsTrait.php
My proposal is to move this logic to ContentEntityBase, and make all content entity baseFieldDefinition() methods call the parent to get the initial fields.

Comments

bojanz created an issue. See original summary.

bojanz’s picture

Status: Active » Needs review
StatusFileSize
new3.81 KB

Initial patch for discussion. Converted nodes as an example.
The original code was written by dawehner, I'm just transporting it.

Note this part:

      $fields[$entity_type->getKey('bundle')] = BaseFieldDefinition::create('entity_reference')
        ->setLabel(t('Type'))
        ->setSetting('target_type', $bundle_entity_type_id)
        ->setReadOnly(TRUE);

We don't really have a way of getting a label for the bundle field, so the best we can do is guess and tell children to change it if it's not precise.

Status: Needs review » Needs work

The last submitted patch, 2: 2635224-1-contententitybase-key-fields.patch, failed testing.

dawehner’s picture

+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -1117,6 +1117,52 @@ protected function getEntityKey($key) {
+    $bundle_entity_type_id = $entity_type->getBundleEntityType();
+    if ($bundle_entity_type_id && $entity_type->hasKey('bundle')) {
+      $fields[$entity_type->getKey('bundle')] = BaseFieldDefinition::create('entity_reference')
+        ->setLabel(t('Type'))
+        ->setSetting('target_type', $bundle_entity_type_id)
+        ->setReadOnly(TRUE);
+    }

if there is no bundle entity type but a bundle key, could we provide a string field?

+1 for less boilerplate!

berdir’s picture

We don't really have a way of getting a label for the bundle field, so the best we can do is guess and tell children to change it if it's not precise.

We do :)

$entity_type->getBundleLabel()

bojanz’s picture

Status: Needs work » Needs review
StatusFileSize
new4.07 KB

Addressed #4 and #5.

We obviously want to avoid doing too many conversions. I was thinking of converting a test entity type, but we have dozens of those. Any suggestions?

Status: Needs review » Needs work

The last submitted patch, 6: 2635224-6-contententitybase-key-fields.patch, failed testing.

bojanz’s picture

Status: Needs work » Needs review
StatusFileSize
new6.12 KB

Added missing use statement, converted EntityTest.

Note that the EntityTest bundle field had setRequired(TRUE) on the type definition, do we need to do that in the base class?
Also, the base class is calling ->setRevisionable(TRUE) on the langcode, does that do anything if the parent entity is not revisionable?

tstoeckler’s picture

Note that the EntityTest bundle field had setRequired(TRUE) on the type definition, do we need to do that in the base class?

Yes, definitely. The bundle key field should always be required.

Also, the base class is calling ->setRevisionable(TRUE) on the langcode, does that do anything if the parent entity is not revisionable?

No that's nonsensical and definitely an oversight/leftover/...

bojanz’s picture

Yes, definitely. The bundle key field should always be required.

Actually, bundle key not being empty is enforced in create() and the field is read-only, so we're safe without "required".
Looks like other base key fields aren't set as required either.

Revisionable entities are definitely calling setRevisionable() on their langcodes, same with translatable entities and setTranslatable().
The question is just whether we should be checking $entity_type->isRevisionable() / $entity_type->isTranslatable() before setting those flags on the field, or if we're safe to just set them anyway.

Status: Needs review » Needs work

The last submitted patch, 8: 2635224-8-contententitybase-key-fields.patch, failed testing.

bojanz’s picture

Status: Needs work » Needs review
StatusFileSize
new6.31 KB
@@ -1140,8 +1140,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     if ($entity_type->hasKey('langcode')) {
       $fields[$entity_type->getKey('langcode')] = BaseFieldDefinition::create('language')
         ->setLabel(t('Language'))
-        ->setTranslatable(TRUE)
-        ->setRevisionable(TRUE)
         ->setDisplayOptions('view', [
           'type' => 'hidden',
         ])
@@ -1149,6 +1147,12 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
           'type' => 'language_select',
           'weight' => 2,
         ]);
+      if ($entity_type->isRevisionable()) {
+        $fields[$entity_type->getKey('langcode')]->setRevisionable(TRUE);
+      }
+      if ($entity_type->isTranslatable()) {
+        $fields[$entity_type->getKey('langcode')]->setTranslatable(TRUE);
+      }
     }

Status: Needs review » Needs work

The last submitted patch, 12: 2635224-12-contententitybase-key-fields.patch, failed testing.

fago’s picture

While my initial thought was this should better stay a trait, I guess it makes sense in the base class. It's a reasonable default and if you want to do it different you are free to not call the parent. so +1 on moving it into the base class.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new7.07 KB
new779 bytes

This is probably all we need.

Status: Needs review » Needs work

The last submitted patch, 15: 2635224-15.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new9.99 KB
new3.8 KB

There we go, hopefully.

Status: Needs review » Needs work

The last submitted patch, 17: 2635224-17.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new10.56 KB
new4.06 KB

Let's see whether this works out.

Status: Needs review » Needs work

The last submitted patch, 19: 2635224-19.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new13.77 KB
new6.37 KB

There we go.

berdir’s picture

+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
@@ -109,6 +109,10 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       ));
+    if (isset($fields['langcode'])) {
+      $fields['langcode']->setDisplayOptions('view', []);
+      $fields['langcode']->setDisplayOptions('form', []);
+    }

why not do this by default?

+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php
@@ -50,14 +50,4 @@
-  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
-    $fields = parent::baseFieldDefinitions($entity_type);
-    $fields['custom_langcode_key'] = $fields['langcode'];
-    unset($fields['langcode']);
-    return $fields;

It's kind of funny how we now have a test that makes sure we can have a custom named langcode key that's automatically generated ;)

dawehner’s picture

why not do this by default?

It breaks a couple of tests, but for sure, we could adapt the, as well.

tstoeckler’s picture

+      $fields['langcode']->setDisplayOptions('view', []);
+      $fields['langcode']->setDisplayOptions('form', []);

Setting the options in that way does not actually enable the default view and form display determined by the default formatter and widget, so while it might fix tests it does not have any impact on the actual display. So I would like to avoid having those calls in the default implementation because I find them confusing. Not sure how others see that...

Maybe we can also adapt the tests to not require these calls in the first place?

berdir’s picture

Ok, if it doesn't actually do anything, then removing it and updating tests to not look for it seems like the best choice to me as well.

dawehner’s picture

I agree

dawehner’s picture

StatusFileSize
new14.11 KB
new1.36 KB

There we go

bojanz’s picture

Great! +1 for RTBC.

berdir’s picture

Status: Needs review » Reviewed & tested by the community

Agreed, RTBC.

dawehner’s picture

.

  • catch committed 7e4b50e on 8.1.x
    Issue #2635224 by dawehner, bojanz: ContentEntityBase should provide...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Looks great. Committed/pushed to 8.1.x, thanks!

amateescu’s picture

+++ b/core/modules/node/src/Entity/Node.php
@@ -325,41 +325,7 @@ public function setRevisionAuthorId($uid) {
-      ->setDescription(t('The node ID.'))
...
-      ->setDescription(t('The node UUID.'))
...
-      ->setDescription(t('The node revision ID.'))
...
-      ->setDescription(t('The node type.'))
...
-      ->setDescription(t('The node language code.'))

Is it a regression that node's base fields do not have a description anymore?

bojanz’s picture

I'd say no, since they never had anything to say (other than repeating the label)

Status: Fixed » Closed (fixed)

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

catch’s picture

Issue tags: +8.1.0 release notes
xjm’s picture

Title: ContentEntityBase should provide field definitions for key fields » [Needs change record]ContentEntityBase should provide field definitions for key fields
Status: Closed (fixed) » Needs work
Issue tags: +Needs change record

This issue is missing a change record. (We should add change records for noteworthy API additions.)

dawehner’s picture

I'll write one for all of the 4 entity related ones: https://www.drupal.org/node/2709511

dawehner’s picture

Title: [Needs change record]ContentEntityBase should provide field definitions for key fields » ContentEntityBase should provide field definitions for key fields
Status: Needs work » Fixed

Added stuff to the change record. Feel free to improve it.

Status: Fixed » Closed (fixed)

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