Problem/Motivation

After update to version 3.0.0 I got this error:
'PHP message: Uncaught PHP Exception Drupal\\Core\\Entity\\Exception\\BundleClassInheritanceException: "Bundle class Drupal\\my_node\\Entity\\Bundle\\PersoonNode does not extend entity class Drupal\\book\\Entity\\Node\\Book." at /xxx/web/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php line 216'

My module is allready working for 2 years.

Steps to reproduce

Create a new Drupal 11.3 site
Create a custom module with in the my_module.module file this function:

function my_node_entity_bundle_info_alter(array &$bundles): void {
  foreach ($bundles['node'] as $bundle => $data) {
    if ($bundle == 'persoon') {
      $bundles['node']['persoon']['class'] = PersoonNode::class;
    }
  }
}

Create in src\Entity\Bundle a file PersoonNode.php and PersoonNodeInterface.php file with some code.
Install the module.
Clear caches.
goto the home page.

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

Issue fork book-3574779

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

promes created an issue. See original summary.

smustgrave’s picture

Did you clear cache ?

promes’s picture

After running update as admin each try to open a page (admin page or regular page) results in an error message. I did try to run rebuild.php but to no avail.
The error occurs also in other sites.

smustgrave’s picture

need to run drush cr

promes’s picture

After drush cr still the same error.

smustgrave’s picture

liam morland’s picture

Version: 3.0.0 » 3.0.x-dev
aayushpathak’s picture

working on it !

aayushpathak’s picture

Status: Active » Needs review
promes’s picture

I installed v3.0 and patched it with the code from https://git.drupalcode.org/project/book/-/merge_requests/166/diffs.
After running the update I got this message:

book-module
#103007 update

Failed: Drupal\Core\Entity\Exception\BundleClassInheritanceException: Bundle class Drupal\my_node\Entity\Bundle\PersoonNode does not extend entity class Drupal\book\Entity\Node\Book. in Drupal\Core\Entity\ContentEntityStorageBase->getEntityClass() (line 216 of /xxx/web/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php).

aayushpathak’s picture

I have updated the merge request to conditionally override the Node entity class only when it still uses the default `Drupal\node\Entity\Node` class. This prevents the Book module from forcing the `Drupal\book\Entity\Node\Book` class when another module defines a custom bundle class . which previously caused the `BundleClassInheritanceException`. Could you please check if the error is still reproducible after applying the updated changes?

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

This will need test coverage. Also some out of scope styling changes in there

liam morland’s picture

I have un-done the coding style changes. All tests and checks are passing now.

aayushpathak’s picture

Status: Needs work » Needs review

Added kernel test coverage to ensure the Book module does not override custom node entity classes. Please verify.

promes’s picture

I got the same error again. But this time not in the update.php page (I am using the UX) like yesterday.
I see some differences with my module. I hope this can help you in testing.

The bundle is defined in my module with name my_node.

use Drupal\my_node\Entity\Bundle\myTest;

/**
 * Implements hook_entity_bundle_info_alter().
 */
function my_node_entity_bundle_info_alter(array &$bundles): void {
  if (isset($bundles['node']['test'])) {
    $bundles['node']['test']['class'] = myTest::class;
  }
}

The actual class is in:
myTest.php
myTestInterface.php

myNode.php:

namespace Drupal\my_node\Entity\Bundle;
use Drupal\node\Entity\Node;
class myTest extends Node implements myTestInterface {
  public function myTestAction(): void {
//    do something.
  }

  public function preSave(EntityStorageInterface $storage) {
    $this->myTestAction();
    parent::preSave($storage);
  }
}
smustgrave’s picture

Status: Needs review » Needs work
philipnorton42’s picture

I encountered this issue as well. I attempted to create an entity bundle class and this caused the site to throw the same error as other people have encountered until I just extended the Book class instead of the Node class in my bundle class.

I'm not sure that replacing the core Node class is the correct approach here. I can see why it's done, but it seems like it will lead to problems as it replaces a core class and any site that uses this entity bundle class replacement technique will trow an error. Unless this is a bug in Drupal I would suggest a different approach.

I've been looking around the Drupal codebase to see how other modules do things. The content moderation module has a entity_bundle_info_alter that does a small entity query to figure out which content types are connected to content moderation before injecting a property. See the class Drupal\content_moderation\Hook\ContentModerationHooks.

