1

Backstory

I have created multiple Paragraph bundles. My goal is to have absolute control over the output of these bundles (1) as well as the output of the containing field (2) in a scalable way (for ease of future bundle addition -- there may be 30+ bundles in the future).

Where I'm at

I've created a theme suggestion using HOOK_theme_suggestions_HOOK_alter to override the bundles' output. This allowed me to create custom twig templates for each bundle.

/**
 * Implements hook_theme()
 *
 * Register a theme for each paragraph type
 *
 */
function MYMODULE_theme($existing, $type, $theme, $path){
  $theme_templates  = [];
  $paragraph_types  = MYMODULE_get_paragraph_types();

  // Register custom Paragraph bundle templates
  foreach ($paragraph_types as $paragraph_type){
    $theme_templates['paragraph__MYMODULE__' . $paragraph_type] = [
      'base hook' => 'paragraph'
    ];
  }

  // Register custom Paragraph bundle template fallback
  $theme_templates['paragraph__MYMODULE'] = [
    'base hook' => 'paragraph'
  ];

  // Register custom Paragraph field wrapper
  // $module_path = drupal_get_path('module', 'MYMODULE');
  // $theme_templates['paragraph__MYMODULE__field_wrapper'] = [
  //   'base hook' => 'field',
  //   'template' => 'paragraph--MYMODULE--field-wrapper',
  //   'path' => $module_path . '/templates'
  //  ];

  return $theme_templates;
}

/**
 * Implements HOOK_theme_suggestions_HOOK_alter
 */
function MYMODULE_theme_suggestions_field_alter(array &$suggestions, array $variables, $hook) {
  $field = $variables['element']['#field_name'];

  if( $field == 'field_content_modules' ) {
    $suggestions[] = 'paragraph__MYMODULE__field_wrapper';
  }
}


/**
 * Implements HOOK_theme_suggestions_HOOK_alter
 */
function MYMODULE_theme_suggestions_paragraph_alter(&$suggestions, $variables){
  $entity              = $variables['elements']['#paragraph'];
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
  $type                = $entity->getType();

  $suggestions[] = 'paragraph__MYMODULE';
  $suggestions[] = 'paragraph__MYMODULE__' . $type;
  $suggestions[] = 'paragraph__MYMODULE__' . $type . '__' . $sanitized_view_mode;
}


/**
 * Implements hook_theme_registry_alter()
 */
function MYMODULE_theme_registry_alter(&$theme_registry) {
  $module_path      = drupal_get_path('module', 'MYMODULE');
  $template_objects = drupal_find_theme_templates($theme_registry, '.html.twig', $module_path);
  $paragraph_types  = MYMODULE_get_paragraph_types();

  // 1. Loop through the paragraph types
  // 2. Check if each paragraph exists in the `$template_objects` array
  // 3. If it doesn't exist, remove it from the registry so we don't get any errors
  // 4. If it does exist, set actual path to template
  foreach ($paragraph_types as $type){
    $template = 'paragraph__MYMODULE__' . $type;
    if(!isset($template_objects[$template])){
      unset($theme_registry['paragraph__MYMODULE__' . $type]);
    } else {
      $theme_registry['paragraph__MYMODULE__' . $type]['path'] = $template_objects[$template]['path'];
    }
  }
}


/**
 * Helper function to get a list of paragraph types by machine name
 *
 * @return array
 */
function MYMODULE_get_paragraph_types(){
  $paragraph_bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('paragraph');
  return array_keys($paragraph_bundles);
}

So part 1 is done.

Part 2 is controlling the Paragraph field (not bundle) container HTML. This should be just a plain field template override. I've added a file:

/modules/custom/MYMODULE/templates/paragraph--MYMODULE--field-wrapper.html.twig

...and dropped in the contents of field.html.twig. When I uncomment the hook_theme code above ("Register custom Paragraph field wrapper"), I get this error:

