Updated: Comment #0

Problem/Motivation

When we add a language entity the logic behind (marking site as multilingual if needed, changing default language if needed and clearing language caches) is never called because it resides in language_save().

This is particularly painful with migrate, where we are storing entities directly.

Proposed resolution

Move this logic to the Entity/Language::postSave().
I'm not sure if this circular reference Entity/Language=>LanguageManager=>Entity/Language is desired.

Remaining tasks

Discuss if this is the way to go.

User interface changes

None.

API changes

None.

Follow-ups

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

penyaskito’s picture

Title: Move language add logic out of language_add() » Move language add logic out of language_save()
Issue summary: View changes
penyaskito’s picture

Status: Active » Needs review
FileSize
3.45 KB

First attempt, just moving code around.

Status: Needs review » Needs work

The last submitted patch, 2: 2234623-language-save-2.patch, failed testing.

penyaskito’s picture

Status: Needs work » Needs review
FileSize
1.16 KB
3.99 KB

Passing the $language->default property and fixed usage statements. This should fix most of the failures.

Status: Needs review » Needs work

The last submitted patch, 4: 2234623-language-save-4.patch, failed testing.

penyaskito’s picture

Status: Needs work » Needs review
FileSize
4.04 KB
713 bytes

Even more fixes. We should try to resolve the dicotomy between Entity/Language and Core/Language, but it is out of scope of this issue.

Status: Needs review » Needs work

The last submitted patch, 6: 2234623-language-save-6.patch, failed testing.

penyaskito’s picture

Status: Needs work » Needs review
FileSize
3.91 KB
2.46 KB

Removed passing $language->default to the entity.
Restored the language default logic into language_save().
Changed the language default form handling to use the language.default service instead of language_save().
This should make most tests pass, but some negotiation tests are failing locally still.

Status: Needs review » Needs work

The last submitted patch, 8: 2234623-language-save-8.patch, failed testing.

Anonymous’s picture

Issue tags: +imp, +blocker
Gábor Hojtsy’s picture

Why do we need to change the submit function?

penyaskito’s picture

Status: Needs work » Needs review
FileSize
3.16 KB
729 bytes

We shouldn't, but actually it fixed some tests. I'm reverting that in the attached patch.

About the attached patch, I've been looking at it and cannot find why it's breaking negotiation.
Drupal\language\LanguageNegotiationMethodBase::setCurrentUser() is being called with NULL in LanguageNegotiator::getNegotiationMethodInstance() with language-url method id, so an exception is thrown and I think it's the root of every other failure. This is only a guess, as I don't know why phpstorm is not being able of debug this on my machine, but I can assure that it was never called with a NULL before this patch.

I lost track of what's going on at LanguageRequestSubscriber::onKernelRequestLanguage(), I will try to get help on IRC.

Status: Needs review » Needs work

The last submitted patch, 12: 2234623-language-save-12.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
FileSize
3.18 KB
4.53 KB

isNew() is no longer TRUE in postSave() and the multilingual check needs to be done in preSave since we're trying to determine if the site is becoming multilingual.

Status: Needs review » Needs work

The last submitted patch, 14: 2234623.14.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
FileSize
3.93 KB
5.72 KB

So the order in which the default is set matters too

Gábor Hojtsy’s picture

This is tagged as an IMP blocker. Is this also blocking deploying languages through config deploy? Since all the additional actions should happen on the entity being saved instead of global functions, right? Does that make this a bug, beta target, beta blocker or what? :)

alexpott’s picture

Category: Task » Bug report
Priority: Normal » Major
Issue tags: +beta target

@Gábor Hojtsy good point - it don't think it is a beta blocker because it does not change an API or data model - but it certainly would prevent a comprehensive test for #2224887: Language configuration overrides should have their own storage being written. That is one that tests a configuration sync that makes a site multilingual and overrides config in the same process..