What do you think of using the entity_type_build hook to add in the book related items (book_outline etc), and then using the entity_bundle_info_alter hook to change the class? The only issue here is that if a site changed the bundle class they would lose some of the book functionality, rather than crashing the site.

philipnorton42’s picture

Oh, also, if you spot this on your site and need a work around for now then you can extend the Book class in your entity bundle class. Even if your node bundle class doesn't have book functionality.

eg:

namespace Drupal\mymodule\Entity;

use Drupal\book\Entity\Node\Book;

class Link extends Book implements LinkInterface {

}

smustgrave’s picture

Trying a different approach using the allowed_types key but causing test failures. Will continue looking at those later if anyone can see if this is fixing hte issue locally.

smustgrave’s picture

Status: Needs work » Needs review

This change scares the hell out of me. But maybe assigning book to every node was always a bad approach.

liam morland’s picture

There should probably be a BookInterface.

promes’s picture

I ran composer update to todays 3.0-dev.
Then update.php
Still the same error
Then I did all update s from:
3574779-bundle-class-alternative Comparechanges, plain diff Status: PassedMR !168 mergeable
Hide branch
3574779-bundleclassinheritanceexception Comparechanges, plain diff MR !166

Again update.php. Still the same error.
Should I update only according to:
3574779-bundle-class-alternative Comparechanges, plain diff Status: PassedMR !168 mergeable
Hide branch
3574779-bundleclassinheritanceexception Comparechanges, plain diff MR !166

smustgrave’s picture

Sorry not sure what you're asking but you can apply 168 as a patch. Running updb should trigger a cache rebuild.

joseph.olstad’s picture

For those not familiar with gitlab or github, take the mr to the number and add .diff or .patch

https://git.drupalcode.org/project/book/-/merge_requests/168.diff

smustgrave’s picture

Kinda out of scope but added a basic interface

promes’s picture

I installed the 3.0-dev version and applied !168. After updating I didnt get an error.
Viewing a book goes ok.
I created a new entry for a book. I was not able to add it to the book. First I tried twice in edit mode. Then I tried outline mode and then I got this error:
Drupal\Core\Database\IntegrityConstraintViolationException: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '75' for key 'PRIMARY': INSERT INTO "book" ("nid", "bid", "pid", "weight", "depth", "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9") VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5, :db_insert_placeholder_6, :db_insert_placeholder_7, :db_insert_placeholder_8, :db_insert_placeholder_9, :db_insert_placeholder_10, :db_insert_placeholder_11, :db_insert_placeholder_12, :db_insert_placeholder_13); Array ( [:db_insert_placeholder_0] => 75 [:db_insert_placeholder_1] => 72 [:db_insert_placeholder_2] => 72 [:db_insert_placeholder_3] => 0 [:db_insert_placeholder_4] => 2 [:db_insert_placeholder_5] => 72 [:db_insert_placeholder_6] => 75 [:db_insert_placeholder_7] => 0 [:db_insert_placeholder_8] => 0 [:db_insert_placeholder_9] => 0 [:db_insert_placeholder_10] => 0 [:db_insert_placeholder_11] => 0 [:db_insert_placeholder_12] => 0 [:db_insert_placeholder_13] => 0 ) in Drupal\mysql\Driver\Database\mysql\ExceptionHandler->rethrowNormalizedException() (line 83 of /xxx/web/core/modules/mysql/src/Driver/Database/mysql/ExceptionHandler.php).

The website is created in Drupal 4, upgraded to D5, D6, D7, D9, D10 and now in D11. The book pages are basic_pages assigned to the book.

smustgrave’s picture

I'm not able to replicate that issue.

promes’s picture

The good news is: the book structure in the database is updated and correct. And the new entry can be found in the book.

smustgrave’s picture

tried again but not getting a duplicate entry error. Could you try on a fresh install? Maybe something got corruptedz/

liam morland’s picture

Since NodeInterface is replaced with BookInterface in several places, that suggests that BookInterface should extend NodeInterface.

promes’s picture

I did a install on a new site with version 3.0.1.
I am missing the bar at the bottom showing the title of the previous book, Up, and the title of the next book.