The website encountered an unexpected error. Please try again later.
Recoverable fatal error: Argument 1 passed to Drupal\Core\Render\Element::children() must be of the type array, null given, called in /Users/phil/Sites/glades/www/modules/contrib/paragraphs/paragraphs.module on line 238 and defined in Drupal\Core\Render\Element::children() (line 71 of core/lib/Drupal/Core/Render/Element.php).

Any thoughts as to what I'm doing wrong with the field template override?

Most of this was inspired by a comment by jeremypeter here: https://www.drupal.org/node/2499827#comment-12159763

  • For part 2, you can't register a field template with the name paragraph--, because the naming convention for templates is, that the template name starts with the base hook. Use field--paragraph--MYMODULE--... instead. – 4k4 Jul 24 '17 at 7:37
1

Per 4k4's comment to the original post, it was a naming convention issue as seen here: https://www.drupal.org/docs/8/theming/twig/twig-template-naming-conventions

In the hook_theme and hook_theme_suggestions_HOOK_alter function above, I changed:

paragraph__contentmodules__field_wrapper

to:

field__paragraph__contentmodules__field_wrapper

Working code:

<?php


/**
 * Implements hook_theme()
 *
 * Register a theme for each paragraph type
 *
 */
function MYMODULE_theme($existing, $type, $theme, $path){
  $theme_templates  = [];
  $paragraph_types  = MYMODULE_get_paragraph_types();

  // Register custom Paragraph bundle templates
  foreach ($paragraph_types as $paragraph_type){
    $theme_templates['paragraph__MYMODULE__' . $paragraph_type] = [
      'base hook' => 'paragraph'
    ];
  }

  // Register custom Paragraph bundle template fallback
  $theme_templates['paragraph__MYMODULE'] = [
    'base hook' => 'paragraph'
  ];

  // Register custom Paragraph field wrapper
  $theme_templates['field__paragraph__MYMODULE__field_wrapper'] = [
    'base hook' => 'field',
  ];

  return $theme_templates;
}

/**
 * Implements HOOK_theme_suggestions_HOOK_alter
 */
function MYMODULE_theme_suggestions_field_alter(array &$suggestions, array $variables, $hook) {
  $field = $variables['element']['#field_name'];

  if( $field == 'field_content_modules' ) {
    $suggestions[] = 'field__paragraph__MYMODULE__field_wrapper';
  }
}


/**
 * Implements HOOK_theme_suggestions_HOOK_alter
 */
function MYMODULE_theme_suggestions_paragraph_alter(&$suggestions, $variables){
  $entity              = $variables['elements']['#paragraph'];
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
  $type                = $entity->getType();

  $suggestions[] = 'paragraph__MYMODULE';
  $suggestions[] = 'paragraph__MYMODULE__' . $type;
  $suggestions[] = 'paragraph__MYMODULE__' . $type . '__' . $sanitized_view_mode;
}


/**
 * Implements hook_theme_registry_alter()
 */
function MYMODULE_theme_registry_alter(&$theme_registry) {
  $module_path      = drupal_get_path('module', 'MYMODULE');
  $template_objects = drupal_find_theme_templates($theme_registry, '.html.twig', $module_path);
  $paragraph_types  = MYMODULE_get_paragraph_types();

  // 1. Loop through the paragraph types
  // 2. Check if each paragraph exists in the `$template_objects` array
  // 3. If it doesn't exist, remove it from the registry so we don't get any errors
  // 4. If it does exist, set actual path to template
  foreach ($paragraph_types as $type){
    $template = 'paragraph__MYMODULE__' . $type;
    if(!isset($template_objects[$template])){
      unset($theme_registry['paragraph__MYMODULE__' . $type]);
    } else {
      $theme_registry['paragraph__MYMODULE__' . $type]['path'] = $template_objects[$template]['path'];
    }
  }
}


/**
 * Helper function to get a list of paragraph types by machine name
 *
 * @return array
 */
function MYMODULE_get_paragraph_types(){
  $paragraph_bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('paragraph');
  return array_keys($paragraph_bundles);
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.