See https://www.drupal.org/project/drupal/issues/3002457

Essentially it needs an array_filter applied to the results in readMultiple before returning them, so CachedStorage can properly do the, imo, wrong thing and replace empty results with a boolean FALSE, the same way it happens in the Target storage.

Otherwise comparing empty, but existing, config will result in FilteredStorage returning an empty array, or empty string and CachedStorage on the target storage side returning a boolean FALSE. Which, when compared in StorageComparer using strict compare, aren't equal. So they'll always show up as needing an update.

I'm adding a patch here to see if it breaks things, I've already added a patch for Core in the linked issue.

CommentFileSizeAuthor
array_filter_return.patch1.31 KBgrayle

Comments

Grayle created an issue. See original summary.

grayle’s picture

grayle’s picture

Status: Needs review » Postponed

Good news, the patch for Core also doesn't break any existing tests. I'd much prefer getting the core patch in, so postpone this one?

  • bircher committed 9216608 on 8.x-1.x
    Issue #3002488 by Grayle, bircher: FilteredStorage::readMultiple needs...
bircher’s picture

Status: Postponed » Fixed

Reading the interface:

  /**
   * Reads configuration data from the storage.
   *
   * @param array $names
   *   List of names of the configuration objects to load.
   *
   * @return array
   *   A list of the configuration data stored for the configuration object name
   *   that could be loaded for the passed list of names.
   */
  public function readMultiple(array $names);

One could argue that if a filter returns an empty config for some value that it should be removed, since that would mean it was not loaded. If that fixes the other issue then we might as well do that too.

I also added test coverage.
Thanks for finding this issue and providing a patch, I removed the comment though.

grayle’s picture

Wait. But. I'm trying to get it into Core, and if the fix gets into core, this patch will have to go. Or at least be the same as in Core: a strict boolean FALSE comparison instead of the falsy array_filter.

An empty array, essentially anything except FALSE, means the config was found and loaded. Even FALSE might be a hit, although removing those is fine because CachedStorage will save it as FALSE in cache anyway.

bircher’s picture

Hmm,
I am not sure how you can have a valid empty or false config.
The file storage and database storage both return an array with only the elements of non empty reads.
for example:

  public function readMultiple(array $names) {
    $list = [];
    foreach ($names as $name) {
      if ($data = $this->read($name)) {
        $list[$name] = $data;
      }
    }
    return $list;
  }

So a filter that sets the config to empty probably should have unset the array item. I am not sure how array_filter would not be a safe thing to apply on the filtered config to catch filters not unsetting the config.

But I am of course open to change it back or to the same as core does once a decision there is taken. But I understand this fixes your issue for now.

grayle’s picture

DatabaseStorage doesn't have the same check in its readMultiple, unlike FileStorage. As you can see, it's quite a mess down there in general.

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Config%21...

DatabaseStorage returns what it finds, even if it finds FALSE, or NULL, or an empty string. Only when it finds nothing, does it set the data to FALSE explicitly. But if it finds anything it'll return that (disregarding the issues with unserializing corrupt data and the like for the moment), even the boolean FALSE.

CachedStorage::readMultiple respects this in that it caches all those results, but then filters them before returning. Yet CachedStorage's read method does *not* filter anything at all. readMultiple can return nothing if you ask it for, say, ['webform.settings'] in the Dutch language collection because it array_filters it out. But ask the same of read, and you'll get an empty array.

The problem for Config Filter is in the fact that CachedStorage wraps itself again before calling DatabaseStorage. Specifically the fact that the following code is run twice. Once with a list fresh from DatabaseStorage, containing entries with empties, and again with a list filtered already by the internal CachedStorage instance where those empties are now entirely missing from the list.

    // Cache configuration objects that were loaded from the storage, cache
    // missing configuration objects as an explicit FALSE.
    $items = [];
    foreach ($names_to_get as $name) {
      $data = isset($list[$name]) ? $list[$name] : FALSE;
      $data_to_return[$name] = $data;
      $items[$cache_keys_map[$name]] = [
        'data' => $data,
      ];
    }
    $this->cache->setMultiple($items);

The first time it runs, with data from DatabaseStorage, configuration that was found, even if the data itself is only an empty string, is saved to the cache. But the second time it runs, that list has been passed through array_filter, and so everything that is now incorrectly treated as "missing" is set to boolean FALSE. And then *that* is saved to cache. And that cache is what CachedStorage::read will read from.

Even looking at the comments in CachedStorage: the intent is to cache missing configuration as FALSE. And below that is a comment saying it does not want to return missing configuration, but it doesn't explicitly check for FALSE, it uses the loose array_filter.

Target storage setup:
CachedStorage::readMultiple -> CachedStorage::readMultiple -> DatabaseStorage::readMultiple
Source storage setup with filters:
CachedStorage::readMultiple -> FilteredStorage::readMultiple -> ... -> DatabaseStorage::readMultiple (in the case of a hit for a Config Ignore filter)

So, in my mind, the correct fix is to either tighten the filter in CachedStorage so non-boolean FALSE isn't treated as a configuration miss, or change all the other Storage solutions (Database, Filtered, etc) so they do filter their results the same way. Which is what the current patch does, just making it work the same way as CachedStorage.

The fact that FileStorage does filter it as well muddies the waters somewhat. I didn't realize that. I guess either approach would be fine, as long as they all do the same thing. The key is that there needs to be consistency across the board, that's what caused the issue.

But, like you said, in the interim this patch does fix those issues.

Status: Fixed » Closed (fixed)

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