When I merge !168 I have these problems:
Go to /admin/structure/book/1 I get an page not found
When I goto /book-1 (the first book page created I get a message something goes wrong:

AssertionError: assert($node instanceof Book) in assert() (line 80 of modules/contrib/book/src/Controller/RouteAccessController.php).

Drupal\book\Controller\RouteAccessController->checkIfBookHasChildren() (Line: 60)
Drupal\book\Controller\RouteAccessController->access()
call_user_func_array() (Line: 66)
Drupal\Core\Access\CustomAccessCheck->access()
call_user_func_array() (Line: 160)
Drupal\Core\Access\AccessManager->performCheck() (Line: 136)
Drupal\Core\Access\AccessManager->check() (Line: 93)
Drupal\Core\Access\AccessManager->checkNamedRoute() (Line: 333)
Drupal\Core\Menu\LocalTaskManager->getTasksBuild() (Line: 375)
Drupal\Core\Menu\LocalTaskManager->getLocalTasks() (Line: 275)
Drupal\navigation\NavigationRenderer->getLocalTasks() (Line: 319)
Drupal\navigation\NavigationRenderer->hasLocalTasks() (Line: 248)
Drupal\navigation\NavigationRenderer->removeLocalTasks() (Line: 111)
Drupal\navigation\Hook\NavigationHooks->blockBuildLocalTasksBlockAlter() (Line: 460)
Drupal\Core\Extension\ModuleHandler->alter() (Line: 91)
Drupal\block\BlockViewBuilder->viewMultiple() (Line: 34)
Drupal\block\BlockViewBuilder->view() (Line: 152)
Drupal\block\Plugin\DisplayVariant\BlockPageVariant->build() (Line: 274)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare() (Line: 131)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse() (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray() (Line: 246)
Symfony\Component\EventDispatcher\EventDispatcher::{closure:Symfony\Component\EventDispatcher\EventDispatcher::optimizeListeners():241}() (Line: 206)
Symfony\Component\EventDispatcher\EventDispatcher->callListeners() (Line: 56)
Symfony\Component\EventDispatcher\EventDispatcher->dispatch() (Line: 188)
Symfony\Component\HttpKernel\HttpKernel->handleRaw() (Line: 76)
Symfony\Component\HttpKernel\HttpKernel->handle() (Line: 53)
Drupal\Core\StackMiddleware\Session->handle() (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle() (Line: 28)
Drupal\Core\StackMiddleware\ContentLength->handle() (Line: 32)
Drupal\big_pipe\StackMiddleware\ContentLength->handle() (Line: 118)
Drupal\page_cache\StackMiddleware\PageCache->pass() (Line: 92)
Drupal\page_cache\StackMiddleware\PageCache->handle() (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle() (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle() (Line: 53)
Drupal\Core\StackMiddleware\AjaxPageState->handle() (Line: 54)
Drupal\Core\StackMiddleware\StackedHttpKernel->handle() (Line: 745)
Drupal\Core\DrupalKernel->handle() (Line: 19)

smustgrave’s picture

I am missing the bar at the bottom showing the title of the previous book, Up, and the title of the next book.

If you do a fresh install this is by design, you have to enable in the display

When I goto /book-1 (the first book page created I get a message something goes wrong:

I'm not able to reproduce this, did you clear cache?

I did find an issue with the preDelete() code.

The previous structure was that all nodes were getting the book key, regardless. So that's stopped now.

promes’s picture

1. Bar at bottom: thats new for me. Thanks.
Of course I did a clear cache.

promes’s picture

I reinstalled v3.0.1 and added the current MR !168. Thanks: Great result. It works now.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Will merge later this evening thanks!

smustgrave’s picture

Status: Reviewed & tested by the community » Fixed

Thanks all glad we got there!

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

promes’s picture

Status: Fixed » Active

Sorry, I have to reopen the issue. In a second site I updated the module to v3.0.2, updated the site and cleared caches.
I went to a book page and got this error:
Error: Call to undefined method Drupal\mynode\Entity\Bundle\CommonNode::getBook() in Drupal\book\Hook\BookHooks->nodeView() (line 184 of /xxx/web/modules/contrib/book/src/Hook/BookHooks.php).
In mynode\Entity\Bundle\CommonNode I don't use any reference to a book.
I presume in line 184 $node->getBook(); is not specific enough.

smustgrave’s picture

if ($display->getComponent('book_navigation') || $display->getComponent('book_navigation_without_tree')) {

This got triggered first

smustgrave’s picture

Status: Active » Fixed

Seems others hit this will do an emergency deploy soon.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

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