If a Node is created in German but the field is not translatable it looks like this:

$node->langcode = "de";
$node->field_text['und'][0]['value'] = "value";

Now if we use the Entity Getters:

$node = node_load(1);
print $node->get('field_text');

There is nothing returned, because in "Entity::getFieldLangcode" the langcode returned for the field is "de" (the language of the node), but actually the array key for the field is "und".

So my suggestion would be to return LANGUAGE_NOT_SPECIFIED all the time (see patch).

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Schnitzel’s picture

Issue tags: +Needs tests

adding tags

Status: Needs review » Needs work

The last submitted patch, entity_get_field_untranslated.patch, failed testing.

Schnitzel’s picture

Status: Needs work » Needs review
FileSize
738 bytes

uups

Gábor Hojtsy’s picture

Issue tags: +Entity system, +D8MI
FileSize
131.5 KB

Here is a figure I did before to help people understand the meaning of not defined language. For this scenario, the "non-translatable field" stands, which is the rightmost one, where it says "All shared/non-translatable fields LANGUAGE_NONE only, cannot have multiple variants". Since this condition is where you end up with a non-translatable field, it should not inherit the node language because it is in fact stored with no language per definition. That is the background of this patch.

Field language variants.png

Still needs tests.

Schnitzel’s picture

just one small addition

LANGUAGE_NONE was renamed to LANGUAGE_NOT_SPECIFIED

Schnitzel’s picture

FileSize
89.7 KB

as discussed with @fago he agrees, that it should return LANGUAGE_NOT_SPECIFIED.
Also discussed with @fago and @gabor, we throw an exception when getFieldLangcode() is called on a non-translatable field with a langcode other than NULL or LANGUAGE_NOT_SPECIFIED.

Updated tests for this exception

Schnitzel’s picture

FileSize
3.36 KB

mhh slightly wrong patch.
this is the right onw

Gábor Hojtsy’s picture

Status: Needs review » Needs work
+++ b/core/modules/entity/lib/Drupal/entity/Entity.phpundefined
@@ -220,9 +220,12 @@ class Entity implements EntityInterface {
     else {
-      // If there is a langcode defined for this field, just return it. Otherwise
-      // return LANGUAGE_NOT_SPECIFIED.
-      return (isset($this->langcode) ? $this->langcode : LANGUAGE_NOT_SPECIFIED);
+      if ($langcode != NULL && $langcode != LANGUAGE_NOT_SPECIFIED) {
+        $field_name = $field['field_name'];
+        throw new EntityStorageException("Field $field_name is not translatable and does only work with langcode = LANGUAGE_NOT_SPECIFIED");
+      }
+      // For non-translatable the langcode is LANGUAGE_NOT_SPECIFIED.
+      return LANGUAGE_NOT_SPECIFIED;

Should this be thrown from set() only? Sounds like if you request some language that is not present, we should not freak out. The code above falls back on default language for example, no?

+++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityTranslationTest.phpundefined
@@ -86,18 +87,25 @@ class EntityTranslationTest extends WebTestBase {
+    $message = "An exception is thrown when trying to set an field with an invalid language";

an field => a field

+++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityTranslationTest.phpundefined
@@ -86,18 +87,25 @@ class EntityTranslationTest extends WebTestBase {
+    } catch (EntityStorageException $e) {
+      $this->assertTrue($e instanceof EntityStorageException, $message);
+    }
...
+    } catch (EntityStorageException $e) {
+      $this->assertTrue($e instanceof EntityStorageException, $message);
+    }

} and catch should be separated by newline :)

Schnitzel’s picture

Status: Needs work » Needs review
FileSize
5.04 KB

as discussed with @gabor, we only throw an exception in the setters not in the getters, in the getters we return NULL.

also fixed the small issues.

plach’s picture

Status: Needs review » Reviewed & tested by the community

Looks good, thanks.

tim.plunkett’s picture

Status: Reviewed & tested by the community » Needs work

Sorry to set this back, but

