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
paragraph--
, because the naming convention for templates is, that the template name starts with the base hook. Usefield--paragraph--MYMODULE--...
instead.