Gábor Hojtsy’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/language/language.module
    @@ -431,9 +431,7 @@ function language_save($language) {
    +  $language_entity->setDefault(!empty($language->default));
    

    This is confusing but due to the value vs. entity object differences. Not sure if possible to fix without further (unrelated) refactoring.

  2. +++ b/core/modules/language/lib/Drupal/language/Entity/Language.php
    @@ -78,10 +81,42 @@ class Language extends ConfigEntityBase implements LanguageInterface {
    +  /**
    +   * Used to detect when the site becomes multilingual.
    +   *
    +   * @var bool
    +   */
    +  protected $multilingual;
    ...
    +    $this->preSaveMultilingual = \Drupal::languageManager()->isMultilingual();
    
    @@ -90,6 +125,54 @@ public function preSave(EntityStorageInterface $storage) {
    +    if (!$this->multilingual && !$update) {
    +      ConfigurableLanguageManager::rebuildServices();
    +    }
    

    Should also say not saved on the language entity, right?

    Also the variable names are not unified?! Does this even work? :)

  3. Finally, how do we ensure those temporary values are not saved on the entity? There is no exported key list defined, so I assume now all of these would be exported, even if they are documented they are not, no? :)
xjm’s picture

Priority: Major » Critical
Issue tags: -beta target +beta blocker

@alexpott and @Gábor Hojtsy discussed this issue today and both suggested that it should actually be beta-blocking.

Gábor Hojtsy’s picture

@alexpott explained #19/3 to me in IRC. The default behavior for saving config entities is only public property values are saved. So by defining $multilingual and $default as protected, they are not saved back. So that is fine. The variable name mismatch in #19/2 though makes the $preSaveMultilingual property public (runtime created), so it will be saved. So once that is fixed the use the right variable, it should be fine. Looks like this is very close, only needs that variable name fixed.

penyaskito’s picture

Status: Needs work » Needs review
FileSize
5.86 KB
1.09 KB

Renamed the property and improved docs.

penyaskito’s picture

+++ b/core/modules/language/lib/Drupal/language/Entity/Language.php
@@ -90,6 +128,54 @@ public function preSave(EntityStorageInterface $storage) {
+      'default' => $this->default,

Shouldn't this call the \Drupal::service('language.default') for getting if this is the default language, as this could be called when the instance has not been saved, and the value could be a wrong one?

alexpott’s picture

#23 good point - perhaps the best thing to here will be actually save the default value to the entity - the problem we have then is keeping it in sync. hmm....

Gábor Hojtsy’s picture

Well, we can make the default language service be dynamic like the configurable language manager, when the language module is enabled it would derive this value from looking at which language is default by looking at all of them. Then we still need to ensure multiples are not defaults. Either way there is some coordination required.

Gábor Hojtsy’s picture

Status: Needs review » Needs work

Looks like needs work.

tstoeckler’s picture

  1. +++ b/core/modules/language/lib/Drupal/language/Entity/Language.php
    @@ -78,10 +81,45 @@ class Language extends ConfigEntityBase implements LanguageInterface {
    +  /**
    +   * Used to detect when the site becomes multilingual.
    +   *
    +   * This property is not saved to the language entity, but is needed for
    +   * detecting when to rebuild the services.
    +   *
    +   * @var bool
    +   */
    +  protected $preSaveMultilingual;
    ...
    +    $this->preSaveMultilingual = \Drupal::languageManager()->isMultilingual();
    

    Don't have better suggestions but this variable name and the documentation is not very clear.

  2. +++ b/core/modules/language/lib/Drupal/language/Entity/Language.php
    @@ -90,6 +128,54 @@ public function preSave(EntityStorageInterface $storage) {
    +      \Drupal::service('language.default')->set($this->toLanguageObject());
    ...
    +  /**
    +   * Converts the Language entity to a Language value object.
    +   *
    +   * @return \Drupal\Core\Language\Language
    +   *   The language configuration entity expressed as a Language value object.
    +   */
    +  protected function toLanguageObject() {
    +    return new LanguageObject(array(
    +      'id' => $this->id(),
    +      'name' => $this->label(),
    +      'direction' => $this->direction,
    +      'weight' => $this->weight,
    +      'locked' => $this->locked,
    +      'default' => $this->default,
    +    ));
    +  }
    

    Since this is a beta blocker this is fine for now but we should add a @todo to remove this again after #2246665: Typehint with Drupal\Core\Language\LanguageInterface instead Drupal\Core\Language\Language and #2246679: Make Language module's LanguageInterface (to be ConfigurableLanguageInterface) extend Core's LanguageInterface.

  3. +++ b/core/modules/language/lib/Drupal/language/Entity/Language.php
    @@ -90,6 +128,54 @@ public function preSave(EntityStorageInterface $storage) {
    +    $language_manager = \Drupal::languageManager();
    +    $language_manager->reset();
    +    if ($language_manager instanceof ConfigurableLanguageManagerInterface) {
    +      $language_manager->updateLockedLanguageWeights();
    +    }
    ...
    +    // If after adding this language the site will become multilingual, we need to
    +    // rebuild language services.
    +    if (!$this->preSaveMultilingual && !$update) {
    +      ConfigurableLanguageManager::rebuildServices();
    +    }
    

    Instead of hardcoding the class name here we could move the second if into the first.

    Note that its perfectly valid to call static methods non-statically. I.e. we could do

    if (!$this->preSaveMultilingual && !$update) {
      $language_manager->rebuildServices();
    }
    

    inside of the first if.

    Reading this code again, shouldn't ConfigurableLanguageManager simply override reset() to do that stuff itself?

    On the other hand this is just moving stuff around and this issue is a beta blocker, so feel free to leave to a follow-up.

alexpott’s picture

Status: Needs work » Needs review
FileSize
7.26 KB
10.97 KB
  1. Added @see's to how it used.
  2. Added @todo's
  3. Improved but it is not really possible to call static methods non statically... to quote https://stackoverflow.com/questions/15707029/calling-static-method-non-s...

    Yes, but you will get a warning, and God kills a kitten (don't do it).

Added code to ensure Language::$default is always correct after loading or creating and added a test. I don't think we should save the default value to the entity - maintaining this will be very messy.

tstoeckler’s picture

+++ b/core/modules/language/lib/Drupal/language/Entity/Language.php
@@ -152,14 +176,17 @@
+   * @todo fix return type hint after https://drupal.org/node/2246665 and
+   *   https://drupal.org/node/2246679.

Actually I'm pretty sure the entire method can be removed after those, but as long as we have the todo we won't forget.

Improved but it is not really possible to call static methods non statically... to quote https://stackoverflow.com/questions/15707029/calling-static-method-non-s...

Yes, but you will get a warning, and God kills a kitten (don't do it).

This is actually not true. I tried this locally and there is no warning. PhpStorm does not complain either.

Status: Needs review » Needs work

The last submitted patch, 28: 2234623.28.patch, failed testing.

Gábor Hojtsy’s picture

  1. +++ b/core/modules/language/lib/Drupal/language/Entity/Language.php
    @@ -78,10 +80,70 @@ class Language extends ConfigEntityBase implements LanguageInterface {
    +      if (static::getDefaultLangcode() == $this->id()) {
    +        return TRUE;
    +      }
    +      else {
    +        return FALSE;
    +      }
    

    This looks like a classic example where return *thecondition* would have done the same?

  2. +++ b/core/modules/language/lib/Drupal/language/Entity/Language.php
    @@ -90,15 +152,90 @@ public function preSave(EntityStorageInterface $storage) {
    +    if ($this->isDefault() && static::getDefaultLangcode() != $this->id()) {
    

    A higher level comment above would be great. This is for the case when this language was made the default but the global default is not yet up to date.

alexpott’s picture

Status: Needs work » Needs review
FileSize
2.07 KB
11.08 KB

Fixed tests and nits reported in #31

Gábor Hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

Looks good to be. RTBC assuming it passes now :)

penyaskito’s picture

Tests for #2166875: Migrate D6 languages are green too, thanks :D

YesCT’s picture

Issue summary: View changes

only one @todo, and it already mentions issues.
updated summary with that info.

effulgentsia’s picture

+++ b/core/modules/language/lib/Drupal/language/Entity/Language.php
@@ -90,15 +147,92 @@ public function preSave(EntityStorageInterface $storage) {
+  /**
+   * {@inheritdoc}
+   */
+  public function get($property_name) {
+    if ($property_name == 'default') {
+      return $this->isDefault();
+    }
+    else {
+      return parent::get($property_name);
+    }
+  }

This doesn't need to block commit, but I'm curious why we need to support callers calling ->get('default') instead of requiring them to call ->isDefault(). Unless it's quick to do here, I'm fine with that being discussed and/or removed in a follow up.

  • Commit 45e59c9 on 8.x by webchick:
    Issue #2234623 by alexpott, penyaskito: Move language add logic out of...
webchick’s picture

Status: Reviewed & tested by the community » Fixed

Alex's quoted code also stuck out to me. It'd be great to get a code comment explaining why this method is overridden. Fine with that being a follow-up though; not worth holding a beta blocker up over this.

Committed and pushed to 8.x. Thanks!

penyaskito’s picture

Gábor Hojtsy’s picture

Issue tags: -sprint

Yay, superb, thanks all!

Status: Fixed » Closed (fixed)

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