Problem/Motivation
The entity_mesh module currently implements its own entity rendering logic in EntityRender::getFullEntityDom(), manually handling:
- Theme switching with custom
ThemeSwitcherservice - Account switching for rendering as specific users
- Language negotiation with custom
LanguageNegotiatorSwitcherandStaticLanguageNegotiatorservices - View builder instantiation and render array construction
- Drupal deprecation compatibility (renderInIsolation vs renderPlain)
- Complex try-catch-finally blocks to ensure context restoration
The entity_render_context module was created based on patterns from entity_mesh itself and provides an identical service for rendering entities with configurable context (theme, user, language, view mode), including:
- Built-in request-scoped caching to avoid redundant renders
- Proper context restoration with guaranteed cleanup (via finally blocks)
- Centralized deprecation handling for Drupal 10.3+ compatibility
- Comprehensive error handling with logging
- Reusable theme and language switching services
Integration benefits:
- Eliminates ~300 lines of duplicate code
- Simplifies
getFullEntityDom()from 65 lines to ~25 lines - Improves performance through shared caching between entity_render_context's internal cache and entity_mesh's DOM cache
- Reduces maintenance burden (rendering logic managed in one place)
- Better separation of concerns (entity_mesh focuses on link analysis, entity_render_context handles rendering)
- Ensures consistent rendering behavior across dependent modules (content_first, etc.)
Steps to reproduce
N/A - This is a refactoring to integrate an external service, not a bug fix.
To verify the integration after applying changes:
- Enable
entity_render_contextandentity_meshmodules - Run database updates:
drush updb(installs entity_render_context if upgrading) - Clear cache:
drush cr - Navigate to the Entity Mesh UI (/admin/content/entity-mesh)
- Trigger entity mesh analysis on content
- Verify links are detected and analyzed correctly (no visual changes expected)
- Check that DOM parsing and link extraction work as before
Proposed resolution
Refactor entity_mesh to use entity_render_context.renderer service:
- Add module dependency: Declare
entity_render_contextas a dependency inentity_mesh.info.ymlandcomposer.json - Update service injection: Replace multiple services (
@renderer,@account_switcher,@entity_mesh.theme_switcher,@entity_mesh.language_negotiator_switcher) with single
@entity_render_context.rendererservice inentity_mesh.services.yml - Simplify EntityRender class:
- Remove imports:
DeprecationHelper,RendererInterface,AccountSwitcherInterface,LanguageNegotiatorSwitcher - Add import:
EntityRenderContextInterface - Remove properties:
$renderer,$accountSwitcher,$languageNegotiatorSwitcher,$themeSwitcher - Add property:
$entityRenderContextwith typeEntityRenderContextInterface - Update constructor to accept
EntityRenderContextInterfaceinstead of individual rendering services - Simplify
getFullEntityDom()method to use single service call
- Remove imports:
- Remove duplicate service classes:
- Delete
src/ThemeSwitcher.php - Delete
src/ThemeSwitcherInterface.php - Delete
src/Language/LanguageNegotiatorSwitcher.php - Delete
src/Language/StaticLanguageNegotiator.php - Remove service definitions from
entity_mesh.services.yml
- Delete
- Add update hook: Create
entity_mesh_update_10022()to install entity_render_context module for existing installations - Update tests: Modify
EntityRenderTest.phpto use new constructor signature with mockedEntityRenderContextInterface
Before (EntityRender::getFullEntityDom):
protected function getFullEntityDom(EntityInterface $entity, string $langcode) {
// Generate cache key based on entity type, ID, and language.
$cache_key = $entity->getEntityTypeId() . ':' . $entity->id() . ':' . $langcode;
// Check if DOM is already cached.
if (isset($this->domCache[$cache_key])) {
return $this->domCache[$cache_key];
}
// Switch to the default theme in case the admin theme is enabled.
$previous_theme = $this->themeSwitcher->switchToDefault();
$this->accountSwitcher->switchTo($this->entityMeshRepository->getMeshAccount());
// Switch system to the entity language so the entity is fully rendered
// in the specific language.
$this->languageNegotiatorSwitcher->switchLanguage($entity->language());
// Load all modules before rendering.
if (!$this->moduleHandler->isLoaded()) {
$this->moduleHandler->loadAll();
}
try {
// Render entity HTML output:
$view_mode = 'full';
$view_builder = $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId());
$pre_render = $view_builder->view($entity, $view_mode, $langcode);
$render_output = DeprecationHelper::backwardsCompatibleCall(
currentVersion: \Drupal::VERSION,
deprecatedVersion: '10.3',
currentCallable: fn() => $this->renderer->renderInIsolation($pre_render),
deprecatedCallable: fn() => $this->renderer->renderPlain($pre_render),
);
// Switches back to the current language:
$this->languageNegotiatorSwitcher->switchBack();
// Switch back to the current user:
$this->accountSwitcher->switchBack();
// Restore the original theme.
$this->themeSwitcher->switchBack($previous_theme);
// Parse the HTML.
$dom = new \DOMDocument();
@$dom->loadHTML('<?xml encoding="UTF-8">' . $render_output, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
// Cache the DOM before returning.
$this->domCache[$cache_key] = $dom;
return $dom;
}
catch (\Exception $e) {
// Ensure we switch everything back in case of an error.
$this->languageNegotiatorSwitcher->switchBack();
$this->accountSwitcher->switchBack();
$this->themeSwitcher->switchBack($previous_theme);
throw $e;
}
}
After (using entity_render_context):
protected function getFullEntityDom(EntityInterface $entity, string $langcode) {
// Generate cache key based on entity type, ID, and language.
$cache_key = $entity->getEntityTypeId() . ':' . $entity->id() . ':' . $langcode;
// Check if DOM is already cached.
if (isset($this->domCache[$cache_key])) {
return $this->domCache[$cache_key];
}
// Use entity_render_context service to render the entity.
// This handles theme switching, account switching, and language switching.
$render_output = $this->entityRenderContext->renderEntity(
$entity,
'full',
NULL, // Use default theme
$this->entityMeshRepository->getMeshAccount(),
$langcode
);
if ($render_output === NULL) {
throw new \Exception('Failed to render entity');
}
// Parse the HTML.
$dom = new \DOMDocument();
@$dom->loadHTML('<?xml encoding="UTF-8">' . $render_output, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
// Cache the DOM before returning.
$this->domCache[$cache_key] = $dom;
return $dom;
}
Rendering context preserved:
- Theme: NULL parameter uses default theme (matches existing behavior via switchToDefault())
- User: Uses mesh account for rendering (preserves existing behavior)
- Language: Explicit langcode parameter respects entity's language (matches existing behavior)
- Context restoration: entity_render_context automatically restores all contexts via finally blocks
User interface changes
None. This is a backend refactoring. The entity_mesh module UI, functionality, and link analysis output remain unchanged.
API changes
Breaking changes (major version required):
- Removed services:
entity_mesh.theme_switcher- Useentity_render_context.theme_switcherinsteadentity_mesh.language_negotiator_switcher- Useentity_render_context.language_negotiator_switcherinsteadentity_mesh.static_language_negotiator- Useentity_render_context.static_language_negotiatorinstead
- Removed classes:
Drupal\entity_mesh\ThemeSwitcherDrupal\entity_mesh\ThemeSwitcherInterfaceDrupal\entity_mesh\Language\LanguageNegotiatorSwitcherDrupal\entity_mesh\Language\StaticLanguageNegotiator
EntityRenderconstructor signature changed:- Removed parameters:
RendererInterface $renderer,AccountSwitcherInterface $account_switcher,LanguageNegotiatorSwitcher $language_negotiator_switcher,ThemeSwitcher $theme_switcher - Added parameter:
EntityRenderContextInterface $entity_render_context
- Removed parameters:
Public API (behavioral compatibility maintained):
- All public methods of
EntityRenderretain same signatures and behavior getFullEntityDom()returns identical DOM structure- Link extraction logic unchanged
New module dependency:
- entity_mesh now requires
entity_render_contextmodule to function
Upgrade path for custom code:
If you inject entity_mesh.theme_switcher, entity_mesh.language_negotiator_switcher, or entity_mesh.static_language_negotiator services, update to use equivalent services from entity_render_context module:
@entity_mesh.theme_switcher→@entity_render_context.theme_switcher@entity_mesh.language_negotiator_switcher→@entity_render_context.language_negotiator_switcher@entity_mesh.static_language_negotiator→@entity_render_context.static_language_negotiator
If you extend EntityRender class, update your constructor to match the new signature.
Data model changes
None. This refactoring affects only the rendering service layer. No database schema, configuration, or entity definitions are changed. Existing entity mesh data remains compatible.
Upgrade path for existing installations:
- Update entity_mesh module code to new version
- Run database updates:
drush updb- Update hook
entity_mesh_update_10022()automatically installsentity_render_contextmodule
- Update hook
- Clear cache:
drush cr - No manual intervention required
Issue fork entity_mesh-3573664
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
Comment #3
eduardo morales albertiReady
Comment #4
eduardo morales albertiComment #5
eduardo morales albertiReady to review
Comment #7
eduardo morales albertiMerged, fixed