+++ b/core/modules/entity/lib/Drupal/entity/Entity.phpundefined
@@ -220,9 +223,18 @@ class Entity implements EntityInterface {
+          throw new EntityStorageException("Field $field_name is not translatable and does only work with langcode = LANGUAGE_NOT_SPECIFIED");

This should use format_string().

Gábor Hojtsy’s picture

Issue tags: +sprint

Tagging.

c31ck’s picture

Status: Needs work » Needs review
FileSize
5.09 KB

Added format_string().

tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

Looks good, thanks.

Gábor Hojtsy’s picture

Issue tags: +Avoid commit conflicts

This deals with the rapidly changing entity landscape and is at a definite risk of commit conflicts.

Gábor Hojtsy’s picture

#13: 1738368-13.patch queued for re-testing.

webchick’s picture

Status: Reviewed & tested by the community » Needs work

Here's my review; mostly docs stuff. In general, my concern is that there's a bunch of crazy-assed stuff in #4 that's not communicated at all here. I don't think we need to move all of that into this code, but we certainly should move enough of it in so someone coming into these tests/functions without the benefit of having seen this issue will have an idea what's going on.

+++ b/core/modules/entity/lib/Drupal/entity/Entity.php
@@ -185,7 +185,9 @@ class Entity implements EntityInterface {
+      // We call getFieldLangcode with strict = FALSE, so if we request a field
+      // with a invalid language we don't throw an exception.
+      $langcode = $this->getFieldLangcode($field, $langcode, FALSE);

Ok, I can mostly make that out from reading that line of code. Why do we do that?

+++ b/core/modules/entity/lib/Drupal/entity/Entity.php
@@ -216,7 +219,7 @@ class Entity implements EntityInterface {
   /**
    * Determines the language code to use for accessing a field value in a certain language.
    */
-  protected function getFieldLangcode($field, $langcode = NULL) {
+  protected function getFieldLangcode($field, $langcode = NULL, $strict = TRUE) {

I guess this wasn't introduced in this patch, but we need PHPDoc of this new parameter. I have no idea why I might or might not want to set that to FALSE. Might as well document all of the parameters while we're here, and shorten that description to 80 chars.

+++ b/core/modules/entity/lib/Drupal/entity/Entity.php
@@ -225,9 +228,18 @@ class Entity implements EntityInterface {
+          $field_name = $field['field_name'];
+          throw new EntityStorageException(format_string("Field @field_name is not translatable and does only work with langcode = LANGUAGE_NOT_SPECIFIED", array('@field_name' => $field_name)));

"and only works with" rather than "does only work with"

Minor, but is there a reason to pull $field['field_name'] into a variable if you're only using it once?

YesCT’s picture

Status: Needs work » Needs review
FileSize
3.61 KB
6.4 KB

Attempted addressing webchick's comments from #17. Docs/comments need review by someone who really understands this to see if I got them right.

YesCT’s picture

oops. a little trailing whitespace problem. Ignore the patch in #18 and look at this one.

Gábor Hojtsy’s picture

Issue tags: +language-content

Subtagging for D8MI.

c31ck’s picture

Status: Needs review » Needs work

This looks good to me, a few minor remarks:

+++ b/core/modules/entity/lib/Drupal/entity/Entity.phpundefined
@@ -213,9 +217,30 @@ class Entity implements EntityInterface {
+   * Determine the language code for accessing a field value.

"Determine the language code" should be "Determines the language code". See http://drupal.org/node/1354#functions for more information.

+++ b/core/modules/entity/lib/Drupal/entity/Entity.phpundefined
@@ -213,9 +217,30 @@ class Entity implements EntityInterface {
+   * The language code to use for accessing a field value in a certain language
+   * is determined to be the langcode if the entity is language-specific. This
+   * function handles the special cases of not translateable fields and
+   * languages that are not specified.

I was thinking of something like this:
If the entity is language-specific and the field is translatable, the langcode is used to access the field value. Otherwise LANGUAGE_NOT_SPECIFIED should be used to access the field value.

When the field is not translatable and the langcode attempted is not LANGUAGE_NOT_SPECIFIED an exception is thrown in strict mode, or NULL is returned in non-strict mode.

+++ b/core/modules/entity/lib/Drupal/entity/Entity.phpundefined
@@ -213,9 +217,30 @@ class Entity implements EntityInterface {
+   * @param $langcode
+   *   The language code attempting to be applied to the field.

This param is optional and should be documented as such:
(optional) The language code attempting to be applied to the field.

+++ b/core/modules/entity/lib/Drupal/entity/Entity.phpundefined
@@ -213,9 +217,30 @@ class Entity implements EntityInterface {
+   * @param $strict
+   *   Optional flag so that we can be differently strict for invalid langcodes

This param is optional so the description should start with (optional).

+++ b/core/modules/entity/lib/Drupal/entity/Entity.phpundefined
@@ -213,9 +217,30 @@ class Entity implements EntityInterface {
+   *   Optional flag so that we can be differently strict for invalid langcodes

I was thinking of:
(optional) TRUE if an exception should be thrown when an invalid langcode is used on a non-translatable field, FALSE if NULL should be returned. Depending on the context in which this function is used, we want to be more strict. For example, the EntityInterface set() uses $strict TRUE, and the EntityInterface get() uses $strict FALSE.

+++ b/core/modules/entity/lib/Drupal/entity/Entity.phpundefined
@@ -213,9 +217,30 @@ class Entity implements EntityInterface {
+   * @return
+   *   The langcode if appropriate, LANGUAGE_NOT_SPECIFIED as in the case of a
+   *   field shared among all language version of an entity, or NULL.

The langcode if appropriate, LANGUAGE_NOT_SPECIFIED for non-translatable fields, or NULL when an invalid langcode was used in non-strict mode.

It might be a good idea to document the data types on the @param and @return directives. For example:
@return string|null

YesCT’s picture

Status: Needs work » Needs review
FileSize
2.96 KB
6.71 KB

This patch includes most of the suggestions from #21. I moved ... as in the case of a field shared among all language version of an entity... to the general description comment. Keeping that because I think it is part of what webchick asked for when she said to try and include some of the information from the graphic in #4. I looked at being more specific with specifying the types (http://drupal.org/node/1354#functions) but I wasn't clear on if most of the @param were just "strings"... and when I looked elsewhere in core for examples, like: core/modules/field/field.info.inc, I didn't find types specified for some of the non trivial params.

Gábor Hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

I agree we don't need to fix all of the problems of existing docs in a bugfix patch :) I think the documentation updates look good, so should be back at RTBC.

Gábor Hojtsy’s picture

Status: Reviewed & tested by the community » Needs work

Explained this in IRC in more detail to webchick, sun and YesCT:

[10:30am] GaborHojtsy: webchick: sun: ok, start with the summary, the problem is that the getter uses the langcode of the entity first and foremost, so it is not possible to use the getter to retrieve non-translatable fields right now
[10:30am] GaborHojtsy: $this is the entity, not the field in the code
[10:30am] GaborHojtsy: so the comment and code does not match
[10:30am] GaborHojtsy: but the same code is used in both a getter and setter context
[10:32am] GaborHojtsy: webchick: sun: and fago wanted to keep the fallback method of the getter() so you can get('body') without knowing if you need to or not need to specify a langcode, or you can iterate through all getters without calling some with the entity langcode and others with NULL or LANGUAGE_NOT_SPECIFIED to get all the fields
[10:32am] GaborHojtsy: webchick: sun: so if we'd throw an exception from get() if you want to get a value in (an invalid) langcode for a non-translatable field, you'd *always* need to wrap your getter with logic to check field translatability FIRST, outside of the getter (although the getter already does this inside)
[10:33am] webchick: Hm ok. so it's primarily for DX, or?
[10:33am] drupol joined the chat room.
[10:33am] GaborHojtsy: webchick: sun: or architect your code around catching exceptions and calling get() back again with NULL or LANGUAGE_NOT_SPECIFIED
[10:33am] GaborHojtsy: webchick: right
[10:34am] GaborHojtsy: webchick: so if body is untranslatable, with get() you get back the same data regardless of which langcode you pass, but for set() you get an exceptions saying "no-no, that is not possible"
[10:34am] GaborHojtsy: webchick: if we *prefer* to throw an exception regardless, that is possible to do
[10:34am] sun: hrm.  the fact that it required 5 messages to explain the situation essentially means that we need to put that explanation into phpDoc 
[10:35am] webchick: No, I don't mean that necessarily. Just from reading the patch it's not clear a) *why* that was done (the "what" is there), and b) when I as a module developer know that I need to call with/witout that param
[10:35am] webchick: and yes, what sun said. 
Gábor Hojtsy’s picture

It also does not apply due to the entity system being moved around.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
6.7 KB

Same patch rerolled.

Gábor Hojtsy’s picture

Title: Entity::getFieldLangcode should return LANGUAGE_NOT_SPECIFIED if field is not translatable » Not possible to use the entity getter to retrieve non-translatable field values
Priority: Normal » Major

Retitled for the problem and elevating bug since this is a pretty big deficiency, it would mean the entity getter does not work on single language sites for untranslatable fields, which is a pretty big portion of Drupal's user base.

sun’s picture

Tried my best to improve the docs, but I'm not sure whether everything is correct.

Gábor Hojtsy’s picture

Great improvements!

+++ b/core/lib/Drupal/Core/Entity/Entity.phpundefined
@@ -213,9 +216,42 @@ public function set($property_name, $value, $langcode = NULL) {
+   * - If a field's values are shared among all language versions of an entity,
+   *   LANGUAGE_NOT_SPECIFIED should be used to access them.

"field's values are shared among all language versions" is the same as "non-translatable field". We might want to use this wording even though its used again in the next paragraph just so that its absolutely clear.

I think its a great improvement either way, and you might just wanted to avoid repetition, so its fine as-is too.

Status: Needs review » Needs work

The last submitted patch, drupal8.entity-getfieldlangcode.28.patch, failed testing.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
6.84 KB
1.95 KB

Updated patch to fix the exception name in the test as well and correct the phpdoc description as explained above.

Status: Needs review » Needs work

The last submitted patch, drupal8.entity-getfieldlangcode.31.patch, failed testing.

Gábor Hojtsy’s picture

Gábor Hojtsy’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests, -Entity system, -D8MI, -sprint, -language-content, -Avoid commit conflicts

Status: Needs review » Needs work

The last submitted patch, drupal8.entity-getfieldlangcode.31.patch, failed testing.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
Issue tags: +Needs tests, +Entity system, +D8MI, +sprint, +language-content, +Avoid commit conflicts
Gábor Hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

Finally no unrelated fails, should be back to RTBC.

plach’s picture

@Gabor:

+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -213,9 +216,43 @@ class Entity implements EntityInterface {
+   * - If the entity is language-specific and the requested field is
+   *   translatable, the entity's language code should be used to access the
+   *   field value.

I think we are missing "when no language is explicitly provided."

Gábor Hojtsy’s picture

That was easy :)

plach’s picture

Thanks :)

Lars Toomre’s picture

Small nit to be incorporated if this patch is re-rolled.

+++ b/core/lib/Drupal/Core/Entity/Entity.phpundefined
@@ -213,9 +216,43 @@ class Entity implements EntityInterface {
+   *   Field the language code is being determined for.
+   * @param string|null $langcode
+   *   (optional) The language code attempting to be applied to the field.
+   * @param bool $strict
+   *   (optional) When $strict is TRUE, an exception is thrown if the field is
+   *   not translatable and the langcode is not LANGUAGE_NOT_SPECIFIED. When
+   *   $strict is FALSE, NULL is returned and no exception is thrown. For
+   *   example, EntityInterface::set() passes TRUE, since it must not set field
+   *   values for invalid langcodes. EntityInterface::get() passes FALSE to
+   *   determine whether any field values exist for a specific langcode.

Both of these @param directives for optional variables need to document what their default values are.

Gábor Hojtsy’s picture

Status: Reviewed & tested by the community » Needs work

@Lars: Thanks, can you help with that?

Lars Toomre’s picture

At the moment, I cannot roll any patches @Gabor.

Gábor Hojtsy’s picture

Status: Needs work » Reviewed & tested by the community

Let's get this in then and people can tweak this however they want in the future.

plach’s picture

Assigned: Unassigned » plach
Status: Reviewed & tested by the community » Needs work

I'll do

plach’s picture

Assigned: plach » Unassigned
Status: Needs work » Reviewed & tested by the community
FileSize
6.94 KB
1.08 KB
Lars Toomre’s picture

Thanks @platch. Those additions look good.

webchick’s picture

Status: Reviewed & tested by the community » Fixed

Awesome; great work on the docs in this patch, folks. That's much clearer now.

Committed and pushed to 8.x.

Gábor Hojtsy’s picture

Issue tags: -sprint

Not on sprint anymore. Thanks all!

Status: Fixed » Closed (fixed)

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

chx’s picture

Issue tags: -Avoid commit conflicts

Removing Avoid commit conflicts tag

chx’s picture

try #2