Problem/Motivation

The content_firstmodule currently implements its own entity rendering logic in ContentFirstBuilder::renderContent(), manually handling:

  • View builder instantiation and render array construction
  • Drupal deprecation compatibility (renderInIsolation vs renderPlain)
  • Context management during rendering

The entity_render_contextmodule 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

Integration benefits:

  • Eliminates code duplication between modules
  • Improves performance through shared caching
  • Reduces maintenance burden (deprecation logic managed in one place)
  • Better separation of concerns (entity_render_context handles rendering complexity)
  • Ensures consistent rendering behavior across dependent modules

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:

  1. Enable both entity_render_contextand content_firstmodules
  2. Clear cache: drush cr
  3. Navigate to any node and click the "Content First" tab
  4. Verify content renders correctly (no visual changes expected)
  5. Export content as markdown and verify formatting is preserved

Proposed resolution

Refactor content_first to use entity_render_context.renderer service:

  1. Add module dependency: Declare entity_render_contextas a dependency in content_first.info.yml
  2. Update service injection: Replace @rendererwith @entity_render_context.rendererin content_first.services.yml
  3. Simplify ContentFirstBuilder class:
    • Remove imports: DeprecationHelper, RendererInterface
    • Add import: EntityRenderContextInterface
    • Update property: $renderer$entityRenderContextwith new type
    • Update constructor parameter and assignment
    • Replace renderContent()method with single service call
  4. No interface changes required: ContentFirstBuilderInterface::buildContent()signature remains unchanged
  5. No consumer changes needed: Controllers and plugins depend on the interface, not the implementation

Before (ContentFirstBuilder::renderContent):

	protected function renderContent(ContentEntityInterface $entity, string $view_mode) : string {               
    $view_builder = $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId());                                             
    $pre_render = $view_builder->view($entity, $view_mode);                                                                                 
    $render_output = DeprecationHelper::backwardsCompatibleCall(                                                                               
     currentVersion: \Drupal::VERSION,                                                                                                         
     deprecatedVersion: '10.3',                                                                                                                
     currentCallable: fn() => $this->renderer->renderInIsolation($pre_render),                                                        
     deprecatedCallable: fn() => $this->renderer->renderPlain($pre_render),                                                           
    );                                                                                                                                         
    return $render_output;                                                                                                                     
  }                                                                                                                                            
  

After (using entity_render_context):

	protected function renderContent(ContentEntityInterface $entity, string $view_mode) : string {               
    // Use entity_render_context service to render the entity.                                                                                 
    // Parameters: entity, view_mode, theme (NULL=default), account (NULL=anonymous), langcode (NULL=entity language)                          
    $render_output = $this->entityRenderContext->renderEntity(                                                                           
      $entity,                                                                                                                                 
      $view_mode,                                                                                                                              
      NULL,  // Use current/default theme                                                                                                      
      NULL,  // Render as anonymous user (uid 0)                                                                                               
      NULL,  // Use entity's language                                                                                                          
    );                                                                                                                                         
                                                                                                                                               
    return $render_output ?? '';                                                                                                               
  }                                                                                                                                            
  

Rendering context preserved:

  • Theme: NULL parameter uses current/default theme (matches existing behavior)
  • User: NULL parameter renders as anonymous user uid 0 (clean exports for content review)
  • Language: NULL parameter respects entity's language (matches existing behavior)

Remaining tasks

  • Apply changes to content_first module in the repository
  • Clear Drupal cache to rebuild service container
  • Run PHPCS and PHPStan checks on modified files
  • Perform functional testing:
    • View content_first tab on various node types
    • Export content as markdown ZIP
    • Test token replacements
    • Verify multilingual content renders correctly
  • Regression testing: Compare output before/after changes (should be identical)
  • Performance validation: Verify caching benefits on repeated renders
  • Prepare patch for drupal.org contribution

User interface changes

None. This is a backend refactoring. The content_first module UI, functionality, and output remain unchanged.

API changes

Public API (no breaking changes):

  • ContentFirstBuilderInterface::buildContent()- Signature and behavior unchanged
  • All public/protected methods retain same signatures

Internal implementation changes (not part of public API):

  • Property $rendererrenamed to $entityRenderContext(internal property, not part of public API)
  • Constructor parameter type changed from RendererInterfaceto EntityRenderContextInterface(service injection, managed by container)

New module dependency:

  • content_first now requires entity_render_contextmodule to function

Data model changes

None. This refactoring affects only the rendering service layer. No database schema, configuration, or entity definitions are changed.

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

eduardo morales alberti’s picture

Status: Active » Needs review

Coding standards stabilized, ready to review

eduardo morales alberti’s picture

Version: 1.x-dev » 2.x-dev

eduardo morales alberti’s picture

Status: Needs review » Fixed

Fixed! Created new release with the new dependency

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.