Problem/Motivation

After update I randomly get this error, but did not create any bundle classes. One time it loads the entity, other time it does not load the same entity. I will update issue as I understand more why this is happening.

TypeError: Return value of Drupal\Core\Entity\EntityStorageBase::getEntityClass() must be of the type string, null returned in Drupal\Core\Entity\EntityStorageBase->getEntityClass() (line 115 of /web/core/lib/Drupal/Core/Entity/EntityStorageBase.php)
#0 /web/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php(196): Drupal\Core\Entity\EntityStorageBase->getEntityClass()
#1 /web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php(508): Drupal\Core\Entity\ContentEntityStorageBase->getEntityClass()
#2 /web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php(427): Drupal\Core\Entity\Sql\SqlContentEntityStorage->mapFromStorageRecords()
#3 /web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php(393): Drupal\Core\Entity\Sql\SqlContentEntityStorage->getFromStorage()
#4 /web/core/lib/Drupal/Core/Entity/EntityStorageBase.php(346): Drupal\Core\Entity\Sql\SqlContentEntityStorage->doLoadMultiple()
#5 /web/core/lib/Drupal/Core/Entity/EntityStorageBase.php(296): Drupal\Core\Entity\EntityStorageBase->loadMultiple()
#6 ...: Drupal\Core\Entity\EntityStorageBase->load()

Steps to reproduce

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Comments

mindaugasd created an issue. See original summary.

mindaugasd’s picture

Issue summary: View changes

Solved this by adding few lines of custom code.

  public function __construct(EntityTypeInterface $entity_type, MemoryCacheInterface $memory_cache) {
    // [...]
    $this->entityType = $entity_type;
    $this->baseEntityClass = $entity_type->getClass();
    // [...]
  }

  public function getEntityClass(?string $bundle = NULL): string {

    // 4 LINES OF CUSTOM CODE SOLVING THE PROBLEM:
    if (empty($this->baseEntityClass)) {
      $this->baseEntityClass = $this->entityType->getClass();
      \Drupal::logger('debug')->warning("getEntityClass error happened");
    }

    return $this->baseEntityClass;
  }

It was happening within Batch API, halting the long running progress randomly.

fgm’s picture

Status: Postponed (maintainer needs more info) » Active

Same problem here with 9.3.5. I'm within a batch too:

// $this->storage is injected, it's Drupal\Node\NodeStorage
$this->storage->loadMultiples($ids);
// this calls
$this->doLoadMultiple($ids); // on EntityStorageBase
// which calls 
$this->getFromStorage($ids); // on SqlContentEntityStorage
// which calls
$this->mapFromStorageRecords($records); // on SqlContentEntityStorage. $records is an array of stdClass containing the raw DB nodes
// which calls
$entity_class = $this->getEntityClass($bundle); // $bundle is one of the site's bundle names
// which calls
$entity_class = parent::getEntityClass(); // on NodeStorage
// which is a one-liner on EntityStorageBase
public function getEntityClass(?string $bundle = NULL): string {
   return $this->baseEntityClass;
}
// and here is the problem: $this->baseEntityClass is NULL, causing the error
fgm’s picture

The relevant difference is that the same $this->storage->loadMultiple() works normally outside a batch: seems batch loops miss some entity system initialization. These same nodes can be loaded:

  • on a normal admin page
  • on their own node page
  • in a Drush Drupal\node\Entity\Node::loadMultiple($ids); call

This looks like it could also be related with https://www.drupal.org/project/drupal/issues/3244802, which changes EntityStorageBase::__set and marks it deprecated.

fgm’s picture

I did a bisect on our project and the bug appeared at some point between 9.2.12 (working) and 9.3.0 -alpha1 (failing): that limits the potential causes.

We didn't catch it because none of our tests cover batches.

fgm’s picture

Finished the bisect. The problem appears with revision 02912a44b1363d838a3e906a7f12709481754b8d : the fix for #3246150: Bundle class changes mean the entity class is now determined by the active entity-type definition, which does change EntityStorageBase where the problem is located.

berdir’s picture

I can't explain why baseEntityClass would suddenly be empty and sometimes not. What exactly happens *before* that other issue? an empty string is just a wrong and bogus as NULL and should fail as well one step later, when trying to create that class.

fgm’s picture

It's a pretty basic batch, in which steps load a chunk of nodes, update one of its field items, and store the node back. Returning "" from that function does not cause further errors in my case. We don't have more details from the OP, except that it was also in a batch.

fgm’s picture

Hey @mindaugasd can you provide more detail, as our cases seem to resemble each other ?

mindaugasd’s picture

I wrote all the relevant details that I had. My PHP version is 7.4.27 (but maybe PHP version was different back then)
I solved issue with a patch.
Unless you could ask me very specific things to do that would be of some benefit.
And I would need to construct a new kind of batch, because existing one is very slow, complex and not suitable for testing.
Or - we can just use a patch, and maybe the problem reveal itself or be solved in some other context later.

fgm’s picture

New finding: during the batch, $this->getEntityClass() gets called on a NodeStorage for which the constructor was never called.

After further digging, this happens because the NodeStorage is deserialized in Core\Queue\Batch::claimItem and does not contain that field.

berdir’s picture

ah, now that does make sense.

the private property that we have is breaking DependencySerializationTrait. Change it to protected and it will work.

That said, I strongly recomend to never inject/store storages in a property, inject the entity type manager and get the storage when you need it. See #3162827: Do not instantiate entity storages in constructors of services that do not always need them

fgm’s picture

Status: Active » Fixed

The problem is that if we put the ETM in a batch info instead of the storage, we now get an assertion failed "The container has been serialized" in DatabaseQueue::createItem() because the batch items now contain a service, and since they are arrays by Batch API specification, they cannot use DependencySerializationTrait.

In my case, I just removed it altogether and made the batch operations callback a normal method instead of a static one, and added the DependencySerializationTrait to allow as an instance method, hence needing to serialize the service carrying it. Another solution would have been to use Drupal::entityTypeManager() but that triggers coder warnings.

Thanks for your help finding the issue. We probably want to handle #3197773: add documentation about not injecting entity storage handlers which would have avoided this, now.

berdir’s picture

Status: Fixed » Active

Lets keep this open for now, I do think we should think about not using private there.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

mindaugasd’s picture

Version: 9.5.x-dev » 10.1.x-dev

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

joseph.olstad’s picture

Drupal 11.1.1
key module 1.x-dev latest as of today
key module 1.19 also affected
symfony mailer lite: 2.0.2

Operation: add a key-override for the smtp username and smtp password of the symfony mailer transport configuration.

The path of the request: /admin/config/development/configuration/key-overrides/add?ajax-form

Same exact error as illustrated above:

TypeError: Return value of Drupal\Core\Entity\EntityStorageBase::getEntityClass() must be of the type string, null

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.