Loading core/lib/Drupal/Core/Entity/EntityViewBuilder.php +6 −0 Original line number Diff line number Diff line Loading @@ -614,6 +614,12 @@ protected function getRenderRecursionKey(array $build): string { $recursion_keys[] = spl_object_id($entity); } // Ensure that recursion keys are not leaking between Fibers. if ($fiber = \Fiber::getCurrent()) { $recursion_keys[] = 'fiber_id'; $recursion_keys[] = spl_object_id($fiber); } if ($entity instanceof TranslatableDataInterface) { $recursion_keys[] = $entity->language()->getId(); } Loading core/modules/system/tests/modules/entity_test/config/schema/entity_test.schema.yml +11 −0 Original line number Diff line number Diff line Loading @@ -48,3 +48,14 @@ entity_test.entity_test_no_id_bundle.*: id: type: string label: 'Machine-readable name' block.settings.entity_test_block: type: block_settings label: 'Entity test block' mapping: entity_type_id: type: string label: 'Entity type ID' entity_id: type: integer label: 'Entity ID' core/modules/system/tests/modules/entity_test/src/Plugin/Block/EntityTestBlock.php 0 → 100644 +62 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\entity_test\Plugin\Block; use Drupal\Core\Block\Attribute\Block; use Drupal\Core\Block\BlockBase; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; /** * Provides a block that renders an entity with parallel placeholder rendering. */ #[Block( id: 'entity_test_block', admin_label: new TranslatableMarkup('Entity test block'), )] class EntityTestBlock extends BlockBase implements ContainerFactoryPluginInterface { public function __construct( array $configuration, $plugin_id, $plugin_definition, protected EntityTypeManagerInterface $entityTypeManager, ) { parent::__construct($configuration, $plugin_id, $plugin_definition); } /** * {@inheritdoc} */ public function defaultConfiguration(): array { return [ 'entity_type_id' => 'entity_test', 'entity_id' => NULL, ] + parent::defaultConfiguration(); } /** * {@inheritdoc} */ public function createPlaceholder(): bool { // Render as a placeholder so this block is rendered in a Fiber, enabling // tests to verify concurrent entity rendering behavior. return TRUE; } /** * {@inheritdoc} */ public function build(): array { $entity_type_id = $this->configuration['entity_type_id']; $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($this->configuration['entity_id']); if ($entity) { return $this->entityTypeManager->getViewBuilder($entity_type_id)->view($entity); } return []; } } core/tests/Drupal/FunctionalTests/Entity/EntityConcurrentRenderTest.php 0 → 100644 +95 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\FunctionalTests\Entity; use Drupal\entity_test\Entity\EntityTest; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\Tests\BrowserTestBase; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; /** * Tests that the same entity can be rendered multiple times on a page. */ #[Group('Entity')] #[RunTestsInSeparateProcesses] class EntityConcurrentRenderTest extends BrowserTestBase { /** * {@inheritdoc} */ protected static $modules = [ 'block', 'entity_test', 'field', 'filter', 'text', ]; /** * {@inheritdoc} */ protected $defaultTheme = 'stark'; /** * {@inheritdoc} */ protected function setUp(): void { parent::setUp(); // Add a formatted text field. The text format processing creates filter // placeholders during rendering, which causes the block's Fiber to // suspend and allows other block Fibers to interleave. FieldStorageConfig::create([ 'entity_type' => 'entity_test', 'field_name' => 'body', 'type' => 'text_long', ])->save(); FieldConfig::create([ 'entity_type' => 'entity_test', 'bundle' => 'entity_test', 'field_name' => 'body', 'label' => 'Body', ])->save(); \Drupal::service('entity_display.repository') ->getViewDisplay('entity_test', 'entity_test') ->setComponent('body') ->save(); $this->drupalLogin($this->drupalCreateUser(['view test entity'])); } /** * Tests that two blocks rendering the same entity both produce output. */ public function testSameEntityInMultipleBlocks(): void { $entity = EntityTest::create([ 'name' => 'Unique entity content', 'body' => ['value' => 'Body text', 'format' => 'plain_text'], ]); $entity->save(); $this->drupalPlaceBlock('entity_test_block', [ 'id' => 'first', 'label' => 'First', 'entity_id' => $entity->id(), ]); $this->drupalPlaceBlock('entity_test_block', [ 'id' => 'second', 'label' => 'Second', 'entity_id' => $entity->id(), ]); $this->drupalGet('<front>'); // Both blocks should render the entity content. $first = $this->assertSession()->elementExists('css', '#block-first'); $second = $this->assertSession()->elementExists('css', '#block-second'); $this->assertStringContainsString('Unique entity content', $first->getText()); $this->assertStringContainsString('Unique entity content', $second->getText()); } } Loading
core/lib/Drupal/Core/Entity/EntityViewBuilder.php +6 −0 Original line number Diff line number Diff line Loading @@ -614,6 +614,12 @@ protected function getRenderRecursionKey(array $build): string { $recursion_keys[] = spl_object_id($entity); } // Ensure that recursion keys are not leaking between Fibers. if ($fiber = \Fiber::getCurrent()) { $recursion_keys[] = 'fiber_id'; $recursion_keys[] = spl_object_id($fiber); } if ($entity instanceof TranslatableDataInterface) { $recursion_keys[] = $entity->language()->getId(); } Loading
core/modules/system/tests/modules/entity_test/config/schema/entity_test.schema.yml +11 −0 Original line number Diff line number Diff line Loading @@ -48,3 +48,14 @@ entity_test.entity_test_no_id_bundle.*: id: type: string label: 'Machine-readable name' block.settings.entity_test_block: type: block_settings label: 'Entity test block' mapping: entity_type_id: type: string label: 'Entity type ID' entity_id: type: integer label: 'Entity ID'
core/modules/system/tests/modules/entity_test/src/Plugin/Block/EntityTestBlock.php 0 → 100644 +62 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\entity_test\Plugin\Block; use Drupal\Core\Block\Attribute\Block; use Drupal\Core\Block\BlockBase; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; /** * Provides a block that renders an entity with parallel placeholder rendering. */ #[Block( id: 'entity_test_block', admin_label: new TranslatableMarkup('Entity test block'), )] class EntityTestBlock extends BlockBase implements ContainerFactoryPluginInterface { public function __construct( array $configuration, $plugin_id, $plugin_definition, protected EntityTypeManagerInterface $entityTypeManager, ) { parent::__construct($configuration, $plugin_id, $plugin_definition); } /** * {@inheritdoc} */ public function defaultConfiguration(): array { return [ 'entity_type_id' => 'entity_test', 'entity_id' => NULL, ] + parent::defaultConfiguration(); } /** * {@inheritdoc} */ public function createPlaceholder(): bool { // Render as a placeholder so this block is rendered in a Fiber, enabling // tests to verify concurrent entity rendering behavior. return TRUE; } /** * {@inheritdoc} */ public function build(): array { $entity_type_id = $this->configuration['entity_type_id']; $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($this->configuration['entity_id']); if ($entity) { return $this->entityTypeManager->getViewBuilder($entity_type_id)->view($entity); } return []; } }
core/tests/Drupal/FunctionalTests/Entity/EntityConcurrentRenderTest.php 0 → 100644 +95 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\FunctionalTests\Entity; use Drupal\entity_test\Entity\EntityTest; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\Tests\BrowserTestBase; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; /** * Tests that the same entity can be rendered multiple times on a page. */ #[Group('Entity')] #[RunTestsInSeparateProcesses] class EntityConcurrentRenderTest extends BrowserTestBase { /** * {@inheritdoc} */ protected static $modules = [ 'block', 'entity_test', 'field', 'filter', 'text', ]; /** * {@inheritdoc} */ protected $defaultTheme = 'stark'; /** * {@inheritdoc} */ protected function setUp(): void { parent::setUp(); // Add a formatted text field. The text format processing creates filter // placeholders during rendering, which causes the block's Fiber to // suspend and allows other block Fibers to interleave. FieldStorageConfig::create([ 'entity_type' => 'entity_test', 'field_name' => 'body', 'type' => 'text_long', ])->save(); FieldConfig::create([ 'entity_type' => 'entity_test', 'bundle' => 'entity_test', 'field_name' => 'body', 'label' => 'Body', ])->save(); \Drupal::service('entity_display.repository') ->getViewDisplay('entity_test', 'entity_test') ->setComponent('body') ->save(); $this->drupalLogin($this->drupalCreateUser(['view test entity'])); } /** * Tests that two blocks rendering the same entity both produce output. */ public function testSameEntityInMultipleBlocks(): void { $entity = EntityTest::create([ 'name' => 'Unique entity content', 'body' => ['value' => 'Body text', 'format' => 'plain_text'], ]); $entity->save(); $this->drupalPlaceBlock('entity_test_block', [ 'id' => 'first', 'label' => 'First', 'entity_id' => $entity->id(), ]); $this->drupalPlaceBlock('entity_test_block', [ 'id' => 'second', 'label' => 'Second', 'entity_id' => $entity->id(), ]); $this->drupalGet('<front>'); // Both blocks should render the entity content. $first = $this->assertSession()->elementExists('css', '#block-first'); $second = $this->assertSession()->elementExists('css', '#block-second'); $this->assertStringContainsString('Unique entity content', $first->getText()); $this->assertStringContainsString('Unique entity content', $second->getText()); } }